Steuerkram/tests/18_StornoBuchungTest.php
fds2610-bot d3a6c379c9 Feature: Vorsteuerkonten 1571/1576 aktivieren, VSt-Buchungszeilen einführen
- Migration 044: Konten 1574 (VSt i.g.E. 19%) und 1774 (USt i.g.E. 19%) anlegen
- Migration 045: Konten 1571/1576 anlegen, ustva_kz='61'/'62' setzen,
  altes DATEV-Routing (ustva_kz '61'/'62' auf Betriebsausgabe-Konten) entfernen,
  parent_booking_id-Spalte, VSt-Buchungszeilen für Bestandsbuchungen migrieren
- Booking::create(): automatisch VSt-Zeile auf 1576/1571 für Ausgaben mit 7/19 %
- Booking::reverse(): VSt-Zeilen beim Storno mitaufheben (reversed_by setzen)
- BookingController::destroy(): VSt-Kindzeilen beim Löschen (Testmodus) mit löschen
- UstvaCalculator: Kz 61/62 jetzt kontobasiert via 1576/1571 (betrag_netto),
  Step 2 (buchungsbasiert via ust_satz) entfernt
- Beleg::all/find: betrag_brutto-Aggregation und Suche schließen VSt-Zeilen aus
- Tests 12, 18 angepasst; Test 28 neu
2026-05-10 14:45:16 +02:00

122 lines
5.9 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 18: Stornobuchungen in UStVA und EÜR.
*
* Prüft, dass eine stornierte Buchung korrekt mit negativem Betrag angelegt wird
* und sich mit der Originalbuchung in den Berichten gegenseitig auf null aufhebt.
*/
class StornoBuchungTest extends TestCase
{
protected string $testName = 'Test 18: Stornobuchungen in UStVA und EÜR';
public function run(): void
{
$year = (int)date('Y');
$month = (int)date('n');
$datum = date('Y-m-d');
$zahlungsId = $this->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;
}
// ── EÜR: Konto-Stand VOR den Test-Buchungen ermitteln ─────────
$euerCalcPre = new \Steuerkram\Services\EuerCalculator();
$euerPre = $euerCalcPre->calculate($year);
$kontoNettoPre = 0.0;
foreach ($euerPre['ausgaben'] as $row) {
if ((int)$row['id'] === (int)$ausgabeId) {
$kontoNettoPre = (float)$row['netto'];
break;
}
}
// ── 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,
'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: Storno + Original heben sich auf (Konto-Saldo unverändert) ─
// Vergleich mit dem Stand vor den Test-Buchungen, da auf dem Produktivsystem
// bereits echte Buchungen auf dem Konto existieren können.
$euerCalc = new \Steuerkram\Services\EuerCalculator();
$euer = $euerCalc->calculate($year);
$kontoNetto = 0.0;
foreach ($euer['ausgaben'] as $row) {
if ((int)$row['id'] === (int)$ausgabeId) {
$kontoNetto = (float)$row['netto'];
break;
}
}
$this->assertEquals(
round($kontoNettoPre, 2),
round($kontoNetto, 2),
'EÜR: Storno + Original = 0 auf Ausgabe-Konto'
);
// ── UStVA: Vorsteuer-KZ hebt sich auf ─────────────────────────
// Kz 61 wird jetzt kontobasiert über VSt-Buchungszeilen (Konto 1576) ermittelt.
// Zur Original-Ausgabe gehört eine VSt-Zeile (+19), zum Storno eine VSt-Storno-Zeile (-19).
// Wir prüfen, dass die VSt-Storno-Zeile im Drill-down erscheint.
$vstReversalRow = \Steuerkram\Database::fetchOne(
'SELECT * FROM bookings WHERE parent_booking_id = ? AND is_reversal = 1',
[$reversalId]
);
$this->assert($vstReversalRow !== false, 'VSt-Stornobuchung wurde zu Storno angelegt');
if ($vstReversalRow) {
$this->assertEquals(-19.00, round((float)$vstReversalRow['betrag_netto'], 2), 'VSt-Storno: betrag_netto = -19');
}
$ustvaCalc = new \Steuerkram\Services\UstvaCalculator();
$bookings = $ustvaCalc->fetchBookingsForKz($year, $month, '61');
$vstReversalFound = false;
foreach ($bookings as $b) {
if ($vstReversalRow && (int)$b['id'] === (int)$vstReversalRow['id']) {
$vstReversalFound = true;
$this->assertEquals(1, (int)$b['is_reversal'], 'UStVA-Drilldown: VSt-Stornobuchung is_reversal = 1');
$this->assertEquals(-19.00, round((float)$b['betrag_netto'], 2), 'UStVA-Drilldown: VSt-Storno betrag_netto = -19');
break;
}
}
$this->assert($vstReversalFound, 'UStVA-Drilldown Kz 61: VSt-Stornobuchung erscheint in Detailansicht');
}
}