Kontenzuordnung als dritter Einrichtungsschritt: Welches Bankkonto gehört zu welchem eigenen Konto? Umsätze merken sich über `bank_konto_id`, woher sie stammen, und ziehen mit, wenn die Zuordnung erst nach einem Abruf fällt. Abgerufene Umsätze buchen sich auf Wunsch selbst (`Services\Bankabgleich`). Im Kontoblatt lassen sich Buchungen sammelweise löschen; Statusmeldungen liegen jetzt über der Ansicht, statt den Inhalt springen zu lassen. Dazu drei Fehler, die zusammen dafür sorgten, dass abgerufene Umsätze spurlos verschwanden – der Abruf meldete sie als vorhanden, das Kontoblatt zeigte nichts, und in der Vorschlagsliste standen sie ebenfalls nicht: - Eine gelöschte Buchung ließ ihren Umsatz auf „uebernommen“ stehen. Der Fremdschlüssel setzte nur `buchung_id` auf NULL. `Buchung::loeschen()` gibt den Umsatz jetzt vorher frei, `Bankumsatz::verwaisteOeffnen()` fängt die Wege ab, die daran vorbeiführen – Kaskaden lösen in MariaDB keine Trigger aus. - Ein Bankkonto entstand doppelt, wenn die Bank die IBAN nicht bei jedem Dialog mitliefert. Gesucht wird jetzt über die Kontonummer; eine nachgelieferte IBAN wird am vorhandenen Eintrag nachgetragen. - Der Fingerabdruck hing an der IBAN und kippte mit ihr. Er nimmt jetzt die `bank_konto_id`, die fest steht, solange es den Bankzugang gibt. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
75 lines
3.4 KiB
PHP
75 lines
3.4 KiB
PHP
<?php
|
||
|
||
use Finanzmanager\View;
|
||
|
||
/** @var string $titel */
|
||
/** @var array $flash */
|
||
/** @var string|null $aktuellerPfad */
|
||
/** @var array|null $benutzer */
|
||
|
||
$navigation = [
|
||
['pfad' => '/', 'name' => 'Cockpit', 'icon' => '⌂'],
|
||
['pfad' => '/konten', 'name' => 'Kontenübersicht', 'icon' => '▤'],
|
||
['pfad' => '/banking', 'name' => 'Online-Banking', 'icon' => '⇊'],
|
||
['pfad' => '/zahlungsverkehr', 'name' => 'Zahlungsverkehr', 'icon' => '⇄'],
|
||
['pfad' => '/auswertungen', 'name' => 'Auswertungen', 'icon' => '◱'],
|
||
['pfad' => '/planung', 'name' => 'Planung', 'icon' => '◷'],
|
||
['pfad' => '/termine', 'name' => 'Termine', 'icon' => '⏱'],
|
||
['pfad' => '/vertraege', 'name' => 'Verträge', 'icon' => '§'],
|
||
['pfad' => '/wertpapiere', 'name' => 'Wertpapiere', 'icon' => '↗'],
|
||
['pfad' => '/verwaltung', 'name' => 'Verwaltung', 'icon' => '⚙'],
|
||
];
|
||
|
||
$istAktiv = static function (string $pfad) use ($aktuellerPfad): bool {
|
||
if ($pfad === '/') {
|
||
return $aktuellerPfad === '/' || $aktuellerPfad === '';
|
||
}
|
||
return is_string($aktuellerPfad) && str_starts_with($aktuellerPfad, $pfad);
|
||
};
|
||
?>
|
||
<!DOCTYPE html>
|
||
<html lang="de"<?= ($thema = View::thema()) !== '' ? ' data-thema="' . View::h($thema) . '"' : '' ?>>
|
||
<head>
|
||
<meta charset="utf-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||
<title><?= View::h($titel ?? 'Finanzmanager') ?> – Finanzmanager</title>
|
||
<link rel="stylesheet" href="/static/app.css">
|
||
<!-- Nur hier eingebunden: Ein zweites Mal im Fuß ließe jeden Handler doppelt
|
||
laufen – Rückfragen kämen zweimal, Menüs entstünden zweimal. -->
|
||
<script src="/static/app.js" defer></script>
|
||
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'><text y='13' font-size='13'>€</text></svg>">
|
||
</head>
|
||
<body>
|
||
<?php include __DIR__ . '/meldungen.php'; ?>
|
||
<div class="rahmen">
|
||
<nav class="seitenleiste" aria-label="Hauptnavigation">
|
||
<div class="marke">Finanzmanager</div>
|
||
<ul>
|
||
<?php foreach ($navigation as $eintrag): ?>
|
||
<li>
|
||
<a href="<?= View::h($eintrag['pfad']) ?>"
|
||
class="<?= $istAktiv($eintrag['pfad']) ? 'aktiv' : '' ?>">
|
||
<span class="icon" aria-hidden="true"><?= $eintrag['icon'] ?></span>
|
||
<?= View::h($eintrag['name']) ?>
|
||
</a>
|
||
</li>
|
||
<?php endforeach; ?>
|
||
</ul>
|
||
</nav>
|
||
|
||
<div class="hauptbereich">
|
||
<header class="werkzeugleiste">
|
||
<h1><?= View::h($titel ?? 'Finanzmanager') ?></h1>
|
||
<div class="werkzeuge">
|
||
<a href="/einstellungen" class="werkzeug" title="Einstellungen">⚙ Einstellungen</a>
|
||
<?php if (!empty($benutzer)): ?>
|
||
<span class="benutzer"><?= View::h($benutzer['anzeigename'] ?: $benutzer['benutzername']) ?></span>
|
||
<form method="post" action="/logout" class="inline">
|
||
<input type="hidden" name="csrf_token" value="<?= View::h($csrfToken ?? '') ?>">
|
||
<button type="submit" class="werkzeug">Abmelden</button>
|
||
</form>
|
||
<?php endif; ?>
|
||
</div>
|
||
</header>
|
||
|
||
<main class="inhalt">
|