From aef58154aa9ac8fb23e9434614d59ea2cbd551ec Mon Sep 17 00:00:00 2001 From: fds2610 Date: Wed, 1 Apr 2026 06:17:11 +0200 Subject: [PATCH] =?UTF-8?q?Fix=20#16:=20Stornobuchungen=20mit=20negierten?= =?UTF-8?q?=20Betr=C3=A4gen,=20is=5Freversal-Filter=20in=20Calculatoren=20?= =?UTF-8?q?entfernt?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Models/Booking.php | 6 +- src/Services/EuerCalculator.php | 5 +- src/Services/UstvaCalculator.php | 8 +- src/Views/tax/euer_konto_bookings.php | 2 +- src/Views/tax/ustva_kz_bookings.php | 5 +- tests/18_StornoBuchungTest.php | 104 ++++++++++++++++++++++++++ 6 files changed, 116 insertions(+), 14 deletions(-) create mode 100644 tests/18_StornoBuchungTest.php diff --git a/src/Models/Booking.php b/src/Models/Booking.php index b04cec5..b34f265 100644 --- a/src/Models/Booking.php +++ b/src/Models/Booking.php @@ -163,10 +163,10 @@ class Booking $reversalData['buchungstyp'], $reversalData['kategorie_konto_id'], $reversalData['zahlungskonto_id'], - $orig['betrag_netto'], + -1 * (float)$orig['betrag_netto'], $orig['ust_satz'], - $orig['betrag_ust'], - $orig['betrag_brutto'], + -1 * (float)$orig['betrag_ust'], + -1 * (float)$orig['betrag_brutto'], $reversalData['buchungstext'], $fy['id'], ] diff --git a/src/Services/EuerCalculator.php b/src/Services/EuerCalculator.php index 5ea807c..86ccc16 100644 --- a/src/Services/EuerCalculator.php +++ b/src/Services/EuerCalculator.php @@ -35,7 +35,6 @@ class EuerCalculator JOIN accounts a ON bk.kategorie_konto_id = a.id JOIN fiscal_years fy ON bk.fiscal_year_id = fy.id WHERE fy.year = ? - AND bk.is_reversal = 0 AND a.kontenart IN (\'Betriebseinnahme\', \'Betriebsausgabe\') GROUP BY a.id, a.kontonummer, a.bezeichnung, a.kontenart, a.euer_zeile HAVING buchungsanzahl > 0 @@ -81,7 +80,7 @@ class EuerCalculator return Database::fetchAll( 'SELECT bk.id, bk.buchungs_datum, bk.buchungstyp, bk.ust_satz, bk.betrag_netto, bk.betrag_ust, bk.betrag_brutto, bk.buchungstext, - bk.beleg_id, bl.beleg_nr, + bk.is_reversal, bk.beleg_id, bl.beleg_nr, k.kontonummer AS kategorie_nr, k.bezeichnung AS kategorie_name, z.kontonummer AS zahlungs_nr, z.bezeichnung AS zahlungs_name FROM bookings bk @@ -89,7 +88,7 @@ class EuerCalculator LEFT JOIN belege bl ON bk.beleg_id = bl.id JOIN accounts k ON bk.kategorie_konto_id = k.id JOIN accounts z ON bk.zahlungskonto_id = z.id - WHERE fy.year = ? AND bk.kategorie_konto_id = ? AND bk.is_reversal = 0 + WHERE fy.year = ? AND bk.kategorie_konto_id = ? ORDER BY bk.buchungs_datum, bk.id', [$year, $kontoId] ); diff --git a/src/Services/UstvaCalculator.php b/src/Services/UstvaCalculator.php index a54820b..eece441 100644 --- a/src/Services/UstvaCalculator.php +++ b/src/Services/UstvaCalculator.php @@ -61,7 +61,6 @@ class UstvaCalculator JOIN fiscal_years fy ON bk.fiscal_year_id = fy.id JOIN accounts a ON a.id = bk.kategorie_konto_id WHERE fy.year = ? AND MONTH(bk.buchungs_datum) = ? - AND bk.is_reversal = 0 AND a.ustva_kz IS NOT NULL AND a.ustva_kz != \'\' GROUP BY a.ustva_kz', [$year, $month] @@ -75,8 +74,8 @@ class UstvaCalculator ]; } - $n = fn(string $kz): float => abs($byKz[$kz]['netto'] ?? 0.0); - $u = fn(string $kz): float => abs($byKz[$kz]['ust'] ?? 0.0); + $n = fn(string $kz): float => (float)($byKz[$kz]['netto'] ?? 0.0); + $u = fn(string $kz): float => (float)($byKz[$kz]['ust'] ?? 0.0); $kz81 = $n('81'); $kz83 = $u('81'); $kz86 = $n('86'); $kz84 = $u('86'); @@ -124,7 +123,7 @@ class UstvaCalculator return Database::fetchAll( 'SELECT bk.id, bk.buchungs_datum, bk.buchungstyp, bk.steuerart, bk.ust_satz, bk.betrag_netto, bk.betrag_ust, bk.betrag_brutto, bk.buchungstext, - bk.beleg_id, bl.beleg_nr, + bk.is_reversal, bk.beleg_id, bl.beleg_nr, k.kontonummer AS kategorie_nr, k.bezeichnung AS kategorie_name, z.kontonummer AS zahlungs_nr, z.bezeichnung AS zahlungs_name FROM bookings bk @@ -133,7 +132,6 @@ class UstvaCalculator JOIN accounts k ON bk.kategorie_konto_id = k.id JOIN accounts z ON bk.zahlungskonto_id = z.id WHERE fy.year = ? AND MONTH(bk.buchungs_datum) = ? - AND bk.is_reversal = 0 AND k.ustva_kz IN (' . $placeholders . ') ORDER BY bk.buchungs_datum, bk.id', $params diff --git a/src/Views/tax/euer_konto_bookings.php b/src/Views/tax/euer_konto_bookings.php index b028a69..f5f45b5 100644 --- a/src/Views/tax/euer_konto_bookings.php +++ b/src/Views/tax/euer_konto_bookings.php @@ -46,7 +46,7 @@ $fmt = fn($v) => number_format((float)$v, 2, ',', '.') . ' €'; $sumUst += $ust; $sumBrutto += $brutto; ?> - + > diff --git a/src/Views/tax/ustva_kz_bookings.php b/src/Views/tax/ustva_kz_bookings.php index a0cb5e6..47ae919 100644 --- a/src/Views/tax/ustva_kz_bookings.php +++ b/src/Views/tax/ustva_kz_bookings.php @@ -20,8 +20,9 @@ $ustBeitragKz69 = function(array $b): float { }; // Ausgaben werden als negative Beträge angezeigt (Geld fließt ab). +// Stornobuchungen haben bereits negative Beträge in der DB – kein abs(). $signedBetrag = fn(array $b, string $field): float => - $b['buchungstyp'] === 'Ausgabe' ? -abs((float)$b[$field]) : abs((float)$b[$field]); + $b['buchungstyp'] === 'Ausgabe' ? -(float)$b[$field] : (float)$b[$field]; ?>
@@ -65,7 +66,7 @@ $signedBetrag = fn(array $b, string $field): float => $sumUst += $ust; $sumBrutto += $brutto; ?> - + > diff --git a/tests/18_StornoBuchungTest.php b/tests/18_StornoBuchungTest.php new file mode 100644 index 0000000..0575033 --- /dev/null +++ b/tests/18_StornoBuchungTest.php @@ -0,0 +1,104 @@ +findAccountId('1200'); // Bank + $ausgabeId = $this->findAccountId('4920'); // Telefon (ustva_kz=61) + + if (!$zahlungsId || !$ausgabeId) { + $this->assert(false, 'Benötigte Konten nicht gefunden – SKR03-Seed vorhanden?'); + return; + } + + // ── Beleg anlegen ────────────────────────────────────────────── + $belegId = \Steuerkram\Models\Beleg::create([ + 'beleg_nr' => 'STORNO-TEST-' . uniqid(), + 'filename' => 'storno-test.pdf', + 'storage_path' => '', + 'mime_type' => 'application/pdf', + 'file_hash' => hash('sha256', 'storno-test-' . uniqid()), + 'beleg_datum' => $datum, + 'eingangs_datum' => $datum, + 'belegtyp' => 'Ausgangsrechnung', + ]); + + // ── Ausgabe-Buchung anlegen (Netto=100, USt 19%=19, Brutto=119) ─ + $bookingId = \Steuerkram\Models\Booking::create([ + 'beleg_id' => $belegId, + 'buchungs_datum' => $datum, + 'buchungstyp' => 'Ausgabe', + 'kategorie_konto_id' => $ausgabeId, + 'zahlungskonto_id' => $zahlungsId, + 'betrag_netto' => 100.00, + 'ust_satz' => 19.0, + 'buchungstext' => 'Test-Ausgabe für Storno', + ]); + + $original = \Steuerkram\Models\Booking::find($bookingId); + $this->assertEquals(100.00, round((float)$original['betrag_netto'], 2), 'Originalbuchung: betrag_netto = 100'); + $this->assertEquals(19.00, round((float)$original['betrag_ust'], 2), 'Originalbuchung: betrag_ust = 19'); + $this->assertEquals(119.00, round((float)$original['betrag_brutto'], 2),'Originalbuchung: betrag_brutto = 119'); + + // ── Stornieren ──────────────────────────────────────────────── + $reversalId = \Steuerkram\Models\Booking::reverse($bookingId, 'Testfall Storno'); + $reversal = \Steuerkram\Models\Booking::find($reversalId); + + $this->assertEquals(1, (int)$reversal['is_reversal'], 'Stornobuchung: is_reversal = 1'); + $this->assertEquals(-100.00, round((float)$reversal['betrag_netto'], 2), 'Stornobuchung: betrag_netto = -100'); + $this->assertEquals(-19.00, round((float)$reversal['betrag_ust'], 2), 'Stornobuchung: betrag_ust = -19'); + $this->assertEquals(-119.00, round((float)$reversal['betrag_brutto'], 2), 'Stornobuchung: betrag_brutto = -119'); + + // ── EÜR: storniertes Konto hat Netto-Summe = 0 ─────────────── + $euerCalc = new \Steuerkram\Services\EuerCalculator(); + $euer = $euerCalc->calculate($year); + + $kontoNetto = null; + foreach ($euer['ausgaben'] as $row) { + if ((int)$row['id'] === (int)$ausgabeId) { + $kontoNetto = (float)$row['netto']; + break; + } + } + // Wenn das Konto nicht mehr auftaucht (alle Buchungen = 0), ist das ebenfalls korrekt. + if ($kontoNetto !== null) { + $this->assertEquals(0.0, round($kontoNetto, 2), 'EÜR: Storno + Original = 0 auf Ausgabe-Konto'); + } else { + $this->assert(true, 'EÜR: Konto taucht bei Summe=0 nicht auf (HAVING buchungsanzahl > 0 greift)'); + } + + // ── UStVA: Vorsteuer-KZ hebt sich auf ───────────────────────── + // Kz 61 enthält Vorsteuer aus dem Ausgabe-Konto (ustva_kz='61'). + // Original: +19, Storno: -19 → Summe = 0 (oder weniger als andere Tests einbringen). + // Da weitere Tests parallel laufen können, prüfen wir nur, dass die Storno-Buchung + // als eigenständige Zeile in fetchBookingsForKz erscheint. + $ustvaCalc = new \Steuerkram\Services\UstvaCalculator(); + $bookings = $ustvaCalc->fetchBookingsForKz($year, $month, '61'); + + $reversalFound = false; + foreach ($bookings as $b) { + if ((int)$b['id'] === $reversalId) { + $reversalFound = true; + $this->assertEquals(1, (int)$b['is_reversal'], 'UStVA-Drilldown: Stornobuchung is_reversal = 1'); + $this->assertEquals(-19.00, round((float)$b['betrag_ust'], 2), 'UStVA-Drilldown: Storno betrag_ust = -19'); + break; + } + } + $this->assert($reversalFound, 'UStVA-Drilldown Kz 61: Stornobuchung erscheint in Detailansicht'); + } +}