4, 'has_header_row' => 1, 'delimiter' => ';', 'enclosure' => '"', 'col_buchungstag' => 0, 'col_wertstellung' => 1, 'col_vorgang' => 6, 'col_buchungstext' => 5, 'col_wert' => 8, 'buchungstext_template_debit' => 'Auftraggeber: {Zahlungsempfänger*in} Buchungstext: {Verwendungszweck}', 'buchungstext_template_credit' => 'Auftraggeber: {Zahlungspflichtige*r} Buchungstext: {Verwendungszweck}', 'date_format' => 'd.m.Y', 'decimal_separator' => ',', 'thousands_separator' => '.', ]; } private function dkbHeader(): string { return '"Buchungsdatum";"Wertstellung";"Status";"Zahlungspflichtige*r";' . '"Zahlungsempfänger*in";"Verwendungszweck";"Umsatztyp";"IBAN";"Betrag (€)";' . '"Gläubiger-ID";"Mandatsreferenz";"Kundenreferenz"'; } public function run(): void { $parser = new \Steuerkram\Services\BankImportParser(); $fmt = $this->dkbFormat(); // ── Negativer Betrag: Zahlungsempfänger*in im Buchungstext ─────── $csv = "Skip1\nSkip2\nSkip3\nSkip4\n" . $this->dkbHeader() . "\n" . '"15.05.2026";"15.05.2026";"Gebucht";"Max Muster";"Amazon GmbH";"Abo Monatsbeitrag";"Lastschrift";"DE123";"- 9,99";"GL01";"MR01";"KR01"' . "\n"; $tmp = $this->makeTempCsv($csv); $rows = $parser->parse($tmp, 'csv', $fmt); unlink($tmp); $this->assertEquals(1, count($rows), 'DKB negativer Betrag: 1 Buchung geparst'); $this->assertEquals('2026-05-15', $rows[0]['buchungstag'], 'Buchungstag korrekt'); $this->assertEquals('2026-05-15', $rows[0]['wertstellung'], 'Wertstellung korrekt'); $this->assertEquals('Lastschrift', $rows[0]['vorgang'], 'Umsatztyp als vorgang'); $this->assertEquals(-9.99, $rows[0]['wert'], 'Betrag -9,99 korrekt'); $this->assertEquals( 'Auftraggeber: Amazon GmbH Buchungstext: Abo Monatsbeitrag', $rows[0]['buchungstext'], 'Negativer Betrag: Template debit mit Zahlungsempfänger*in' ); // ── Positiver Betrag: Zahlungspflichtige*r im Buchungstext ─────── $csv2 = "Skip1\nSkip2\nSkip3\nSkip4\n" . $this->dkbHeader() . "\n" . '"14.05.2026";"14.05.2026";"Gebucht";"Kunde AG";"Eigenkonto";"Rechnung 2026-042";"Gutschrift";"DE456";"1.500,00";"";"";"KR02"' . "\n"; $tmp2 = $this->makeTempCsv($csv2); $rows2 = $parser->parse($tmp2, 'csv', $fmt); unlink($tmp2); $this->assertEquals(1, count($rows2), 'DKB positiver Betrag: 1 Buchung geparst'); $this->assertEquals(1500.00, $rows2[0]['wert'], 'Betrag 1.500,00 korrekt'); $this->assertEquals( 'Auftraggeber: Kunde AG Buchungstext: Rechnung 2026-042', $rows2[0]['buchungstext'], 'Positiver Betrag: Template credit mit Zahlungspflichtige*r' ); // ── Betrag 0: credit-Template (>= 0) ──────────────────────────── $csv3 = "Skip1\nSkip2\nSkip3\nSkip4\n" . $this->dkbHeader() . "\n" . '"13.05.2026";"13.05.2026";"Gebucht";"Nullbetrag GmbH";"Gegenkonto";"Storno";"Umbuchung";"DE789";"0,00";"";"";"KR03"' . "\n"; $tmp3 = $this->makeTempCsv($csv3); $rows3 = $parser->parse($tmp3, 'csv', $fmt); unlink($tmp3); $this->assertEquals(1, count($rows3), 'DKB Betrag 0: 1 Buchung geparst'); $this->assertEquals( 'Auftraggeber: Nullbetrag GmbH Buchungstext: Storno', $rows3[0]['buchungstext'], 'Betrag 0: credit-Template genutzt' ); // ── Unbekannte Placeholder werden als leer aufgelöst ───────────── $fmtUnknown = $fmt; $fmtUnknown['buchungstext_template_debit'] = 'Test: {NichtVorhanden} Ende'; $csv5 = "Skip1\nSkip2\nSkip3\nSkip4\n" . $this->dkbHeader() . "\n" . '"12.05.2026";"12.05.2026";"Gebucht";"A";"B";"C";"Lastschrift";"DE";"- 1,00";"";"";"KR05"' . "\n"; $tmp5 = $this->makeTempCsv($csv5); $rows5 = $parser->parse($tmp5, 'csv', $fmtUnknown); unlink($tmp5); $this->assertEquals('Test: Ende', $rows5[0]['buchungstext'], 'Unbekannter Placeholder wird als leerer String aufgelöst'); // ── Format ohne Template: col_buchungstext wird direkt genutzt ─── $fmtStd = [ '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' => '.', ]; $csv4 = "Z1\nZ2\nZ3\n" . "Buchungstag;Wertstellung;Vorgang;Buchungstext;Betrag\n" . "15.05.2026;15.05.2026;Lastschrift;Normaler Text;\"-50,00\"\n"; $tmp4 = $this->makeTempCsv($csv4); $rows4 = $parser->parse($tmp4, 'csv', $fmtStd); unlink($tmp4); $this->assertEquals('Normaler Text', $rows4[0]['buchungstext'], 'Format ohne Template: col_buchungstext direkt genutzt'); } }