Steuerkram/tests/30_IgErwerbReverseChargeTest.php

126 lines
5.6 KiB
PHP
Raw 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 67, 1774 für Kz 91) und die UStVA diese kontobasiert ausweist.
* 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 91 (USt), Kz 67 (VSt)
$kzMit = $calc->calculate($year, $month);
$delta89 = round((float)$kzMit['89']['betrag'] - (float)$kzPre['89']['betrag'], 2);
$delta91 = round((float)$kzMit['91']['steuer'] - (float)$kzPre['91']['steuer'], 2);
$delta67 = round((float)$kzMit['67']['steuer'] - (float)$kzPre['67']['steuer'], 2);
$delta69 = round((float)$kzMit['69']['steuer'] - (float)$kzPre['69']['steuer'], 2);
$this->assertEquals(1000.00, $delta89, 'Kz 89 stieg um 1000 €');
$this->assertEquals(190.00, $delta91, 'Kz 91 stieg um 190 €');
$this->assertEquals(190.00, $delta67, 'Kz 67 stieg um 190 €');
$this->assertEquals(0.00, $delta69, 'Kz 69 (Zahllast) unverändert – ReverseCharge neutral');
// Drill-down Kz 91 enthält die 1774-Kindzeile
$drilldown = $calc->fetchBookingsForKz($year, $month, '91');
$found = false;
foreach ($drilldown as $b) {
if ((int)$b['id'] === (int)$kind1774['id']) { $found = true; break; }
}
$this->assert($found, 'Drill-down Kz 91 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);
$delta91n = round((float)$kzNach['91']['steuer'] - (float)$kzPre['91']['steuer'], 2);
$delta67n = round((float)$kzNach['67']['steuer'] - (float)$kzPre['67']['steuer'], 2);
$this->assertEquals(0.00, $delta91n, 'Kz 91 nach Storno wieder auf Ausgangswert');
$this->assertEquals(0.00, $delta67n, 'Kz 67 nach Storno wieder auf Ausgangswert');
}
}