findAccountId('1200'); $ausgabeId = $this->findAccountId('4120'); $konto1574 = \Steuerkram\Database::fetchOne( "SELECT id FROM accounts WHERE kontonummer = '1574' AND aktiv = 1" ); $konto1774 = \Steuerkram\Database::fetchOne( "SELECT id FROM accounts WHERE kontonummer = '1774' AND aktiv = 1" ); if (!$zahlungsId || !$ausgabeId || !$konto1574 || !$konto1774) { $this->assert(false, 'Benötigte Konten nicht gefunden (1200, 4120, 1574, 1774)'); return; } $this->assert(true, 'Konten 1200, 4120, 1574, 1774 vorhanden'); $calc = new \Steuerkram\Services\UstvaCalculator(); $kzPre = $calc->calculate($year, $month); $belegId = \Steuerkram\Models\Beleg::create([ 'beleg_nr' => 'IGE-TEST-' . uniqid(), 'filename' => 'ige-test.pdf', 'storage_path' => '', 'mime_type' => 'application/pdf', 'file_hash' => hash('sha256', 'ige-test-' . uniqid()), 'beleg_datum' => $datum, 'belegtyp' => 'Eingangsrechnung', ]); // Ausgabe-Buchung 0 % USt, steuerart=ig_erwerb_19, Netto=1000 → IGE-USt=190 $bookingId = \Steuerkram\Models\Booking::create([ 'beleg_id' => $belegId, 'buchungs_datum' => $datum, 'buchungstyp' => 'Ausgabe', 'kategorie_konto_id' => $ausgabeId, 'zahlungskonto_id' => $zahlungsId, 'betrag_netto' => 1000.00, 'ust_satz' => 0.0, 'steuerart' => 'ig_erwerb_19', 'buchungstext' => 'Test ig. Erwerb 19%', ]); // Zwei Kindzeilen müssen angelegt worden sein $kinder = \Steuerkram\Database::fetchAll( 'SELECT bk.*, a.kontonummer FROM bookings bk JOIN accounts a ON bk.kategorie_konto_id = a.id WHERE bk.parent_booking_id = ? ORDER BY a.kontonummer', [$bookingId] ); $this->assertEquals(2, count($kinder), 'Genau zwei Kindzeilen angelegt'); $kind1574 = null; $kind1774 = null; foreach ($kinder as $k) { if ($k['kontonummer'] === '1574') $kind1574 = $k; if ($k['kontonummer'] === '1774') $kind1774 = $k; } $this->assert($kind1574 !== null, 'Kindzeile auf Konto 1574 vorhanden'); $this->assertEquals(190.00, round((float)($kind1574['betrag_netto'] ?? 0), 2), '1574 betrag_netto = 190'); $this->assertEquals(0.00, round((float)($kind1574['betrag_ust'] ?? 0), 2), '1574 betrag_ust = 0'); $this->assert($kind1774 !== null, 'Kindzeile auf Konto 1774 vorhanden'); $this->assertEquals(190.00, round((float)($kind1774['betrag_netto'] ?? 0), 2), '1774 betrag_netto = 190'); $this->assertEquals(0.00, round((float)($kind1774['betrag_ust'] ?? 0), 2), '1774 betrag_ust = 0'); // UStVA: Kz 89 (Bemessungsgrundlage), Kz 61 (VSt ig. Erwerb via 1574), Kz 83 (neutral) $kzMit = $calc->calculate($year, $month); $delta89 = round((float)$kzMit['89']['betrag'] - (float)$kzPre['89']['betrag'], 2); $delta61 = round((float)$kzMit['61']['steuer'] - (float)$kzPre['61']['steuer'], 2); $delta83 = round((float)$kzMit['83']['steuer'] - (float)$kzPre['83']['steuer'], 2); $this->assertEquals(1000.00, $delta89, 'Kz 89 stieg um 1000 €'); $this->assertEquals(190.00, $delta61, 'Kz 61 (VSt ig. Erwerb, 1574) stieg um 190 €'); // ReverseCharge ist zahllastneutral: USt auf 1774 (+190) minus Kz61-Abzug (-190) = 0 $this->assertEquals(0.00, $delta83, 'Kz 83 (Vorauszahlung) unverändert – ReverseCharge neutral'); // Drill-down Kz 83 enthält die 1774-Kindzeile $drilldown = $calc->fetchBookingsForKz($year, $month, '83'); $found = false; foreach ($drilldown as $b) { if ((int)$b['id'] === (int)$kind1774['id']) { $found = true; break; } } $this->assert($found, 'Drill-down Kz 83 enthält die 1774-Kindzeile'); // Storno: beide Kindzeilen werden mitaufgehoben $reversalId = \Steuerkram\Models\Booking::reverse($bookingId, 'Test IGE-Storno'); $stornoKinder = \Steuerkram\Database::fetchAll( 'SELECT bk.*, a.kontonummer FROM bookings bk JOIN accounts a ON bk.kategorie_konto_id = a.id WHERE bk.parent_booking_id = ? AND bk.is_reversal = 1', [$reversalId] ); $this->assertEquals(2, count($stornoKinder), 'Storno erzeugt zwei Kindzeilen'); $kzNach = $calc->calculate($year, $month); $delta61n = round((float)$kzNach['61']['steuer'] - (float)$kzPre['61']['steuer'], 2); $delta83n = round((float)$kzNach['83']['steuer'] - (float)$kzPre['83']['steuer'], 2); $this->assertEquals(0.00, $delta61n, 'Kz 61 nach Storno wieder auf Ausgangswert'); $this->assertEquals(0.00, $delta83n, 'Kz 83 nach Storno wieder auf Ausgangswert'); } }