144 lines
6.8 KiB
PHP
144 lines
6.8 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 und ein Storno die USt-Zeilen
|
||
* mitaufhebt. Kz 83 = ust81 + ust89 − Kz66 − Kz61 (abgeleitet, nicht kontobasiert).
|
||
*/
|
||
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'); // Erlöse 19 % USt (ustva_kz='81')
|
||
$einnahme7Id = $this->findAccountId('8300'); // Erlöse 7 % USt (ustva_kz=NULL)
|
||
$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 || !$einnahme7Id || !$konto1776 || !$konto1771) {
|
||
$this->assert(false, 'Benötigte Konten nicht gefunden (1200, 8300, 8400, 1771, 1776)');
|
||
return;
|
||
}
|
||
$this->assert(true, 'Konten 1200, 8300, 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) auf korrektem 7%-Konto 8300
|
||
$bookingId7 = \Steuerkram\Models\Booking::create([
|
||
'beleg_id' => $belegId,
|
||
'buchungs_datum' => $datum,
|
||
'buchungstyp' => 'Einnahme',
|
||
'kategorie_konto_id' => $einnahme7Id,
|
||
'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 = ust81 + ust89 − Kz66 − Kz61 ─────────────
|
||
// Nach 19%-Einnahme (500 netto → floor(500+X)*0,19 steigt um 95) und
|
||
// 7%-Einnahme auf 8300 (ustva_kz=NULL → kein Einfluss auf Kz 81/83):
|
||
// Kz 83 stieg um mindestens 95 €
|
||
$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 €'
|
||
);
|
||
|
||
// ── Drill-down Kz 83 enthält die USt-Buchungszeile (1776) ─────
|
||
$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 (1776)');
|
||
|
||
// ── 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');
|
||
|
||
// Kz 83 nach Storno der 19%-Buchung: kz81 zurück auf Baseline → delta83 = 0
|
||
// (7%-Buchung auf 8300 hat ustva_kz=NULL und beeinflusst Kz 81/83 nicht)
|
||
$kzNach = $calc->calculate($year, $month);
|
||
$delta83 = round((float)$kzNach['83']['steuer'] - (float)$kzPre['83']['steuer'], 2);
|
||
$this->assertEquals(0.00, $delta83, 'Kz 83: zurück auf Ausgangswert nach Storno der 19%-Buchung');
|
||
}
|
||
}
|