Finanzmanager/src/Views/verwaltung/import.php
fds2610-bot 3e55b3ba73 QIF-Import: nur ausgewählte Konten übernehmen
Der Import kannte bisher nur den Zeitraum als Filter – ein Export ließ sich
nur ganz einlesen. Neu ist die Option "konten": im Dialog eine Ankreuzliste,
die erst nach dem Trockenlauf erscheint (vorher ist die Datei ungelesen), auf
der Kommandozeile --konto=NAME und --konten.

Maßgeblich ist nicht allein der !Account-Abschnitt, sondern jedes Konto, das
der Eintrag berührt: das eigene, die Umbuchungsziele aus L und den Splitzeilen
und beim Wertpapiergeschäft das Verrechnungskonto. Eine Umbuchung steht nur
einmal in der Datei – sonst fehlte dem gewählten Konto die Gegenseite.

Die Anfangssalden werden weiter über die ganze Datei gerechnet, aber nur für
die gewählten Konten geschrieben; ein Teilimport überschreibt sonst fremde
Salden. Die Fingerabdrücke entstehen jetzt vorab in einem eigenen Durchgang:
Der laufende Zähler für gleiche Fingerabdrücke hing sonst an der Auswahl, und
dieselbe Buchung wäre beim zweiten Lauf mit anderer Auswahl doppelt angelegt
worden.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-12 14:50:43 +02:00

316 lines
15 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?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 array<string,int> $dateikonten */
/** @var list<string> $auswahl */
/** @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>
<?php if ((int)($buchungen['buchungen']['nicht_gewaehlt'] ?? 0) > 0): ?>
<tr>
<td class="nebentext">gehört zu keinem der gewählten Konten</td>
<td class="rechts nebentext"><?= (int)$buchungen['buchungen']['nicht_gewaehlt'] ?></td>
</tr>
<?php endif; ?>
<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.
<?php if (($buchungen['konten'] ?? null) !== null): ?>
Übernommen wurden nur die Buchungen von
<strong><?= View::h(implode(', ', $buchungen['konten'])) ?></strong> – einschließlich
der Umbuchungen, die auf einem anderen Konto erfasst wurden und hierher zeigen.
Die Anfangssalden der übrigen Konten bleiben unangetastet.
<?php endif; ?>
</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="formular">
<input type="hidden" name="csrf_token" value="<?= View::h($csrfToken) ?>">
<input type="hidden" name="datei" value="<?= View::h($datei) ?>">
<input type="hidden" name="stichtag" value="<?= View::h($stichtag) ?>">
<input type="hidden" name="bis" value="<?= View::h($bis) ?>">
<?php if ($dateikonten !== []): ?>
<h3>Konten</h3>
<p class="feldhinweis">
Zur Wahl stehen die Konten, die in dieser Datei vorkommen. Ohne Häkchen
zählt die ganze Datei. Eine Umbuchung kommt mit, sobald eines der beiden
Konten gewählt ist – sie steht im Export nur einmal, beim erfassenden
Konto. Die Zahl nennt die Einträge, die das Konto berühren.
</p>
<div class="auswahlliste">
<?php foreach ($dateikonten as $name => $anzahl): ?>
<label class="ankreuzfeld">
<input type="checkbox" name="konten[]" value="<?= View::h((string)$name) ?>"
<?= $auswahl === [] || in_array((string)$name, $auswahl, true) ? 'checked' : '' ?>>
<span>
<?= View::h((string)$name) ?>
<span class="nebentext"><?= (int)$anzahl ?></span>
</span>
</label>
<?php endforeach; ?>
</div>
<?php endif; ?>
<div class="knopfleiste">
<button type="submit" name="modus" value="import" class="knopf primaer">Jetzt übernehmen</button>
<?php if ($dateikonten !== []): ?>
<button type="submit" name="modus" value="vorschau" class="knopf">
Trockenlauf mit dieser Auswahl
</button>
<?php endif; ?>
</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>
<p class="hinweis">
Soll nur ein Teil der Konten übernommen werden, zuerst den <strong>Trockenlauf</strong>
starten: danach steht fest, welche Konten die Datei führt, und sie stehen einzeln zur
Wahl. <strong>Importieren</strong> übernimmt die ganze Datei.
</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'; ?>