Damit ist der geplante Funktionsumfang beisammen. Phase 4 abgeschlossen - Konfigurierbare Cockpit-Kacheln; abgewählte werden nicht berechnet - Einnahmen/Ausgaben je Monat als Balkenpaare mit Saldolinie, aufgesetzt auf den Summenbericht statt auf eine eigene Abfrage - Meine Ausgaben als Ring auf der obersten Kategorieebene - Offene Vorgänge aus allen Modulen Phase 5 – Online-Banking - nemiah/php-fints als Fork auf Codeberg, psr/log direkt eingebaut: die Anwendung hängt an keinem fremden Update-Zyklus - PIN AES-256-GCM-verschlüsselt; Schlüssel aus APP_KEY oder aus data/schluessel/app_key, den der Entrypoint einmalig erzeugt - TAN-Dialog über mehrere Requests (FinTs::persist plus serialisierte Aktion in der Sitzung) – ein wartender Prozess wäre mit php-fpm der falsche Weg - Abgleich statt Direktbuchung: verbinden, übernehmen oder verwerfen - Regeln mit vier Vergleichsarten, Betragsgrenzen und Split-Schema Phase 6 – Wertpapiere - Depotbuchung und Geldbuchung als zwei Seiten eines Geschäfts; betrag ist der Bruttowert, Nebenkosten stehen daneben - Einstand nach gleitendem Durchschnitt, realisierte Gewinne, Erträge - Kursabruf über Leeway oder FMP, täglicher Lauf per Cron - QIF-Import für !Type:Invst Dabei behoben: Der Import buchte den Geldfluss jedes Wertpapiergeschäfts als Abgang – auch bei Verkäufen und Dividenden; und er erkannte das Verrechnungskonto nicht, wenn dem Konto im L-Feld eine Kategorie vorangeht (bei Verkäufen also immer). Phase 7 – Nebenmodule und Zahlungsverkehr - Buchungsvorlagen inklusive QIF-Import, Splits werden mitskaliert - Regelmäßige Buchungen mit Fälligkeitslauf, Finanzkalender, Erinnerungen - Adressen mit IBAN-Prüfung, Verträge mit Kündigungsfrist, Sparziele - Kredite mit gerechnetem Tilgungsplan - Überweisungen über FinTS; die Buchung entsteht erst nach Annahme durch die Bank. Die pain.001-Nachricht erzeugt SepaXml selbst Nebenbei: .dockerignore ergänzt (ohne sie brach der Build an data/mariadb ab), PlatzhalterController entfernt, zwei Inline-Handler beseitigt, die die Content-Security-Policy ohnehin blockierte. 888 PHP-Prüfungen und 45 E2E-Tests grün. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
269 lines
12 KiB
PHP
269 lines
12 KiB
PHP
<?php
|
||
|
||
use Finanzmanager\View;
|
||
|
||
/** @var list<array{name: string, groesse: int, geaendert: int}> $dateien */
|
||
/** @var string $verzeichnis */
|
||
/** @var array<string,mixed>|null $ergebnis */
|
||
/** @var array<string,mixed>|null $buchungen */
|
||
/** @var array<string,mixed>|null $vorlagen */
|
||
/** @var string $stichtag */
|
||
/** @var string $bis */
|
||
/** @var string $datei */
|
||
/** @var string $csrfToken */
|
||
|
||
$bereiche = [
|
||
'klassen' => 'Klassen',
|
||
'kategorien' => 'Kategorien',
|
||
'konten' => 'Konten',
|
||
];
|
||
|
||
include __DIR__ . '/../layout/kopf.php';
|
||
?>
|
||
|
||
<div class="seitenaktionen">
|
||
<a href="/verwaltung" class="knopf">Zurück zur Verwaltung</a>
|
||
<a href="/verwaltung/kategorien" class="knopf">Kategorien</a>
|
||
<a href="/konten" class="knopf">Kontenübersicht</a>
|
||
</div>
|
||
|
||
<?php if ($ergebnis !== null): ?>
|
||
<div class="kachel">
|
||
<header>
|
||
<h2><?= $ergebnis['trockenlauf'] ? 'Trockenlauf' : 'Import abgeschlossen' ?></h2>
|
||
<span class="phasenmarke"><?= View::h($datei) ?></span>
|
||
</header>
|
||
|
||
<?php if ($ergebnis['trockenlauf']): ?>
|
||
<p class="hinweis">
|
||
Nichts wurde gespeichert. Die Zahlen zeigen, was ein Import anlegen würde.
|
||
</p>
|
||
<?php endif; ?>
|
||
|
||
<table class="liste">
|
||
<thead>
|
||
<tr>
|
||
<th>Bereich</th>
|
||
<th class="rechts"><?= $ergebnis['trockenlauf'] ? 'Neu' : 'Angelegt' ?></th>
|
||
<th class="rechts">Schon vorhanden</th>
|
||
<th class="rechts">Fehler</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<?php foreach ($bereiche as $schluessel => $beschriftung): ?>
|
||
<tr>
|
||
<td><?= View::h($beschriftung) ?></td>
|
||
<td class="rechts"><?= (int)$ergebnis[$schluessel]['neu'] ?></td>
|
||
<td class="rechts nebentext"><?= (int)$ergebnis[$schluessel]['vorhanden'] ?></td>
|
||
<td class="rechts <?= (int)$ergebnis[$schluessel]['fehler'] > 0 ? 'betrag negativ' : 'nebentext' ?>">
|
||
<?= (int)$ergebnis[$schluessel]['fehler'] ?>
|
||
</td>
|
||
</tr>
|
||
<?php endforeach; ?>
|
||
<tr>
|
||
<td>Kontengruppen</td>
|
||
<td class="rechts nebentext" colspan="3">
|
||
nicht im QIF-Export enthalten – <a href="/verwaltung/kontengruppen">hier zuordnen</a>
|
||
</td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
|
||
<p class="hinweis">
|
||
Der FinanzManager exportiert je Konto nur Name, Kontoart, Beschreibung und
|
||
Kreditlimit. Die Zuordnung zu einer Kontengruppe steht weder im QIF noch in der
|
||
<code>QSD</code>-Datei; sie wird deshalb einmalig in der Verwaltung nachgetragen.
|
||
</p>
|
||
|
||
<?php if ($buchungen !== null): ?>
|
||
<h3>Buchungen</h3>
|
||
<table class="liste">
|
||
<tbody>
|
||
<tr>
|
||
<td><?= $buchungen['trockenlauf'] ? 'Neu' : 'Angelegt' ?></td>
|
||
<td class="rechts"><?= (int)$buchungen['buchungen']['neu'] ?></td>
|
||
</tr>
|
||
<tr>
|
||
<td class="nebentext">schon vorhanden</td>
|
||
<td class="rechts nebentext"><?= (int)$buchungen['buchungen']['vorhanden'] ?></td>
|
||
</tr>
|
||
<tr>
|
||
<td class="nebentext">
|
||
vor dem Stichtag – in den Anfangssaldo von
|
||
<?= (int)$buchungen['anfangssalden']['konten'] ?> Konten verrechnet
|
||
</td>
|
||
<td class="rechts nebentext"><?= (int)$buchungen['buchungen']['vor_stichtag'] ?></td>
|
||
</tr>
|
||
<tr>
|
||
<td class="nebentext">nach dem Enddatum – vorausberechnet, verworfen</td>
|
||
<td class="rechts nebentext"><?= (int)$buchungen['buchungen']['zukunft'] ?></td>
|
||
</tr>
|
||
<tr>
|
||
<td class="nebentext">darunter Wertpapiergeschäfte (Depot- und Geldbuchung)</td>
|
||
<td class="rechts nebentext"><?= (int)$buchungen['buchungen']['wertpapier'] ?></td>
|
||
</tr>
|
||
<tr>
|
||
<td>Fehler</td>
|
||
<td class="rechts <?= (int)$buchungen['buchungen']['fehler'] > 0 ? 'betrag negativ' : 'nebentext' ?>">
|
||
<?= (int)$buchungen['buchungen']['fehler'] ?>
|
||
</td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
<p class="hinweis">
|
||
Zeitraum <?= View::datum($buchungen['stichtag']) ?> bis <?= View::datum($buchungen['bis']) ?>.
|
||
Alles davor ist nicht verloren: es steckt im Anfangssaldo der Konten.
|
||
</p>
|
||
<?php endif; ?>
|
||
|
||
<?php if (($vorlagen ?? null) !== null): ?>
|
||
<h3>Buchungsvorlagen</h3>
|
||
<table class="liste">
|
||
<tbody>
|
||
<tr>
|
||
<td><?= $buchungen !== null && $buchungen['trockenlauf'] ? 'Neu' : 'Angelegt' ?></td>
|
||
<td class="rechts"><?= (int)$vorlagen['neu'] ?></td>
|
||
</tr>
|
||
<tr>
|
||
<td class="nebentext">bereits vorhanden</td>
|
||
<td class="rechts nebentext"><?= (int)$vorlagen['vorhanden'] ?></td>
|
||
</tr>
|
||
<tr>
|
||
<td>Fehler</td>
|
||
<td class="rechts <?= (int)$vorlagen['fehler'] > 0 ? 'betrag negativ' : 'nebentext' ?>">
|
||
<?= (int)$vorlagen['fehler'] ?>
|
||
</td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
<p class="hinweis">
|
||
Der FinanzManager merkt sich jede erfasste Zahlung als Vorlage – im
|
||
Beispielexport sind das mehrere hundert. Sie stehen danach unter
|
||
<a href="/vorlagen">Buchungsvorlagen</a>.
|
||
</p>
|
||
<?php endif; ?>
|
||
|
||
<?php if (!empty($ergebnis['abschnitte'])): ?>
|
||
<h3>Inhalt der Datei</h3>
|
||
<p class="hinweis">
|
||
<?php $teile = [];
|
||
foreach ($ergebnis['abschnitte'] as $typ => $anzahl) {
|
||
$teile[] = View::h((string)$typ) . ': ' . (int)$anzahl;
|
||
}
|
||
echo implode(' · ', $teile); ?>
|
||
</p>
|
||
<p class="hinweis">
|
||
Buchungsvorlagen (<code>Memorized</code>) folgen in Phase 7, Wertpapierbestände
|
||
in Phase 6.
|
||
</p>
|
||
<?php endif; ?>
|
||
|
||
<?php if (!empty($ergebnis['meldungen'])): ?>
|
||
<h3>Protokoll</h3>
|
||
<ul class="protokoll">
|
||
<?php foreach ($ergebnis['meldungen'] as $meldung): ?>
|
||
<li class="<?= View::h($meldung['typ']) ?>"><?= View::h($meldung['text']) ?></li>
|
||
<?php endforeach; ?>
|
||
</ul>
|
||
<?php endif; ?>
|
||
|
||
<?php if ($ergebnis['trockenlauf']): ?>
|
||
<form method="post" action="/verwaltung/import/ausfuehren" class="inline">
|
||
<input type="hidden" name="csrf_token" value="<?= View::h($csrfToken) ?>">
|
||
<input type="hidden" name="datei" value="<?= View::h($datei) ?>">
|
||
<input type="hidden" name="modus" value="import">
|
||
<input type="hidden" name="stichtag" value="<?= View::h($stichtag) ?>">
|
||
<input type="hidden" name="bis" value="<?= View::h($bis) ?>">
|
||
<div class="knopfleiste">
|
||
<button type="submit" class="knopf primaer">Jetzt übernehmen</button>
|
||
</div>
|
||
</form>
|
||
<?php else: ?>
|
||
<div class="knopfleiste">
|
||
<a href="/konten" class="knopf primaer">Kontenübersicht</a>
|
||
<a href="/verwaltung/kontengruppen" class="knopf">Kontengruppen</a>
|
||
<a href="/verwaltung/kategorien" class="knopf">Kategorien</a>
|
||
</div>
|
||
<p class="hinweis">
|
||
Die Anfangssalden wurden aus dem Zeitraum vor dem Stichtag berechnet. In der
|
||
Kontenübersicht lässt sich jeder Wert nachträglich ändern – ebenso das
|
||
Kennzeichen, ob ein Konto zur Summe der Kontostände zählt.
|
||
</p>
|
||
<?php endif; ?>
|
||
</div>
|
||
<?php endif; ?>
|
||
|
||
<div class="kachel">
|
||
<header><h2>Exportdatei hochladen</h2></header>
|
||
<form method="post" action="/verwaltung/import/hochladen" enctype="multipart/form-data" class="formular">
|
||
<input type="hidden" name="csrf_token" value="<?= View::h($csrfToken) ?>">
|
||
<label for="qif">QIF-Datei</label>
|
||
<input type="file" id="qif" name="qif" accept=".qif,.QIF" required>
|
||
<p class="feldhinweis">
|
||
Die Datei wird unter <code><?= View::h($verzeichnis) ?></code> abgelegt und bleibt
|
||
dort – so lässt sich derselbe Export erst prüfen und dann übernehmen.
|
||
</p>
|
||
<div class="knopfleiste">
|
||
<button type="submit" class="knopf primaer">Hochladen</button>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
|
||
<div class="kachel">
|
||
<header><h2>Vorhandene Dateien</h2></header>
|
||
|
||
<p class="hinweis">
|
||
Übernommen werden Buchungen ab <strong><?= View::datum($stichtag) ?></strong> bis
|
||
<strong><?= View::datum($bis) ?></strong>. Alles davor fließt in den Anfangssaldo der
|
||
Konten, alles danach gilt als Vorausberechnung des FinanzManagers und bleibt draußen.
|
||
Der Stichtag steht in den <a href="/einstellungen?bereich=import">Einstellungen</a>.
|
||
</p>
|
||
|
||
<?php if ($dateien === []): ?>
|
||
<p class="platzhalter">
|
||
Noch keine Datei in <code><?= View::h($verzeichnis) ?></code>.
|
||
</p>
|
||
<?php else: ?>
|
||
<table class="liste">
|
||
<thead>
|
||
<tr><th>Datei</th><th class="rechts">Größe</th><th>Abgelegt</th><th class="rechts">Aktion</th></tr>
|
||
</thead>
|
||
<tbody>
|
||
<?php foreach ($dateien as $eintrag): ?>
|
||
<tr>
|
||
<td><code><?= View::h($eintrag['name']) ?></code></td>
|
||
<td class="rechts"><?= View::h(number_format($eintrag['groesse'] / 1024, 1, ',', '.')) ?> kB</td>
|
||
<td><?= View::h(date('d.m.Y H:i', $eintrag['geaendert'])) ?></td>
|
||
<td class="rechts nowrap">
|
||
<form method="post" action="/verwaltung/import/ausfuehren" class="inline">
|
||
<input type="hidden" name="csrf_token" value="<?= View::h($csrfToken) ?>">
|
||
<input type="hidden" name="datei" value="<?= View::h($eintrag['name']) ?>">
|
||
<input type="hidden" name="modus" value="vorschau">
|
||
<input type="hidden" name="stichtag" value="<?= View::h($stichtag) ?>">
|
||
<input type="hidden" name="bis" value="<?= View::h($bis) ?>">
|
||
<button type="submit" class="knopf klein">Trockenlauf</button>
|
||
</form>
|
||
<form method="post" action="/verwaltung/import/ausfuehren" class="inline"
|
||
data-bestaetigen="„<?= View::h($eintrag['name']) ?>“ jetzt importieren?">
|
||
<input type="hidden" name="csrf_token" value="<?= View::h($csrfToken) ?>">
|
||
<input type="hidden" name="datei" value="<?= View::h($eintrag['name']) ?>">
|
||
<input type="hidden" name="modus" value="import">
|
||
<input type="hidden" name="stichtag" value="<?= View::h($stichtag) ?>">
|
||
<input type="hidden" name="bis" value="<?= View::h($bis) ?>">
|
||
<button type="submit" class="knopf klein primaer">Importieren</button>
|
||
</form>
|
||
<form method="post" action="/verwaltung/import/<?= View::h(rawurlencode($eintrag['name'])) ?>/loeschen"
|
||
class="inline"
|
||
data-bestaetigen="Datei „<?= View::h($eintrag['name']) ?>“ löschen?">
|
||
<input type="hidden" name="csrf_token" value="<?= View::h($csrfToken) ?>">
|
||
<button type="submit" class="knopf klein gefahr">Löschen</button>
|
||
</form>
|
||
</td>
|
||
</tr>
|
||
<?php endforeach; ?>
|
||
</tbody>
|
||
</table>
|
||
<?php endif; ?>
|
||
</div>
|
||
|
||
<?php include __DIR__ . '/../layout/fuss.php'; ?>
|