tempPath('.csv'); file_put_contents($path, $content); return $path; } private function comdirectFormat(): array { return [ 'skip_rows' => 3, 'has_header_row' => 1, 'delimiter' => ';', 'enclosure' => '"', 'col_buchungstag' => 0, 'col_wertstellung' => 1, 'col_vorgang' => 2, 'col_buchungstext' => 3, 'col_wert' => 4, 'date_format' => 'd.m.Y', 'decimal_separator' => ',', 'thousands_separator' => '.', ]; } public function run(): void { $parser = new \Steuerkram\Services\BankImportParser(); $fmt = $this->comdirectFormat(); // ── Normales CSV: 2 Buchungen ───────────────────────────────── $csv = "Zeile1\n" . "Zeile2\n" . "Zeile3\n" . "Buchungstag;Wertstellung;Vorgang;Buchungstext;Betrag\n" . "15.05.2026;15.05.2026;Lastschrift;Lieferant A;\"-119,00\"\n" . "14.05.2026;14.05.2026;Gutschrift;Kunde B;\"500,00\"\n" . "\n"; $tmp = $this->makeTempCsv($csv); $rows = $parser->parse($tmp, 'csv', $fmt); $this->assertEquals(2, count($rows), 'Normales CSV: 2 Buchungen geparsed'); $this->assertEquals('2026-05-15', $rows[0]['buchungstag'], 'Buchungstag korrekt geparst'); $this->assertEquals('2026-05-15', $rows[0]['wertstellung'], 'Wertstellung korrekt geparst'); $this->assertEquals('Lastschrift', $rows[0]['vorgang'], 'Vorgang korrekt geparst'); $this->assertEquals(-119.00, $rows[0]['wert'], 'Betrag -119,00 korrekt geparst'); $this->assertEquals(500.00, $rows[1]['wert'], 'Betrag 500,00 korrekt geparst'); // ── Leerzeilen werden übersprungen ──────────────────────────── $csvMitLeeren = "Z1\nZ2\nZ3\n" . "Buchungstag;Wertstellung;Vorgang;Buchungstext;Betrag\n" . "15.05.2026;15.05.2026;Lastschrift;Lieferant;\"-50,00\"\n" . ";;;\n" . "\n" . "14.05.2026;14.05.2026;Gutschrift;Kunde;\"200,00\"\n"; $tmp2 = $this->makeTempCsv($csvMitLeeren); $rows2 = $parser->parse($tmp2, 'csv', $fmt); $this->assertEquals(2, count($rows2), 'Leerzeilen werden übersprungen (2 von 3 Zeilen)'); // ── Leere Datei gibt leeres Array zurück ────────────────────── $csvLeer = "Z1\nZ2\nZ3\n" . "Buchungstag;Wertstellung;Vorgang;Buchungstext;Betrag\n" . "\n"; $tmp3 = $this->makeTempCsv($csvLeer); $rows3 = $parser->parse($tmp3, 'csv', $fmt); $this->assertEquals(0, count($rows3), 'Nur-Header-CSV: leeres Ergebnis'); // ── Ungültiger Dateipfad wirft RuntimeException ─────────────── $exceptionThrown = false; try { $parser->parse('/tmp/nicht_vorhanden_xyz_33.csv', 'csv', $fmt); } catch (\RuntimeException $e) { $exceptionThrown = true; } $this->assert($exceptionThrown, 'Nicht-vorhandene Datei wirft RuntimeException'); // ── UTF-8 BOM wird korrekt entfernt ─────────────────────────── $csvBom = "\xEF\xBB\xBFZ1\nZ2\nZ3\n" . "Buchungstag;Wertstellung;Vorgang;Buchungstext;Betrag\n" . "13.05.2026;13.05.2026;Lastschrift;BOM-Test;\"-99,00\"\n"; $tmp4 = $this->makeTempCsv($csvBom); $rows4 = $parser->parse($tmp4, 'csv', $fmt); $this->assertEquals(1, count($rows4), 'CSV mit UTF-8-BOM: 1 Buchung geparst'); $this->assertEquals(-99.00, $rows4[0]['wert'], 'Betrag trotz BOM korrekt geparst'); // ── Tausendertrennzeichen korrekt entfernt ──────────────────── $csvTausend = "Z1\nZ2\nZ3\n" . "Buchungstag;Wertstellung;Vorgang;Buchungstext;Betrag\n" . "12.05.2026;12.05.2026;Gutschrift;Grosskunde;\"-1.234,56\"\n"; $tmp5 = $this->makeTempCsv($csvTausend); $rows5 = $parser->parse($tmp5, 'csv', $fmt); $this->assertEquals(1, count($rows5), 'CSV mit Tausendertrenner: 1 Buchung'); $this->assertEquals(-1234.56, $rows5[0]['wert'], 'Betrag -1.234,56 korrekt geparst'); } }