Feature: Anlagevermögen – Navigation, Tabelle assets, CRUD und Test
This commit is contained in:
parent
36ee8e4f83
commit
921ba57419
9 changed files with 524 additions and 0 deletions
13
Changes.md
13
Changes.md
|
|
@ -2,6 +2,19 @@
|
|||
|
||||
---
|
||||
|
||||
## 2026-04-12 – Anlagevermögen (Wirtschaftsgüter)
|
||||
|
||||
### Neu
|
||||
- Navigation: neuer Abschnitt „Betriebsvermögen" mit Menüpunkt „Anlagevermögen"
|
||||
- `migrations/028_assets_anlagevermoegen.sql`: neue Tabelle `assets` für Wirtschaftsgüter
|
||||
- `src/Models/Asset.php`: CRUD-Model mit automatischer laufender Nummer und Geschäftsjahrzuordnung
|
||||
- `src/Controllers/AssetController.php`: Liste, Anlegen, Bearbeiten, Löschen
|
||||
- `src/Views/assets/list.php`: Anlagenspiegel-Tabelle mit Bleistift- und Papierkorb-Aktionen
|
||||
- `src/Views/assets/form.php`: Formular für Wirtschaftsgut (Bezeichnung, Anschaffungsdatum, Kosten, Zugänge, Abgänge, AfA, Nutzungsdauer, Notizen)
|
||||
- `tests/20_AnlagevermoegensTest.php`: 14 Tests für CRUD-Operationen
|
||||
|
||||
---
|
||||
|
||||
## 2026-04-12 – Backup: smarte Rotation und Lock-Schutz
|
||||
|
||||
### Geändert
|
||||
|
|
|
|||
20
migrations/028_assets_anlagevermoegen.sql
Normal file
20
migrations/028_assets_anlagevermoegen.sql
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
-- Migration 028: Tabelle für Anlagevermögen (Wirtschaftsgüter)
|
||||
CREATE TABLE IF NOT EXISTS assets (
|
||||
id INT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
lfd_nr INT UNSIGNED NOT NULL COMMENT 'Laufende Nummer im Anlagenspiegel',
|
||||
bezeichnung VARCHAR(255) NOT NULL COMMENT 'Bezeichnung des Wirtschaftsguts',
|
||||
anschaffungsdatum DATE NOT NULL,
|
||||
anschaffungskosten DECIMAL(12,2) NOT NULL DEFAULT 0.00,
|
||||
zugaenge DECIMAL(12,2) NOT NULL DEFAULT 0.00 COMMENT 'Zugänge im laufenden Wirtschaftsjahr',
|
||||
abgaenge DECIMAL(12,2) NOT NULL DEFAULT 0.00 COMMENT 'Abgänge im laufenden Wirtschaftsjahr',
|
||||
abschreibung_jahr DECIMAL(12,2) NOT NULL DEFAULT 0.00 COMMENT 'Abschreibung des laufenden Wirtschaftsjahres',
|
||||
nutzungsdauer SMALLINT UNSIGNED NULL COMMENT 'Nutzungsdauer in Jahren (für AfA-Berechnung)',
|
||||
notizen TEXT NULL,
|
||||
fiscal_year_id INT UNSIGNED NULL,
|
||||
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (id),
|
||||
KEY idx_assets_fiscal_year (fiscal_year_id),
|
||||
KEY idx_assets_lfd_nr (lfd_nr)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
|
||||
COMMENT='Anlagevermögen / Wirtschaftsgüter (Anlagenspiegel)';
|
||||
|
|
@ -19,6 +19,7 @@ use Steuerkram\Controllers\UstvaController;
|
|||
use Steuerkram\Controllers\EuerController;
|
||||
use Steuerkram\Controllers\SettingsController;
|
||||
use Steuerkram\Controllers\LoginController;
|
||||
use Steuerkram\Controllers\AssetController;
|
||||
use Steuerkram\Controllers\UserAdminController;
|
||||
|
||||
Config::load();
|
||||
|
|
@ -97,6 +98,14 @@ $router->post('/invoices/{id}/cancel', fn($p) => (new InvoiceController())->canc
|
|||
$router->post('/invoices/{id}/lines', fn($p) => (new InvoiceController())->addLine($p));
|
||||
$router->post('/invoices/{id}/lines/{line_id}/delete', fn($p) => (new InvoiceController())->deleteLine($p));
|
||||
|
||||
// ── Anlagevermögen ───────────────────────────────────────────
|
||||
$router->get('/assets', fn($p) => (new AssetController())->index($p));
|
||||
$router->get('/assets/new', fn($p) => (new AssetController())->create($p));
|
||||
$router->post('/assets', fn($p) => (new AssetController())->store($p));
|
||||
$router->get('/assets/{id}/edit', fn($p) => (new AssetController())->edit($p));
|
||||
$router->post('/assets/{id}', fn($p) => (new AssetController())->update($p));
|
||||
$router->post('/assets/{id}/delete', fn($p) => (new AssetController())->destroy($p));
|
||||
|
||||
// ── UStVA ────────────────────────────────────────────────────
|
||||
$router->get('/tax/ustva', fn($p) => (new UstvaController())->index($p));
|
||||
$router->get('/tax/ustva/{year}/{month}', fn($p) => (new UstvaController())->show($p));
|
||||
|
|
|
|||
154
src/Controllers/AssetController.php
Normal file
154
src/Controllers/AssetController.php
Normal file
|
|
@ -0,0 +1,154 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Steuerkram\Controllers;
|
||||
|
||||
use Steuerkram\Models\Asset;
|
||||
use Steuerkram\View;
|
||||
|
||||
class AssetController
|
||||
{
|
||||
public function index(array $params): void
|
||||
{
|
||||
$assets = Asset::all();
|
||||
|
||||
$view = new View();
|
||||
$view->render('layout/base.php', [
|
||||
'pageTitle' => 'Anlagevermögen',
|
||||
'content' => $this->renderView('assets/list', [
|
||||
'assets' => $assets,
|
||||
]),
|
||||
'topbarActions' => '<a href="/assets/new" class="btn btn-primary">+ Wirtschaftsgut anlegen</a>',
|
||||
]);
|
||||
}
|
||||
|
||||
public function create(array $params): void
|
||||
{
|
||||
$view = new View();
|
||||
$view->render('layout/base.php', [
|
||||
'pageTitle' => 'Wirtschaftsgut anlegen',
|
||||
'content' => $this->renderView('assets/form', [
|
||||
'asset' => [],
|
||||
'errors' => [],
|
||||
'action' => '/assets',
|
||||
]),
|
||||
]);
|
||||
}
|
||||
|
||||
public function store(array $params): void
|
||||
{
|
||||
$data = $this->extractPost();
|
||||
$errors = $this->validate($data);
|
||||
|
||||
if ($errors) {
|
||||
$view = new View();
|
||||
$view->render('layout/base.php', [
|
||||
'pageTitle' => 'Wirtschaftsgut anlegen',
|
||||
'content' => $this->renderView('assets/form', [
|
||||
'asset' => $data,
|
||||
'errors' => $errors,
|
||||
'action' => '/assets',
|
||||
]),
|
||||
]);
|
||||
return;
|
||||
}
|
||||
|
||||
Asset::create($data);
|
||||
View::redirect('/assets', 'Wirtschaftsgut erfolgreich angelegt.');
|
||||
}
|
||||
|
||||
public function edit(array $params): void
|
||||
{
|
||||
$asset = Asset::find((int)$params['id']);
|
||||
if (!$asset) { http_response_code(404); return; }
|
||||
|
||||
$view = new View();
|
||||
$view->render('layout/base.php', [
|
||||
'pageTitle' => 'Wirtschaftsgut bearbeiten',
|
||||
'content' => $this->renderView('assets/form', [
|
||||
'asset' => $asset,
|
||||
'errors' => [],
|
||||
'action' => '/assets/' . $asset['id'],
|
||||
]),
|
||||
]);
|
||||
}
|
||||
|
||||
public function update(array $params): void
|
||||
{
|
||||
$id = (int)$params['id'];
|
||||
$asset = Asset::find($id);
|
||||
if (!$asset) { http_response_code(404); return; }
|
||||
|
||||
$data = $this->extractPost();
|
||||
$errors = $this->validate($data);
|
||||
|
||||
if ($errors) {
|
||||
$view = new View();
|
||||
$view->render('layout/base.php', [
|
||||
'pageTitle' => 'Wirtschaftsgut bearbeiten',
|
||||
'content' => $this->renderView('assets/form', [
|
||||
'asset' => array_merge($asset, $data),
|
||||
'errors' => $errors,
|
||||
'action' => '/assets/' . $id,
|
||||
]),
|
||||
]);
|
||||
return;
|
||||
}
|
||||
|
||||
Asset::update($id, $data);
|
||||
View::redirect('/assets', 'Wirtschaftsgut aktualisiert.');
|
||||
}
|
||||
|
||||
public function destroy(array $params): void
|
||||
{
|
||||
if (!Asset::delete((int)$params['id'])) {
|
||||
View::redirect('/assets', 'Wirtschaftsgut konnte nicht gelöscht werden.', 'error');
|
||||
return;
|
||||
}
|
||||
View::redirect('/assets', 'Wirtschaftsgut gelöscht.');
|
||||
}
|
||||
|
||||
// ── Private helpers ──────────────────────────────────────────
|
||||
|
||||
private function extractPost(): array
|
||||
{
|
||||
return [
|
||||
'bezeichnung' => trim($_POST['bezeichnung'] ?? ''),
|
||||
'anschaffungsdatum' => trim($_POST['anschaffungsdatum'] ?? ''),
|
||||
'anschaffungskosten' => $_POST['anschaffungskosten'] ?? '0',
|
||||
'zugaenge' => $_POST['zugaenge'] ?? '0',
|
||||
'abgaenge' => $_POST['abgaenge'] ?? '0',
|
||||
'abschreibung_jahr' => $_POST['abschreibung_jahr'] ?? '0',
|
||||
'nutzungsdauer' => trim($_POST['nutzungsdauer'] ?? ''),
|
||||
'notizen' => trim($_POST['notizen'] ?? ''),
|
||||
];
|
||||
}
|
||||
|
||||
private function validate(array $data): array
|
||||
{
|
||||
$errors = [];
|
||||
|
||||
if ($data['bezeichnung'] === '') {
|
||||
$errors['bezeichnung'] = 'Pflichtfeld';
|
||||
}
|
||||
if ($data['anschaffungsdatum'] === '') {
|
||||
$errors['anschaffungsdatum'] = 'Pflichtfeld';
|
||||
} elseif (!strtotime($data['anschaffungsdatum'])) {
|
||||
$errors['anschaffungsdatum'] = 'Ungültiges Datum';
|
||||
}
|
||||
if (!is_numeric($data['anschaffungskosten']) || (float)$data['anschaffungskosten'] < 0) {
|
||||
$errors['anschaffungskosten'] = 'Muss eine positive Zahl sein';
|
||||
}
|
||||
|
||||
return $errors;
|
||||
}
|
||||
|
||||
private function renderView(string $template, array $data): string
|
||||
{
|
||||
extract($data, EXTR_SKIP);
|
||||
ob_start();
|
||||
include dirname(__DIR__) . '/Views/' . $template . '.php';
|
||||
return ob_get_clean();
|
||||
}
|
||||
}
|
||||
113
src/Models/Asset.php
Normal file
113
src/Models/Asset.php
Normal file
|
|
@ -0,0 +1,113 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Steuerkram\Models;
|
||||
|
||||
use Steuerkram\Database;
|
||||
|
||||
class Asset
|
||||
{
|
||||
public static function all(?int $fiscalYearId = null): array
|
||||
{
|
||||
$where = '1=1';
|
||||
$params = [];
|
||||
|
||||
if ($fiscalYearId !== null) {
|
||||
$where = 'fiscal_year_id = ?';
|
||||
$params[] = $fiscalYearId;
|
||||
}
|
||||
|
||||
return Database::fetchAll(
|
||||
"SELECT * FROM assets WHERE $where ORDER BY lfd_nr ASC, id ASC",
|
||||
$params
|
||||
);
|
||||
}
|
||||
|
||||
public static function find(int $id): array|false
|
||||
{
|
||||
return Database::fetchOne('SELECT * FROM assets WHERE id = ?', [$id]);
|
||||
}
|
||||
|
||||
public static function nextLfdNr(): int
|
||||
{
|
||||
$row = Database::fetchOne('SELECT COALESCE(MAX(lfd_nr), 0) + 1 AS next FROM assets');
|
||||
return (int)($row['next'] ?? 1);
|
||||
}
|
||||
|
||||
public static function create(array $data): int
|
||||
{
|
||||
$fyId = null;
|
||||
if (!empty($data['anschaffungsdatum'])) {
|
||||
$year = (int)date('Y', strtotime($data['anschaffungsdatum']));
|
||||
$fy = FiscalYear::findOrCreateByYear($year);
|
||||
$fyId = $fy['id'];
|
||||
}
|
||||
|
||||
Database::execute(
|
||||
'INSERT INTO assets
|
||||
(lfd_nr, bezeichnung, anschaffungsdatum, anschaffungskosten,
|
||||
zugaenge, abgaenge, abschreibung_jahr, nutzungsdauer, notizen, fiscal_year_id)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)',
|
||||
[
|
||||
$data['lfd_nr'] ?? self::nextLfdNr(),
|
||||
$data['bezeichnung'],
|
||||
$data['anschaffungsdatum'],
|
||||
$data['anschaffungskosten'] ?? 0,
|
||||
$data['zugaenge'] ?? 0,
|
||||
$data['abgaenge'] ?? 0,
|
||||
$data['abschreibung_jahr'] ?? 0,
|
||||
isset($data['nutzungsdauer']) && $data['nutzungsdauer'] !== '' ? (int)$data['nutzungsdauer'] : null,
|
||||
$data['notizen'] ?? null,
|
||||
$fyId,
|
||||
]
|
||||
);
|
||||
return (int)Database::lastInsertId();
|
||||
}
|
||||
|
||||
public static function update(int $id, array $data): void
|
||||
{
|
||||
$fyId = null;
|
||||
if (!empty($data['anschaffungsdatum'])) {
|
||||
$year = (int)date('Y', strtotime($data['anschaffungsdatum']));
|
||||
$fy = FiscalYear::findOrCreateByYear($year);
|
||||
$fyId = $fy['id'];
|
||||
}
|
||||
|
||||
Database::execute(
|
||||
'UPDATE assets SET
|
||||
bezeichnung = ?,
|
||||
anschaffungsdatum = ?,
|
||||
anschaffungskosten = ?,
|
||||
zugaenge = ?,
|
||||
abgaenge = ?,
|
||||
abschreibung_jahr = ?,
|
||||
nutzungsdauer = ?,
|
||||
notizen = ?,
|
||||
fiscal_year_id = COALESCE(?, fiscal_year_id),
|
||||
updated_at = NOW()
|
||||
WHERE id = ?',
|
||||
[
|
||||
$data['bezeichnung'],
|
||||
$data['anschaffungsdatum'],
|
||||
$data['anschaffungskosten'] ?? 0,
|
||||
$data['zugaenge'] ?? 0,
|
||||
$data['abgaenge'] ?? 0,
|
||||
$data['abschreibung_jahr'] ?? 0,
|
||||
isset($data['nutzungsdauer']) && $data['nutzungsdauer'] !== '' ? (int)$data['nutzungsdauer'] : null,
|
||||
$data['notizen'] ?? null,
|
||||
$fyId,
|
||||
$id,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
public static function delete(int $id): bool
|
||||
{
|
||||
$asset = self::find($id);
|
||||
if (!$asset) return false;
|
||||
|
||||
Database::execute('DELETE FROM assets WHERE id = ?', [$id]);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
81
src/Views/assets/form.php
Normal file
81
src/Views/assets/form.php
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
<?php use Steuerkram\View; $h = fn($v) => View::h($v); ?>
|
||||
|
||||
<div class="card" style="max-width:600px">
|
||||
<div class="card-body">
|
||||
<form method="post" action="<?= $h($action) ?>">
|
||||
|
||||
<div class="form-group">
|
||||
<label for="bezeichnung">Bezeichnung des Wirtschaftsguts *</label>
|
||||
<input type="text" id="bezeichnung" name="bezeichnung"
|
||||
value="<?= $h($asset['bezeichnung'] ?? '') ?>"
|
||||
class="<?= isset($errors['bezeichnung']) ? 'input-error' : '' ?>"
|
||||
required autofocus>
|
||||
<?php if (isset($errors['bezeichnung'])): ?>
|
||||
<div class="error-text"><?= $h($errors['bezeichnung']) ?></div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="anschaffungsdatum">Anschaffungsdatum *</label>
|
||||
<input type="date" id="anschaffungsdatum" name="anschaffungsdatum"
|
||||
value="<?= $h($asset['anschaffungsdatum'] ?? '') ?>"
|
||||
class="<?= isset($errors['anschaffungsdatum']) ? 'input-error' : '' ?>"
|
||||
required>
|
||||
<?php if (isset($errors['anschaffungsdatum'])): ?>
|
||||
<div class="error-text"><?= $h($errors['anschaffungsdatum']) ?></div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="anschaffungskosten">Anschaffungskosten (€) *</label>
|
||||
<input type="number" id="anschaffungskosten" name="anschaffungskosten"
|
||||
value="<?= $h($asset['anschaffungskosten'] ?? '0') ?>"
|
||||
class="<?= isset($errors['anschaffungskosten']) ? 'input-error' : '' ?>"
|
||||
min="0" step="0.01" required>
|
||||
<?php if (isset($errors['anschaffungskosten'])): ?>
|
||||
<div class="error-text"><?= $h($errors['anschaffungskosten']) ?></div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<div style="display:grid;grid-template-columns:1fr 1fr;gap:1rem">
|
||||
<div class="form-group">
|
||||
<label for="zugaenge">Zugänge (€)</label>
|
||||
<input type="number" id="zugaenge" name="zugaenge"
|
||||
value="<?= $h($asset['zugaenge'] ?? '0') ?>"
|
||||
min="0" step="0.01">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="abgaenge">Abgänge (€)</label>
|
||||
<input type="number" id="abgaenge" name="abgaenge"
|
||||
value="<?= $h($asset['abgaenge'] ?? '0') ?>"
|
||||
min="0" step="0.01">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="display:grid;grid-template-columns:1fr 1fr;gap:1rem">
|
||||
<div class="form-group">
|
||||
<label for="abschreibung_jahr">Abschreibung lfd. Jahr (€)</label>
|
||||
<input type="number" id="abschreibung_jahr" name="abschreibung_jahr"
|
||||
value="<?= $h($asset['abschreibung_jahr'] ?? '0') ?>"
|
||||
min="0" step="0.01">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="nutzungsdauer">Nutzungsdauer (Jahre)</label>
|
||||
<input type="number" id="nutzungsdauer" name="nutzungsdauer"
|
||||
value="<?= $h($asset['nutzungsdauer'] ?? '') ?>"
|
||||
min="1" step="1" placeholder="optional">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="notizen">Notizen</label>
|
||||
<textarea id="notizen" name="notizen" rows="3"><?= $h($asset['notizen'] ?? '') ?></textarea>
|
||||
</div>
|
||||
|
||||
<div style="display:flex;gap:.75rem;margin-top:1rem">
|
||||
<button type="submit" class="btn btn-primary">Speichern</button>
|
||||
<a href="/assets" class="btn btn-secondary">Abbrechen</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
57
src/Views/assets/list.php
Normal file
57
src/Views/assets/list.php
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
<?php use Steuerkram\View; $h = fn($v) => View::h($v); ?>
|
||||
|
||||
<div class="card">
|
||||
<div class="table-wrap">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width:4rem">Nr.</th>
|
||||
<th>Anschaffungsdatum</th>
|
||||
<th>Wirtschaftsgut</th>
|
||||
<th class="td-num">Anschaffungskosten</th>
|
||||
<th class="td-num">Zugänge</th>
|
||||
<th class="td-num">Abgänge</th>
|
||||
<th class="td-num">Abschreibungen</th>
|
||||
<th style="width:6rem"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($assets as $a): ?>
|
||||
<tr>
|
||||
<td class="td-mono text-sm"><?= $h($a['lfd_nr']) ?></td>
|
||||
<td class="td-muted text-sm"><?= View::formatDate($a['anschaffungsdatum']) ?></td>
|
||||
<td><?= $h($a['bezeichnung']) ?></td>
|
||||
<td class="td-num"><?= number_format((float)$a['anschaffungskosten'], 2, ',', '.') ?> €</td>
|
||||
<td class="td-num"><?= number_format((float)$a['zugaenge'], 2, ',', '.') ?> €</td>
|
||||
<td class="td-num"><?= number_format((float)$a['abgaenge'], 2, ',', '.') ?> €</td>
|
||||
<td class="td-num"><?= number_format((float)$a['abschreibung_jahr'], 2, ',', '.') ?> €</td>
|
||||
<td>
|
||||
<div style="display:flex;gap:.4rem;justify-content:flex-end">
|
||||
<a href="/assets/<?= $h($a['id']) ?>/edit"
|
||||
class="btn btn-secondary btn-sm"
|
||||
title="Bearbeiten">
|
||||
<!-- Bleistift -->
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" width="14" height="14"><path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/><path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"/></svg>
|
||||
</a>
|
||||
<form method="post" action="/assets/<?= $h($a['id']) ?>/delete" style="margin:0"
|
||||
data-confirm="Wirtschaftsgut "<?= $h($a['bezeichnung']) ?>" wirklich löschen?">
|
||||
<button type="submit" class="btn btn-danger btn-sm" title="Löschen">
|
||||
<!-- Papierkorb -->
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" width="14" height="14"><polyline points="3,6 5,6 21,6"/><path d="M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6"/><path d="M10 11v6M14 11v6"/><path d="M9 6V4a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1v2"/></svg>
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
<?php if (empty($assets)): ?>
|
||||
<tr>
|
||||
<td colspan="8" class="text-center text-muted" style="padding:2rem">
|
||||
Noch keine Wirtschaftsgüter angelegt.
|
||||
</td>
|
||||
</tr>
|
||||
<?php endif; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -49,6 +49,16 @@ function navActive(string $prefix, string $current): string {
|
|||
</nav>
|
||||
</div>
|
||||
|
||||
<div class="sidebar-section">
|
||||
<div class="sidebar-label">Betriebsvermögen</div>
|
||||
<nav>
|
||||
<a href="/assets" class="<?= navActive('/assets', $path) ?>">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="2" y="7" width="20" height="14" rx="2"/><path d="M16 7V5a2 2 0 0 0-2-2h-4a2 2 0 0 0-2 2v2"/><line x1="12" y1="12" x2="12" y2="16"/><line x1="10" y1="14" x2="14" y2="14"/></svg>
|
||||
Anlagevermögen
|
||||
</a>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<div class="sidebar-section">
|
||||
<div class="sidebar-label">Steuerauswertungen</div>
|
||||
<nav>
|
||||
|
|
|
|||
67
tests/20_AnlagevermoegensTest.php
Normal file
67
tests/20_AnlagevermoegensTest.php
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use Steuerkram\Models\Asset;
|
||||
|
||||
/**
|
||||
* Test 20: Anlagevermögen – CRUD und Listenfunktion.
|
||||
*/
|
||||
class AnlagevermoegensTest extends TestCase
|
||||
{
|
||||
protected string $testName = 'Test 20: Anlagevermögen (Assets)';
|
||||
|
||||
public function run(): void
|
||||
{
|
||||
// ── Anlegen ───────────────────────────────────────────────────
|
||||
$id = Asset::create([
|
||||
'bezeichnung' => 'Test-Laptop',
|
||||
'anschaffungsdatum' => '2026-01-15',
|
||||
'anschaffungskosten' => 1500.00,
|
||||
'zugaenge' => 0,
|
||||
'abgaenge' => 0,
|
||||
'abschreibung_jahr' => 300.00,
|
||||
'nutzungsdauer' => 5,
|
||||
'notizen' => 'Testnotiz',
|
||||
]);
|
||||
$this->assertGreaterThan(0, $id, 'Asset wurde angelegt (ID > 0)');
|
||||
|
||||
// ── find() ────────────────────────────────────────────────────
|
||||
$asset = Asset::find($id);
|
||||
$this->assertNotEmpty($asset, 'Asset::find() liefert einen Datensatz');
|
||||
$this->assertEquals('Test-Laptop', $asset['bezeichnung'], 'Bezeichnung korrekt');
|
||||
$this->assertEquals('2026-01-15', $asset['anschaffungsdatum'], 'Anschaffungsdatum korrekt');
|
||||
$this->assertEquals(1500.00, (float)$asset['anschaffungskosten'], 'Anschaffungskosten korrekt');
|
||||
$this->assertEquals(300.00, (float)$asset['abschreibung_jahr'], 'Abschreibung korrekt');
|
||||
$this->assertEquals(5, (int)$asset['nutzungsdauer'], 'Nutzungsdauer korrekt');
|
||||
|
||||
// ── Lfd. Nr. wird automatisch vergeben ───────────────────────
|
||||
$this->assertGreaterThan(0, (int)$asset['lfd_nr'], 'Lfd. Nr. > 0');
|
||||
|
||||
// ── all() enthält das neue Asset ─────────────────────────────
|
||||
$all = Asset::all();
|
||||
$found = array_filter($all, fn($a) => (int)$a['id'] === $id);
|
||||
$this->assert(!empty($found), 'Asset erscheint in Asset::all()');
|
||||
|
||||
// ── update() ──────────────────────────────────────────────────
|
||||
Asset::update($id, [
|
||||
'bezeichnung' => 'Test-Laptop (aktualisiert)',
|
||||
'anschaffungsdatum' => '2026-01-15',
|
||||
'anschaffungskosten' => 1500.00,
|
||||
'zugaenge' => 200.00,
|
||||
'abgaenge' => 0,
|
||||
'abschreibung_jahr' => 340.00,
|
||||
'nutzungsdauer' => 5,
|
||||
'notizen' => '',
|
||||
]);
|
||||
$updated = Asset::find($id);
|
||||
$this->assertEquals('Test-Laptop (aktualisiert)', $updated['bezeichnung'], 'Bezeichnung nach Update korrekt');
|
||||
$this->assertEquals(200.00, (float)$updated['zugaenge'], 'Zugänge nach Update korrekt');
|
||||
$this->assertEquals(340.00, (float)$updated['abschreibung_jahr'], 'Abschreibung nach Update korrekt');
|
||||
|
||||
// ── delete() ──────────────────────────────────────────────────
|
||||
$deleted = Asset::delete($id);
|
||||
$this->assert($deleted, 'Asset::delete() gibt true zurück');
|
||||
$this->assert(Asset::find($id) === false, 'Gelöschtes Asset nicht mehr auffindbar');
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue