Feature: Konto-Journal im Kontenrahmen
This commit is contained in:
parent
55c02b54de
commit
61bce80012
4 changed files with 180 additions and 0 deletions
|
|
@ -44,6 +44,7 @@ $router->get('/dashboard', fn($p) => (new AccountController())->dashboard($p));
|
||||||
$router->get('/accounts', fn($p) => (new AccountController())->index($p));
|
$router->get('/accounts', fn($p) => (new AccountController())->index($p));
|
||||||
$router->get('/accounts/new', fn($p) => (new AccountController())->create($p));
|
$router->get('/accounts/new', fn($p) => (new AccountController())->create($p));
|
||||||
$router->post('/accounts', fn($p) => (new AccountController())->store($p));
|
$router->post('/accounts', fn($p) => (new AccountController())->store($p));
|
||||||
|
$router->get('/accounts/{id}/journal', fn($p) => (new AccountController())->journal($p));
|
||||||
$router->get('/accounts/{id}/edit', fn($p) => (new AccountController())->edit($p));
|
$router->get('/accounts/{id}/edit', fn($p) => (new AccountController())->edit($p));
|
||||||
$router->post('/accounts/{id}', fn($p) => (new AccountController())->update($p));
|
$router->post('/accounts/{id}', fn($p) => (new AccountController())->update($p));
|
||||||
$router->post('/accounts/{id}/delete', fn($p) => (new AccountController())->destroy($p));
|
$router->post('/accounts/{id}/delete', fn($p) => (new AccountController())->destroy($p));
|
||||||
|
|
|
||||||
|
|
@ -198,6 +198,60 @@ class AccountController
|
||||||
View::redirect($back, 'Konto gelöscht.');
|
View::redirect($back, 'Konto gelöscht.');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function journal(array $params): void
|
||||||
|
{
|
||||||
|
$id = (int)$params['id'];
|
||||||
|
$account = Account::find($id);
|
||||||
|
if (!$account) { http_response_code(404); return; }
|
||||||
|
|
||||||
|
$availableYears = \Steuerkram\Database::fetchAll(
|
||||||
|
'SELECT year FROM fiscal_years ORDER BY year DESC'
|
||||||
|
);
|
||||||
|
$years = array_column($availableYears, 'year');
|
||||||
|
if (empty($years)) {
|
||||||
|
$years = [(int)date('Y')];
|
||||||
|
}
|
||||||
|
|
||||||
|
$year = isset($_GET['year']) ? (int)$_GET['year'] : (int)date('Y');
|
||||||
|
if (!in_array($year, $years, true)) {
|
||||||
|
$year = $years[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Buchungen wo dieses Konto Kategorie-Konto ist (alle, auch Kindzeilen)
|
||||||
|
// ODER Zahlungs-Konto ist (nur Hauptbuchungen, Kindzeilen gehören nicht ans Zahlungskonto)
|
||||||
|
$bookings = \Steuerkram\Database::fetchAll(
|
||||||
|
'SELECT bk.*,
|
||||||
|
k.kontonummer AS kategorie_nr, k.bezeichnung AS kategorie_name,
|
||||||
|
z.kontonummer AS zahlungs_nr, z.bezeichnung AS zahlungs_name,
|
||||||
|
bl.beleg_nr
|
||||||
|
FROM bookings bk
|
||||||
|
JOIN accounts k ON bk.kategorie_konto_id = k.id
|
||||||
|
JOIN accounts z ON bk.zahlungskonto_id = z.id
|
||||||
|
LEFT JOIN belege bl ON bk.beleg_id = bl.id
|
||||||
|
JOIN fiscal_years fy ON bk.fiscal_year_id = fy.id
|
||||||
|
WHERE fy.year = ?
|
||||||
|
AND (
|
||||||
|
bk.kategorie_konto_id = ?
|
||||||
|
OR (bk.zahlungskonto_id = ? AND bk.parent_booking_id IS NULL
|
||||||
|
AND bk.kategorie_konto_id != ?)
|
||||||
|
)
|
||||||
|
ORDER BY bk.buchungs_datum, bk.id',
|
||||||
|
[$year, $id, $id, $id]
|
||||||
|
);
|
||||||
|
|
||||||
|
$view = new View();
|
||||||
|
$view->render('layout/base.php', [
|
||||||
|
'pageTitle' => 'Konto ' . $account['kontonummer'] . ' – Journal',
|
||||||
|
'content' => $this->renderView('accounts/journal', [
|
||||||
|
'account' => $account,
|
||||||
|
'bookings' => $bookings,
|
||||||
|
'year' => $year,
|
||||||
|
'years' => $years,
|
||||||
|
'accountId' => $id,
|
||||||
|
]),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
public function search(array $params): void
|
public function search(array $params): void
|
||||||
{
|
{
|
||||||
$q = trim($_GET['q'] ?? '');
|
$q = trim($_GET['q'] ?? '');
|
||||||
|
|
|
||||||
124
src/Views/accounts/journal.php
Normal file
124
src/Views/accounts/journal.php
Normal file
|
|
@ -0,0 +1,124 @@
|
||||||
|
<?php
|
||||||
|
use Steuerkram\View;
|
||||||
|
$h = fn($v) => View::h($v);
|
||||||
|
$fmt = fn($v) => number_format(abs((float)$v), 2, ',', '.');
|
||||||
|
|
||||||
|
// Jahr-Navigation
|
||||||
|
$prevYear = $year - 1;
|
||||||
|
$nextYear = $year + 1;
|
||||||
|
$hasPrev = in_array($prevYear, $years, true);
|
||||||
|
$hasNext = in_array($nextYear, $years, true);
|
||||||
|
?>
|
||||||
|
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header">
|
||||||
|
<div>
|
||||||
|
<div class="card-title">
|
||||||
|
<?= $h($account['kontonummer']) ?> – <?= $h($account['bezeichnung']) ?>
|
||||||
|
</div>
|
||||||
|
<div class="text-muted text-sm"><?= $h($account['kontenart']) ?></div>
|
||||||
|
</div>
|
||||||
|
<div style="display:flex;gap:.5rem;align-items:center">
|
||||||
|
<?php if ($hasPrev): ?>
|
||||||
|
<a href="/accounts/<?= $accountId ?>/journal?year=<?= $prevYear ?>" class="btn btn-secondary btn-sm">← <?= $prevYear ?></a>
|
||||||
|
<?php else: ?>
|
||||||
|
<button class="btn btn-secondary btn-sm" disabled>←</button>
|
||||||
|
<?php endif; ?>
|
||||||
|
<span class="font-bold" style="min-width:4rem;text-align:center"><?= $year ?></span>
|
||||||
|
<?php if ($hasNext): ?>
|
||||||
|
<a href="/accounts/<?= $accountId ?>/journal?year=<?= $nextYear ?>" class="btn btn-secondary btn-sm"><?= $nextYear ?> →</a>
|
||||||
|
<?php else: ?>
|
||||||
|
<button class="btn btn-secondary btn-sm" disabled>→</button>
|
||||||
|
<?php endif; ?>
|
||||||
|
<a href="/accounts" class="btn btn-secondary btn-sm">← Kontenrahmen</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php if (empty($bookings)): ?>
|
||||||
|
<div class="card-body">
|
||||||
|
<p class="text-muted">Keine Buchungen für dieses Konto in <?= $year ?>.</p>
|
||||||
|
</div>
|
||||||
|
<?php else: ?>
|
||||||
|
<?php
|
||||||
|
$saldo = 0.0;
|
||||||
|
foreach ($bookings as $b) {
|
||||||
|
$isKategorie = (int)$b['kategorie_konto_id'] === $accountId;
|
||||||
|
if ($isKategorie) {
|
||||||
|
$wert = (float)$b['betrag_netto'];
|
||||||
|
} else {
|
||||||
|
// Zahlungs-Seite: Brutto-Betrag, Ausgaben fließen ab
|
||||||
|
$wert = (float)$b['betrag_brutto'];
|
||||||
|
if ($b['buchungstyp'] === 'Ausgabe') {
|
||||||
|
$wert = -$wert;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$saldo += $wert;
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
<div class="table-wrap">
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Datum</th>
|
||||||
|
<th>Nummer</th>
|
||||||
|
<th>Beleg</th>
|
||||||
|
<th>Typ</th>
|
||||||
|
<th>Gegenkonto</th>
|
||||||
|
<th>Text</th>
|
||||||
|
<th class="td-right">Wert</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<?php
|
||||||
|
$laufendSaldo = 0.0;
|
||||||
|
foreach ($bookings as $b):
|
||||||
|
$isKategorie = (int)$b['kategorie_konto_id'] === $accountId;
|
||||||
|
|
||||||
|
if ($isKategorie) {
|
||||||
|
$wert = (float)$b['betrag_netto'];
|
||||||
|
$gegenkonto = $b['zahlungs_nr'] . ' ' . $b['zahlungs_name'];
|
||||||
|
} else {
|
||||||
|
$wert = (float)$b['betrag_brutto'];
|
||||||
|
if ($b['buchungstyp'] === 'Ausgabe') {
|
||||||
|
$wert = -$wert;
|
||||||
|
}
|
||||||
|
$gegenkonto = $b['kategorie_nr'] . ' ' . $b['kategorie_name'];
|
||||||
|
}
|
||||||
|
$laufendSaldo += $wert;
|
||||||
|
$isReversal = (bool)$b['is_reversal'];
|
||||||
|
?>
|
||||||
|
<tr<?= $isReversal ? ' style="font-style:italic;color:#888"' : '' ?>>
|
||||||
|
<td class="td-mono text-sm"><?= $h($b['buchungs_datum']) ?></td>
|
||||||
|
<td class="td-mono text-sm"><?= $h($b['buchungs_nr']) ?></td>
|
||||||
|
<td>
|
||||||
|
<?php if ($b['beleg_id']): ?>
|
||||||
|
<a href="/belege/<?= (int)$b['beleg_id'] ?>"><?= $h($b['beleg_nr'] ?? '#' . $b['beleg_id']) ?></a>
|
||||||
|
<?php else: ?>
|
||||||
|
<span class="text-muted">–</span>
|
||||||
|
<?php endif; ?>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<span class="badge <?= $b['buchungstyp'] === 'Einnahme' ? 'badge-green' : 'badge-red' ?>">
|
||||||
|
<?= $h($b['buchungstyp']) ?>
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
<td class="td-mono text-sm"><?= $h($gegenkonto) ?></td>
|
||||||
|
<td><?= $h($b['buchungstext']) ?></td>
|
||||||
|
<td class="td-right td-mono<?= $wert < 0 ? ' text-danger' : '' ?>">
|
||||||
|
<?= $wert < 0 ? '−' : '' ?><?= $fmt($wert) ?> €
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</tbody>
|
||||||
|
<tfoot>
|
||||||
|
<tr style="font-weight:700;border-top:2px solid var(--border-color,#e5e7eb)">
|
||||||
|
<td colspan="6">Kontostand <?= $year ?> (<?= count($bookings) ?> Buchungen)</td>
|
||||||
|
<td class="td-right td-mono<?= $saldo < 0 ? ' text-danger' : '' ?>">
|
||||||
|
<?= $saldo < 0 ? '−' : '' ?><?= $fmt($saldo) ?> €
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tfoot>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
|
@ -61,6 +61,7 @@
|
||||||
<td class="td-muted text-sm"><?= $h(mb_strimwidth($acc['beschreibung'] ?? '', 0, 60, '…')) ?></td>
|
<td class="td-muted text-sm"><?= $h(mb_strimwidth($acc['beschreibung'] ?? '', 0, 60, '…')) ?></td>
|
||||||
<td>
|
<td>
|
||||||
<div style="display:flex;gap:.5rem">
|
<div style="display:flex;gap:.5rem">
|
||||||
|
<a href="/accounts/<?= $h($acc['id']) ?>/journal" class="btn btn-secondary btn-sm">Journal</a>
|
||||||
<a href="/accounts/<?= $h($acc['id']) ?>/edit" class="btn btn-secondary btn-sm">Bearbeiten</a>
|
<a href="/accounts/<?= $h($acc['id']) ?>/edit" class="btn btn-secondary btn-sm">Bearbeiten</a>
|
||||||
<form method="post" action="/accounts/<?= $h($acc['id']) ?>/delete"
|
<form method="post" action="/accounts/<?= $h($acc['id']) ?>/delete"
|
||||||
data-confirm="Konto '<?= $h($acc['kontonummer']) ?>' wirklich löschen?">
|
data-confirm="Konto '<?= $h($acc['kontonummer']) ?>' wirklich löschen?">
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue