Der Plan ging davon aus, Umbuchungen stünden zweimal im Export und müssten zusammengeführt werden. Der Vollexport zeigt das Gegenteil: Eine Umbuchung steht einmal, in dem Konto, in dem sie erfasst wurde; die Gegenseite ist nur durch L[Zielkonto] benannt und muss beim Import erzeugt werden. Von 965 Umbuchungen hat keine ein zweites Vorkommen. Mit erzeugter Gegenseite stimmen acht gegen den FinanzManager geprüfte Kontosalden auf den Cent. Datenmodell: Migration 003 mit buchungen, buchung_splits und buchungsadressen. Eine Umbuchung ist ein Datensatz mit gegenkonto_id, kein gespiegeltes Paar - zwei Zeilen laufen früher oder später auseinander. Das Kontoblatt des Gegenkontos zeigt sie mit umgekehrtem Vorzeichen. Auch einzelne Splitzeilen dürfen ein Gegenkonto tragen. Konten führen zusätzlich in_summe, die Sigma-Spalte der Kontenübersicht, und saldo_online für Phase 5. Oberfläche: Kontoblatt mit laufendem Saldo, Filter auf Zeitraum, Kategorie, Klasse und Abgleich, Volltextsuche und Seitenblättern. Buchungsmaske für Einnahme, Ausgabe und Umbuchung in beide Richtungen, mit Splitzeilen und Restbetragsanzeige. Import: QifBuchungsImport arbeitet mit Stichtag. Alles davor wird je Konto zum Anfangssaldo aufsummiert, Gegenbuchungen eingerechnet; alles danach bis zum Importtag kommt einzeln in die Datenbank. Spätere Buchungen sind Vorausberechnungen des FinanzManagers und bleiben draußen. Wertpapiergeschäfte schlagen vorerst nur mit ihrem Geldfluss auf dem Verrechnungskonto zu Buche. Am Vollexport geprüft: 833 Buchungen ab 01.01.2026, 7946 in die Anfangssalden von 35 Konten verrechnet. Behoben: Die confirm()-Rückfragen vor dem Löschen waren wirkungslos. public/static/app.js brachte den Mechanismus mit, war aber nie im Layout eingebunden; die ersatzweise gesetzten onsubmit-Attribute verstoßen gegen die eigene Content-Security-Policy und wurden vom Browser verworfen - gelöscht wurde ohne Rückfrage. Ein E2E-Test besteht jetzt auf dem Dialog. Tests: 307 PHP-Prüfungen und 21 Playwright-Tests, alle grün. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
228 lines
10 KiB
PHP
228 lines
10 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 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; ?>
|
||
</tbody>
|
||
</table>
|
||
|
||
<?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 (nur Geldfluss)</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 (!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/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'; ?>
|