Steuerkram/tests/30_IgErwerbReverseChargeTest.php

126 lines
5.7 KiB
PHP
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
declare(strict_types=1);
/**
* Test 30: Innergemeinschaftlicher Erwerb 19 % – ReverseCharge-Kindzeilen.
*
* Prüft, dass ig_erwerb_19-Buchungen automatisch zwei Kindzeilen erhalten
* (1574 für Kz 61, 1774 für Kz 83) und die UStVA diese kontobasiert ausweist.
* ReverseCharge ist zahllastneutral (Kz 83 unverändert).
* Storno muss beide Kindzeilen mitaufheben.
*/
class IgErwerbReverseChargeTest extends TestCase
{
protected string $testName = 'Test 30: ig. Erwerb 19 % ReverseCharge-Kindzeilen';
public function run(): void
{
$year = (int)date('Y');
$month = (int)date('n');
$datum = date('Y-m-d');
$zahlungsId = $this->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');
}
}