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>
202 lines
6.9 KiB
PHP
202 lines
6.9 KiB
PHP
#!/usr/bin/env php
|
||
<?php
|
||
|
||
declare(strict_types=1);
|
||
|
||
/**
|
||
* QIF-Import auf der Kommandozeile.
|
||
*
|
||
* Dieselbe Logik wie im Dialog unter Verwaltung → QIF-Import, aber ohne Upload –
|
||
* praktisch für den großen Vollexport und für die Erstbefüllung.
|
||
*
|
||
* docker compose exec -u www-data php php scripts/qif-import.php data/import/Export.QIF
|
||
* docker compose exec -u www-data php php scripts/qif-import.php --uebernehmen data/import/Export.QIF
|
||
*
|
||
* Ohne --uebernehmen läuft nur der Trockenlauf: es wird nichts gespeichert.
|
||
*
|
||
* Weitere Optionen:
|
||
* --nur-stammdaten Klassen, Kategorien und Konten, keine Buchungen
|
||
* --stichtag=JJJJ-MM-TT ab hier werden Buchungen übernommen; alles davor
|
||
* fließt in den Anfangssaldo (Vorgabe aus den
|
||
* Einstellungen, sonst 2026-01-01)
|
||
* --bis=JJJJ-MM-TT Enddatum; spätere Buchungen sind Vorausberechnungen
|
||
* des FinanzManagers (Vorgabe: heute)
|
||
* --konto=NAME nur dieses Konto übernehmen; mehrfach angebbar. Ohne
|
||
* Angabe zählen alle Konten der Datei.
|
||
* --konten nennt die Konten der Datei und beendet sich
|
||
* --benutzer=NAME Mandant, sonst der erste aktive Benutzer
|
||
*/
|
||
|
||
require dirname(__DIR__) . '/vendor/autoload.php';
|
||
|
||
use Finanzmanager\Config;
|
||
use Finanzmanager\Mandant;
|
||
use Finanzmanager\Models\Benutzer;
|
||
use Finanzmanager\Models\Einstellung;
|
||
use Finanzmanager\Services\QifBuchungsImport;
|
||
use Finanzmanager\Services\QifParser;
|
||
use Finanzmanager\Services\QifStammdatenImport;
|
||
|
||
Config::load();
|
||
|
||
$argumente = array_slice($argv, 1);
|
||
$uebernehmen = false;
|
||
$nurStammdaten = false;
|
||
$nurKonten = false;
|
||
$benutzer = null;
|
||
$datei = null;
|
||
$stichtag = null;
|
||
$bis = null;
|
||
$konten = [];
|
||
|
||
foreach ($argumente as $argument) {
|
||
if ($argument === '--uebernehmen') {
|
||
$uebernehmen = true;
|
||
} elseif ($argument === '--nur-stammdaten') {
|
||
$nurStammdaten = true;
|
||
} elseif ($argument === '--konten') {
|
||
$nurKonten = true;
|
||
} elseif (str_starts_with($argument, '--konto=')) {
|
||
$konten[] = substr($argument, 8);
|
||
} elseif (str_starts_with($argument, '--benutzer=')) {
|
||
$benutzer = substr($argument, 11);
|
||
} elseif (str_starts_with($argument, '--stichtag=')) {
|
||
$stichtag = substr($argument, 11);
|
||
} elseif (str_starts_with($argument, '--bis=')) {
|
||
$bis = substr($argument, 6);
|
||
} elseif (str_starts_with($argument, '-')) {
|
||
fwrite(STDERR, "Unbekannte Option: {$argument}\n");
|
||
exit(1);
|
||
} else {
|
||
$datei = $argument;
|
||
}
|
||
}
|
||
|
||
if ($datei === null) {
|
||
fwrite(STDERR, "Aufruf: qif-import.php [--uebernehmen] [--nur-stammdaten] [--konten] "
|
||
. "[--konto=NAME] [--stichtag=JJJJ-MM-TT] [--bis=JJJJ-MM-TT] [--benutzer=NAME] <datei.qif>\n");
|
||
exit(1);
|
||
}
|
||
|
||
// Relative Angaben beziehen sich auf das Projektverzeichnis.
|
||
if (!str_starts_with($datei, '/')) {
|
||
$datei = dirname(__DIR__) . '/' . $datei;
|
||
}
|
||
|
||
if (!is_file($datei)) {
|
||
fwrite(STDERR, "Datei nicht gefunden: {$datei}\n");
|
||
exit(1);
|
||
}
|
||
|
||
if ($benutzer !== null) {
|
||
$zeile = Benutzer::findByName($benutzer);
|
||
if ($zeile === null) {
|
||
fwrite(STDERR, "Benutzer nicht gefunden: {$benutzer}\n");
|
||
exit(1);
|
||
}
|
||
Mandant::setze((int)$zeile['id']);
|
||
}
|
||
|
||
$stichtag ??= (string)Einstellung::get('import_stichtag', '2026-01-01');
|
||
$bis ??= date('Y-m-d');
|
||
|
||
// Die Konten der Datei nennen, ohne etwas anzufassen – so lässt sich
|
||
// nachsehen, welche Namen --konto= überhaupt kennt.
|
||
if ($nurKonten) {
|
||
try {
|
||
$parser = QifParser::ausDatei($datei);
|
||
} catch (\Throwable $e) {
|
||
fwrite(STDERR, 'Datei nicht lesbar: ' . $e->getMessage() . "\n");
|
||
exit(1);
|
||
}
|
||
|
||
echo 'Konten in ' . basename($datei) . ":\n";
|
||
foreach (QifBuchungsImport::kontenDerDatei($parser) as $name => $anzahl) {
|
||
printf(" %-40s %6d Einträge\n", $name, $anzahl);
|
||
}
|
||
exit(0);
|
||
}
|
||
|
||
try {
|
||
$parser = QifParser::ausDatei($datei);
|
||
|
||
// Ein Tippfehler im Kontonamen bliebe sonst unbemerkt: Der Import liefe
|
||
// durch und legte nichts an.
|
||
$bekannt = array_keys(QifBuchungsImport::kontenDerDatei($parser));
|
||
foreach ($konten as $name) {
|
||
if (!in_array($name, $bekannt, true)) {
|
||
fwrite(STDERR, "Konto nicht in der Datei: {$name}\n");
|
||
fwrite(STDERR, "Vorhanden: " . implode(', ', $bekannt) . "\n");
|
||
exit(1);
|
||
}
|
||
}
|
||
|
||
$stamm = (new QifStammdatenImport($parser))->ausfuehren(!$uebernehmen);
|
||
|
||
$buch = null;
|
||
if (!$nurStammdaten) {
|
||
$buch = (new QifBuchungsImport($parser, [
|
||
'stichtag' => $stichtag,
|
||
'bis' => $bis,
|
||
'konten' => $konten,
|
||
]))->ausfuehren(!$uebernehmen);
|
||
}
|
||
} catch (\Throwable $e) {
|
||
fwrite(STDERR, 'Import fehlgeschlagen: ' . $e->getMessage() . "\n");
|
||
exit(1);
|
||
}
|
||
|
||
echo ($uebernehmen ? 'Import' : 'Trockenlauf') . ': ' . basename($datei) . "\n";
|
||
|
||
$abschnitte = [];
|
||
foreach ($parser->zusammenfassung() as $typ => $anzahl) {
|
||
$abschnitte[] = "{$typ}: {$anzahl}";
|
||
}
|
||
echo 'Datei enthält ' . implode(' · ', $abschnitte) . "\n\n";
|
||
|
||
printf("%-12s %8s %12s %8s\n", 'Bereich', $uebernehmen ? 'angelegt' : 'neu', 'vorhanden', 'Fehler');
|
||
foreach (['klassen' => 'Klassen', 'kategorien' => 'Kategorien', 'konten' => 'Konten'] as $schluessel => $titel) {
|
||
printf(
|
||
"%-12s %8d %12d %8d\n",
|
||
$titel,
|
||
$stamm[$schluessel]['neu'],
|
||
$stamm[$schluessel]['vorhanden'],
|
||
$stamm[$schluessel]['fehler']
|
||
);
|
||
}
|
||
|
||
$meldungen = $stamm['meldungen'];
|
||
|
||
if ($buch !== null) {
|
||
printf("%-12s %8d %12d %8d\n", 'Buchungen',
|
||
$buch['buchungen']['neu'], $buch['buchungen']['vorhanden'], $buch['buchungen']['fehler']);
|
||
|
||
echo "\nZeitraum ab {$stichtag} bis {$bis}\n";
|
||
if ($buch['konten'] !== null) {
|
||
echo ' nur die Konten: ' . implode(', ', $buch['konten']) . "\n";
|
||
printf(" wegen der Kontoauswahl übergangen: %4d Buchungen\n", $buch['buchungen']['nicht_gewaehlt']);
|
||
}
|
||
printf(" vor dem Stichtag verrechnet: %6d Buchungen auf %d Konten (Summe %s)\n",
|
||
$buch['buchungen']['vor_stichtag'],
|
||
$buch['anfangssalden']['konten'],
|
||
number_format($buch['anfangssalden']['summe'], 2, ',', '.'));
|
||
printf(" nach dem Enddatum verworfen: %6d vorausberechnete Buchungen\n", $buch['buchungen']['zukunft']);
|
||
printf(" darunter Wertpapiergeschäfte: %5d (nur Geldfluss)\n", $buch['buchungen']['wertpapier']);
|
||
|
||
$meldungen = array_merge($meldungen, $buch['meldungen']);
|
||
}
|
||
|
||
if ($meldungen !== []) {
|
||
echo "\nProtokoll:\n";
|
||
foreach ($meldungen as $meldung) {
|
||
echo ' - ' . $meldung['text'] . "\n";
|
||
}
|
||
}
|
||
|
||
if (!$uebernehmen) {
|
||
echo "\nEs wurde nichts gespeichert. Mit --uebernehmen wird der Import durchgeführt.\n";
|
||
}
|
||
|
||
$fehler = $stamm['klassen']['fehler'] + $stamm['kategorien']['fehler'] + $stamm['konten']['fehler']
|
||
+ ($buch['buchungen']['fehler'] ?? 0);
|
||
exit($fehler > 0 ? 1 : 0);
|