144 lines
6 KiB
PHP
144 lines
6 KiB
PHP
<?php
|
||
|
||
declare(strict_types=1);
|
||
|
||
/**
|
||
* Test 7: UStVA-Kennzahlen für Auslandsumsätze und ig. Erwerb.
|
||
*
|
||
* Prüft, dass steuerart-Buchungen korrekt in die UStVA-Kennzahlen einfließen.
|
||
* Im neuen Schema: Kz 89 (ig. Erwerb Bmgl.), Kz 61 (VSt ig. Erwerb), Kz 83 (Vorauszahlung).
|
||
* Kz 41/44 (ig. Lieferung, Ausfuhr) werden nicht mehr separat ausgewiesen.
|
||
*/
|
||
class UstvaAuslandTest extends TestCase
|
||
{
|
||
protected string $testName = 'Test 7: UStVA Auslandsumsätze und ig. Erwerb';
|
||
|
||
public function run(): void
|
||
{
|
||
$year = (int)date('Y');
|
||
$month = (int)date('n');
|
||
$datum = date('Y-m-d');
|
||
|
||
$erloesId = $this->findKonto('Betriebseinnahme');
|
||
$ausgabeId = $this->findKonto('Betriebsausgabe');
|
||
$zahlungId = $this->findKonto('Zahlungskonto');
|
||
|
||
if (!$erloesId || !$ausgabeId || !$zahlungId) {
|
||
$this->assert(false, 'Benötigte Konten nicht gefunden – SKR03-Seed vorhanden?');
|
||
return;
|
||
}
|
||
$this->assert(true, 'Konten vorhanden');
|
||
|
||
$bankId = \Steuerkram\Database::fetchOne(
|
||
"SELECT id FROM accounts WHERE kontenart = 'Zahlungskonto' AND aktiv = 1 AND id != ? LIMIT 1",
|
||
[$zahlungId]
|
||
);
|
||
$bankId = $bankId ? (int)$bankId['id'] : $zahlungId;
|
||
|
||
$calc = new \Steuerkram\Services\UstvaCalculator();
|
||
$kzPre = $calc->calculate($year, $month);
|
||
|
||
// ── 1. Steuerfreie ig. Lieferung (steuerart=ig_lieferung) ─────────────
|
||
// Wird nicht in Kz 81 eingerechnet (EU-Steuerarten-Filter), kein eigenes Kz.
|
||
$belegIgL = \Steuerkram\Models\Beleg::create([
|
||
'beleg_nr' => 'TEST-IGL-' . uniqid(),
|
||
'filename' => 'test-igl.pdf',
|
||
'storage_path' => '',
|
||
'mime_type' => 'application/pdf',
|
||
'file_hash' => hash('sha256', 'test-igl'),
|
||
'beleg_datum' => $datum,
|
||
'belegtyp' => 'Ausgangsrechnung',
|
||
]);
|
||
\Steuerkram\Models\Booking::create([
|
||
'beleg_id' => $belegIgL,
|
||
'buchungs_datum' => $datum,
|
||
'buchungstyp' => 'Einnahme',
|
||
'steuerart' => 'ig_lieferung',
|
||
'kategorie_konto_id' => $erloesId,
|
||
'zahlungskonto_id' => $zahlungId,
|
||
'betrag_netto' => 1000.00,
|
||
'ust_satz' => 0.0,
|
||
'buchungstext' => 'Test ig. Lieferung EU',
|
||
]);
|
||
|
||
// ── 2. Steuerfreie Ausfuhr (steuerart=export) ─────────────────────────
|
||
$belegExp = \Steuerkram\Models\Beleg::create([
|
||
'beleg_nr' => 'TEST-EXP-' . uniqid(),
|
||
'filename' => 'test-exp.pdf',
|
||
'storage_path' => '',
|
||
'mime_type' => 'application/pdf',
|
||
'file_hash' => hash('sha256', 'test-exp'),
|
||
'beleg_datum' => $datum,
|
||
'belegtyp' => 'Ausgangsrechnung',
|
||
]);
|
||
\Steuerkram\Models\Booking::create([
|
||
'beleg_id' => $belegExp,
|
||
'buchungs_datum' => $datum,
|
||
'buchungstyp' => 'Einnahme',
|
||
'steuerart' => 'export',
|
||
'kategorie_konto_id' => $erloesId,
|
||
'zahlungskonto_id' => $zahlungId,
|
||
'betrag_netto' => 2000.00,
|
||
'ust_satz' => 0.0,
|
||
'buchungstext' => 'Test Ausfuhr Drittland',
|
||
]);
|
||
|
||
// ── 3. Ig. Erwerb 19 % → Kz 89 / Kz 61 / Kz 83 ──────────────────────
|
||
$belegIgE19 = \Steuerkram\Models\Beleg::create([
|
||
'beleg_nr' => 'TEST-IGE19-' . uniqid(),
|
||
'filename' => 'test-ige19.pdf',
|
||
'storage_path' => '',
|
||
'mime_type' => 'application/pdf',
|
||
'file_hash' => hash('sha256', 'test-ige19'),
|
||
'beleg_datum' => $datum,
|
||
'belegtyp' => 'Eingangsrechnung',
|
||
]);
|
||
\Steuerkram\Models\Booking::create([
|
||
'beleg_id' => $belegIgE19,
|
||
'buchungs_datum' => $datum,
|
||
'buchungstyp' => 'Ausgabe',
|
||
'steuerart' => 'ig_erwerb_19',
|
||
'kategorie_konto_id' => $ausgabeId,
|
||
'zahlungskonto_id' => $bankId,
|
||
'betrag_netto' => 500.00,
|
||
'ust_satz' => 0.0,
|
||
'buchungstext' => 'Test ig. Erwerb 19%',
|
||
]);
|
||
|
||
// ── UStVA berechnen und Delta prüfen ──────────────────────────────────
|
||
$kz = $calc->calculate($year, $month);
|
||
|
||
// ig. Lieferung/Ausfuhr fließen nicht in Kz 81 ein (EU-Steuerarten-Filter)
|
||
$delta81 = round((float)$kz['81']['betrag'] - (float)$kzPre['81']['betrag'], 2);
|
||
$this->assertEquals(0.00, $delta81, 'ig. Lieferung/Ausfuhr gehen nicht in Kz 81 ein');
|
||
|
||
// Kz 89: ig. Erwerb 19% Bemessungsgrundlage +500 €
|
||
$this->assert(
|
||
round((float)$kz['89']['betrag'] - (float)$kzPre['89']['betrag'], 2) >= 500.00,
|
||
'Kz 89 (ig. Erwerb 19% Bmgl.): Delta ≥ 500 €'
|
||
);
|
||
|
||
// Kz 61: VSt ig. Erwerb (Konto 1574) +95 €
|
||
$this->assertEquals(
|
||
95.00,
|
||
round((float)$kz['61']['steuer'] - (float)$kzPre['61']['steuer'], 2),
|
||
'Kz 61 (VSt ig. Erwerb): Delta = 95,00 €'
|
||
);
|
||
|
||
// ReverseCharge ist zahllastneutral: USt auf 1774 (+95) wird durch Kz61-Abzug (-95) aufgehoben
|
||
$this->assertEquals(
|
||
0.00,
|
||
round((float)$kz['83']['steuer'] - (float)$kzPre['83']['steuer'], 2),
|
||
'Kz 83 (Vorauszahlung) durch ig. Erwerb nicht verändert'
|
||
);
|
||
}
|
||
|
||
private function findKonto(string $kontenart): ?int
|
||
{
|
||
$row = \Steuerkram\Database::fetchOne(
|
||
"SELECT id FROM accounts WHERE kontenart = ? AND aktiv = 1 LIMIT 1",
|
||
[$kontenart]
|
||
);
|
||
return $row ? (int)$row['id'] : null;
|
||
}
|
||
}
|