Steuerkram/tests/29_UmsatzsteuerkontenTest.php
fds2610-bot 55c02b54de Feature: USt-Buchungszeilen für Ausgangsrechnungen einführen
- Migration 046: ustva_kz='83'/'84' für Konten 1776/1771, Bestandsmigration für Einnahme-Buchungen
- Booking::create(): USt-Kindzeile auf 1771/1776 für Einnahmen mit 7/19 % (analog VSt)
- UstvaCalculator: Kz 83/84 kontobasiert via 1771/1776, KZ_ACCOUNT_MAP aktualisiert
- Invoice::cancel(): nur Hauptbuchungen direkt stornieren (parent_booking_id IS NULL)
- Tests 10, 27 angepasst; Test 29 neu
2026-05-10 15:11:00 +02:00

143 lines
6.6 KiB
PHP

<?php
declare(strict_types=1);
/**
* Test 29: USt-Buchungszeilen für Ausgangsrechnungen.
*
* Prüft, dass Einnahme-Buchungen mit USt 7 % / 19 % automatisch eine
* USt-Buchungszeile auf 1771 / 1776 erhalten, die UStVA Kz 83/84 daraus
* kontobasiert ableitet und ein Storno die USt-Zeilen mitaufhebt.
*/
class UmsatzsteuerkontenTest extends TestCase
{
protected string $testName = 'Test 29: USt-Buchungszeilen für Ausgangsrechnungen';
public function run(): void
{
$year = (int)date('Y');
$month = (int)date('n');
$datum = date('Y-m-d');
$zahlungsId = $this->findAccountId('1200');
$einnahmeId = $this->findAccountId('8400');
$konto1776 = \Steuerkram\Database::fetchOne(
"SELECT id FROM accounts WHERE kontonummer = '1776' AND aktiv = 1"
);
$konto1771 = \Steuerkram\Database::fetchOne(
"SELECT id FROM accounts WHERE kontonummer = '1771' AND aktiv = 1"
);
if (!$zahlungsId || !$einnahmeId || !$konto1776 || !$konto1771) {
$this->assert(false, 'Benötigte Konten nicht gefunden (1200, 8400, 1771, 1776)');
return;
}
$this->assert(true, 'Konten 1200, 8400, 1771, 1776 vorhanden');
// ── UStVA-Stand vor den Testbuchungen ─────────────────────────
$calc = new \Steuerkram\Services\UstvaCalculator();
$kzPre = $calc->calculate($year, $month);
// ── Beleg anlegen ──────────────────────────────────────────────
$belegId = \Steuerkram\Models\Beleg::create([
'beleg_nr' => 'UST-TEST-' . uniqid(),
'filename' => 'ust-test.pdf',
'storage_path' => '',
'mime_type' => 'application/pdf',
'file_hash' => hash('sha256', 'ust-test-' . uniqid()),
'beleg_datum' => $datum,
'belegtyp' => 'Ausgangsrechnung',
]);
// ── Einnahme-Buchung 19 % (Netto=500, USt=95) ─────────────────
$bookingId19 = \Steuerkram\Models\Booking::create([
'beleg_id' => $belegId,
'buchungs_datum' => $datum,
'buchungstyp' => 'Einnahme',
'kategorie_konto_id' => $einnahmeId,
'zahlungskonto_id' => $zahlungsId,
'betrag_netto' => 500.00,
'ust_satz' => 19.0,
'buchungstext' => 'Test USt 19%',
]);
// USt-Buchungszeile muss auf 1776 angelegt worden sein
$ustRow19 = \Steuerkram\Database::fetchOne(
'SELECT bk.*, a.kontonummer FROM bookings bk
JOIN accounts a ON bk.kategorie_konto_id = a.id
WHERE bk.parent_booking_id = ?',
[$bookingId19]
);
$this->assert($ustRow19 !== false, 'USt-Buchungszeile für 19%-Buchung angelegt');
$this->assertEquals('1776', $ustRow19['kontonummer'] ?? '', 'USt-Buchungszeile auf Konto 1776');
$this->assertEquals(95.00, round((float)($ustRow19['betrag_netto'] ?? 0), 2), 'USt betrag_netto = 95');
$this->assertEquals(0.00, round((float)($ustRow19['betrag_ust'] ?? 0), 2), 'USt betrag_ust = 0');
// ── Einnahme-Buchung 7 % (Netto=200, USt=14) ──────────────────
$bookingId7 = \Steuerkram\Models\Booking::create([
'beleg_id' => $belegId,
'buchungs_datum' => $datum,
'buchungstyp' => 'Einnahme',
'kategorie_konto_id' => $einnahmeId,
'zahlungskonto_id' => $zahlungsId,
'betrag_netto' => 200.00,
'ust_satz' => 7.0,
'buchungstext' => 'Test USt 7%',
]);
$ustRow7 = \Steuerkram\Database::fetchOne(
'SELECT bk.*, a.kontonummer FROM bookings bk
JOIN accounts a ON bk.kategorie_konto_id = a.id
WHERE bk.parent_booking_id = ?',
[$bookingId7]
);
$this->assert($ustRow7 !== false, 'USt-Buchungszeile für 7%-Buchung angelegt');
$this->assertEquals('1771', $ustRow7['kontonummer'] ?? '', 'USt-Buchungszeile auf Konto 1771');
$this->assertEquals(14.00, round((float)($ustRow7['betrag_netto'] ?? 0), 2), 'USt betrag_netto = 14');
// ── UStVA: Kz 83 und 84 aus Konten lesen ──────────────────────
$kzMit = $calc->calculate($year, $month);
$this->assert(
round((float)$kzMit['83']['steuer'] - (float)$kzPre['83']['steuer'], 2) >= 95.00,
'UStVA Kz 83 stieg durch 19%-Buchung um ≥ 95 €'
);
$this->assert(
round((float)$kzMit['84']['steuer'] - (float)$kzPre['84']['steuer'], 2) >= 14.00,
'UStVA Kz 84 stieg durch 7%-Buchung um ≥ 14 €'
);
// ── Drill-down Kz 83 enthält die USt-Buchungszeile ────────────
$drilldown = $calc->fetchBookingsForKz($year, $month, '83');
$ustFound = false;
foreach ($drilldown as $b) {
if ((int)$b['id'] === (int)$ustRow19['id']) {
$ustFound = true;
break;
}
}
$this->assert($ustFound, 'Drill-down Kz 83 enthält die USt-Buchungszeile');
// ── Storno: USt-Zeile muss mitaufgehoben werden ───────────────
$reversalId = \Steuerkram\Models\Booking::reverse($bookingId19, 'Testfall USt-Storno');
$ustStornoRow = \Steuerkram\Database::fetchOne(
'SELECT * FROM bookings WHERE parent_booking_id = ? AND is_reversal = 1',
[$reversalId]
);
$this->assert($ustStornoRow !== false, 'USt-Stornobuchung wurde angelegt');
$this->assertEquals(-95.00, round((float)($ustStornoRow['betrag_netto'] ?? 0), 2), 'USt-Storno betrag_netto = -95');
// Original-USt-Zeile muss als storniert markiert sein
$ustOriginal = \Steuerkram\Database::fetchOne(
'SELECT reversed_by FROM bookings WHERE id = ?',
[(int)$ustRow19['id']]
);
$this->assert(!empty($ustOriginal['reversed_by']), 'Original-USt-Buchungszeile hat reversed_by gesetzt');
// UStVA Kz 83 nach Storno: netto Änderung = nur noch 7%-Buchung sichtbar, 19% aufgehoben
$kzNach = $calc->calculate($year, $month);
$delta83 = round((float)$kzNach['83']['steuer'] - (float)$kzPre['83']['steuer'], 2);
$this->assertEquals(0.00, $delta83, 'Kz 83: Buchung+Storno heben sich gegenüber Ausgangslage auf');
}
}