Fix #6: Auslandsumsätze und ig. Erwerb korrekt in UStVA abbilden

This commit is contained in:
fds2610 2026-03-26 20:07:28 +01:00
parent 2516623143
commit 7609d1e4e3
7 changed files with 393 additions and 42 deletions

View file

@ -0,0 +1,12 @@
-- Migration 006: steuerart für Auslandsbuchungen
-- Unterscheidet Buchungsarten für USt-freie Auslandsumsätze (UStVA-Pflicht)
ALTER TABLE bookings
ADD COLUMN steuerart ENUM(
'normal', -- Standard-Inlandsgeschäft
'ig_lieferung', -- Steuerfreie ig. Lieferung § 4 Nr. 1b (Kz 41)
'export', -- Ausfuhr § 4 Nr. 1a (Kz 44)
'ig_erwerb_19', -- Innergemeinschaftlicher Erwerb 19 % (Kz 89/91)
'ig_erwerb_7' -- Innergemeinschaftlicher Erwerb 7 % (Kz 93/94)
) NOT NULL DEFAULT 'normal'
AFTER ust_satz;

View file

@ -96,11 +96,16 @@ class Booking
$nr = self::generateBuchungsNr();
$steuerart = $data['steuerart'] ?? 'normal';
if (!in_array($steuerart, ['normal','ig_lieferung','export','ig_erwerb_19','ig_erwerb_7'], true)) {
$steuerart = 'normal';
}
Database::execute(
'INSERT INTO bookings (buchungs_nr, beleg_id, buchungs_datum, buchungstyp,
kategorie_konto_id, zahlungskonto_id,
betrag_netto, ust_satz, betrag_ust, betrag_brutto, buchungstext, fiscal_year_id)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)',
betrag_netto, ust_satz, steuerart, betrag_ust, betrag_brutto, buchungstext, fiscal_year_id)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)',
[
$nr,
(int)$data['beleg_id'],
@ -110,6 +115,7 @@ class Booking
(int)$data['zahlungskonto_id'],
$netto,
(float)($data['ust_satz'] ?? 0),
$steuerart,
$ust,
$brutto,
$data['buchungstext'],

View file

@ -153,17 +153,52 @@ class ElsterXmlService
$kzEl = $dom->createElement('Kz35', (string)round($kz['35']['betrag']));
$ustva->appendChild($kzEl);
}
// Kz41 (steuerfreie ig. Lieferungen)
if (!empty($kz['41']['betrag'])) {
$kzEl = $dom->createElement('Kz41', (string)round($kz['41']['betrag']));
$ustva->appendChild($kzEl);
}
// Kz44 (steuerfreie Ausfuhren)
if (!empty($kz['44']['betrag'])) {
$kzEl = $dom->createElement('Kz44', (string)round($kz['44']['betrag']));
$ustva->appendChild($kzEl);
}
// Kz83 (USt 19%)
$kzEl = $dom->createElement('Kz83', (string)(round($kz['83']['steuer'] * 100)));
$ustva->appendChild($kzEl);
// Kz84 (USt 7%)
$kzEl = $dom->createElement('Kz84', (string)(round($kz['84']['steuer'] * 100)));
$ustva->appendChild($kzEl);
// Kz89 (ig. Erwerb 19% Bemessungsgrundlage)
if (!empty($kz['89']['betrag'])) {
$kzEl = $dom->createElement('Kz89', (string)round($kz['89']['betrag']));
$ustva->appendChild($kzEl);
}
// Kz91 (USt auf ig. Erwerb 19%)
if (!empty($kz['91']['steuer'])) {
$kzEl = $dom->createElement('Kz91', (string)(round($kz['91']['steuer'] * 100)));
$ustva->appendChild($kzEl);
}
// Kz93 (ig. Erwerb 7% Bemessungsgrundlage)
if (!empty($kz['93']['betrag'])) {
$kzEl = $dom->createElement('Kz93', (string)round($kz['93']['betrag']));
$ustva->appendChild($kzEl);
}
// Kz94 (USt auf ig. Erwerb 7%)
if (!empty($kz['94']['steuer'])) {
$kzEl = $dom->createElement('Kz94', (string)(round($kz['94']['steuer'] * 100)));
$ustva->appendChild($kzEl);
}
// Kz66 (Vorsteuer gesamt)
if (!empty($kz['66']['steuer'])) {
$kzEl = $dom->createElement('Kz66', (string)(round($kz['66']['steuer'] * 100)));
$ustva->appendChild($kzEl);
}
// Kz67 (Vorsteuer aus ig. Erwerb)
if (!empty($kz['67']['steuer'])) {
$kzEl = $dom->createElement('Kz67', (string)(round($kz['67']['steuer'] * 100)));
$ustva->appendChild($kzEl);
}
// Kz69 (Vorauszahlung / Überschuss)
$kzEl = $dom->createElement('Kz69', (string)(round($kz['69']['steuer'] * 100)));
$ustva->appendChild($kzEl);

View file

@ -9,70 +9,120 @@ use Steuerkram\Database;
/**
* Berechnet alle ELSTER-Kennzahlen (Kz) für die Umsatzsteuervoranmeldung.
*
* Maßgeblich sind buchungstyp, ust_satz und betrag_ust der Buchung selbst.
* Maßgeblich sind buchungstyp, steuerart, ust_satz und betrag_ust der Buchung.
*
* Buchungslogik:
* Umsatzsteuer (Kz 81/86): buchungstyp = 'Einnahme' mit ust_satz > 0
* Vorsteuer (Kz 61/62): buchungstyp = 'Ausgabe' mit ust_satz > 0
* Steuerfreie Umsätze (Kz 35): buchungstyp = 'Einnahme' mit ust_satz = 0
* Umsatzsteuer (Kz 81/86): buchungstyp = 'Einnahme', steuerart = 'normal', ust_satz > 0
* Steuerfreie Umsätze (Kz 35): buchungstyp = 'Einnahme', steuerart = 'normal', ust_satz = 0
* Steuerfreie ig. Lieferungen (Kz 41): buchungstyp = 'Einnahme', steuerart = 'ig_lieferung'
* Ausfuhren (Kz 44): buchungstyp = 'Einnahme', steuerart = 'export'
* Vorsteuer (Kz 61/62): buchungstyp = 'Ausgabe', steuerart = 'normal', ust_satz > 0
* Ig. Erwerb 19 % (Kz 89/91): steuerart = 'ig_erwerb_19'
* Ig. Erwerb 7 % (Kz 93/94): steuerart = 'ig_erwerb_7'
* Vorsteuer aus ig. Erwerb (Kz 67): abgeleitet aus Kz 91 + Kz 94
*/
class UstvaCalculator
{
public function calculate(int $year, int $month): array
{
$kz = [
'81' => ['name' => 'Steuerpfl. Umsätze 19 %', 'betrag' => 0.0, 'steuer' => 0.0],
'86' => ['name' => 'Steuerpfl. Umsätze 7 %', 'betrag' => 0.0, 'steuer' => 0.0],
'35' => ['name' => 'Steuerfreie Umsätze (ohne Vorsteuerabzug)', 'betrag' => 0.0, 'steuer' => null],
'41' => ['name' => 'Steuerfreie EU-Lieferungen (§ 4 Nr. 1b)', 'betrag' => 0.0, 'steuer' => null],
'83' => ['name' => 'USt auf steuerpfl. Umsätze 19 %', 'betrag' => null, 'steuer' => 0.0],
'84' => ['name' => 'USt auf steuerpfl. Umsätze 7 %', 'betrag' => null, 'steuer' => 0.0],
'61' => ['name' => 'Vorsteuer aus Eingangsrechnungen 19 %', 'betrag' => null, 'steuer' => 0.0],
'62' => ['name' => 'Vorsteuer aus Eingangsrechnungen 7 %', 'betrag' => null, 'steuer' => 0.0],
'66' => ['name' => 'Vorsteuerbeträge gesamt', 'betrag' => null, 'steuer' => 0.0],
'69' => ['name' => 'Verbleibende USt-Vorauszahlung/Erstattung', 'betrag' => null, 'steuer' => 0.0],
// Steuerpflichtige und steuerfreie Umsätze
'81' => ['name' => 'Steuerpfl. Umsätze 19 %', 'betrag' => 0.0, 'steuer' => 0.0],
'86' => ['name' => 'Steuerpfl. Umsätze 7 %', 'betrag' => 0.0, 'steuer' => 0.0],
'35' => ['name' => 'Steuerfreie Umsätze (ohne Vorsteuerabzug)', 'betrag' => 0.0, 'steuer' => null],
'41' => ['name' => 'Steuerfreie ig. Lieferungen (§ 4 Nr. 1b)', 'betrag' => 0.0, 'steuer' => null],
'44' => ['name' => 'Steuerfreie Ausfuhren (§ 4 Nr. 1a)', 'betrag' => 0.0, 'steuer' => null],
// USt auf Umsätze (abgeleitet)
'83' => ['name' => 'USt auf steuerpfl. Umsätze 19 %', 'betrag' => null, 'steuer' => 0.0],
'84' => ['name' => 'USt auf steuerpfl. Umsätze 7 %', 'betrag' => null, 'steuer' => 0.0],
// Innergemeinschaftlicher Erwerb
'89' => ['name' => 'Ig. Erwerb 19 % – Bemessungsgrundlage', 'betrag' => 0.0, 'steuer' => null],
'91' => ['name' => 'USt auf ig. Erwerb 19 %', 'betrag' => null, 'steuer' => 0.0],
'93' => ['name' => 'Ig. Erwerb 7 % – Bemessungsgrundlage', 'betrag' => 0.0, 'steuer' => null],
'94' => ['name' => 'USt auf ig. Erwerb 7 %', 'betrag' => null, 'steuer' => 0.0],
// Vorsteuer
'61' => ['name' => 'Vorsteuer aus Eingangsrechnungen 19 %', 'betrag' => null, 'steuer' => 0.0],
'62' => ['name' => 'Vorsteuer aus Eingangsrechnungen 7 %', 'betrag' => null, 'steuer' => 0.0],
'67' => ['name' => 'Vorsteuer aus ig. Erwerb', 'betrag' => null, 'steuer' => 0.0],
'66' => ['name' => 'Vorsteuerbeträge gesamt', 'betrag' => null, 'steuer' => 0.0],
// Ergebnis
'69' => ['name' => 'Verbleibende USt-Vorauszahlung/Erstattung', 'betrag' => null, 'steuer' => 0.0],
];
$bookings = $this->fetchBookings($year, $month);
foreach ($bookings as $b) {
$steuerart = $b['steuerart'] ?? 'normal';
$ustSatz = (float)$b['ust_satz'];
$betragUst = abs((float)$b['betrag_ust']);
$betragNetto = abs((float)$b['betrag_netto']);
$buchungstyp = $b['buchungstyp'];
// ── Umsatzsteuer (Einnahmen) ─────────────────────────────
if ($buchungstyp === 'Einnahme' && $ustSatz > 0) {
if (abs($ustSatz - 19) < 0.01) {
$kz['81']['betrag'] += $betragNetto;
} elseif (abs($ustSatz - 7) < 0.01) {
$kz['86']['betrag'] += $betragNetto;
// ── Einnahmen ───────────────────────────────────────────────
if ($buchungstyp === 'Einnahme') {
switch ($steuerart) {
case 'ig_lieferung':
$kz['41']['betrag'] += $betragNetto;
break;
case 'export':
$kz['44']['betrag'] += $betragNetto;
break;
default: // normal
if ($ustSatz > 0) {
if (abs($ustSatz - 19) < 0.01) {
$kz['81']['betrag'] += $betragNetto;
} elseif (abs($ustSatz - 7) < 0.01) {
$kz['86']['betrag'] += $betragNetto;
}
} else {
$kz['35']['betrag'] += $betragNetto;
}
}
}
// Steuerfreie Umsätze: Einnahme mit ust_satz = 0
if ($buchungstyp === 'Einnahme' && $ustSatz == 0) {
$kz['35']['betrag'] += $betragNetto;
}
// ── Vorsteuer (Ausgaben) ─────────────────────────────────
if ($buchungstyp === 'Ausgabe' && $ustSatz > 0 && $betragUst > 0) {
if (abs($ustSatz - 19) < 0.01) {
$kz['61']['steuer'] += $betragUst;
} elseif (abs($ustSatz - 7) < 0.01) {
$kz['62']['steuer'] += $betragUst;
// ── Ausgaben ────────────────────────────────────────────────
if ($buchungstyp === 'Ausgabe') {
switch ($steuerart) {
case 'ig_erwerb_19':
$kz['89']['betrag'] += $betragNetto;
break;
case 'ig_erwerb_7':
$kz['93']['betrag'] += $betragNetto;
break;
default: // normal
if ($ustSatz > 0 && $betragUst > 0) {
if (abs($ustSatz - 19) < 0.01) {
$kz['61']['steuer'] += $betragUst;
} elseif (abs($ustSatz - 7) < 0.01) {
$kz['62']['steuer'] += $betragUst;
}
}
}
$kz['66']['steuer'] += $betragUst;
}
}
// Abgeleitete Kennzahlen
// ── Abgeleitete Kennzahlen ───────────────────────────────────────
$kz['83']['steuer'] = round($kz['81']['betrag'] * 0.19, 2);
$kz['84']['steuer'] = round($kz['86']['betrag'] * 0.07, 2);
$ustGesamt = $kz['83']['steuer'] + $kz['84']['steuer'];
$kz['91']['steuer'] = round($kz['89']['betrag'] * 0.19, 2);
$kz['94']['steuer'] = round($kz['93']['betrag'] * 0.07, 2);
// Vorsteuer aus ig. Erwerb = Steuer auf ig. Erwerb (vollständig abziehbar)
$kz['67']['steuer'] = round($kz['91']['steuer'] + $kz['94']['steuer'], 2);
// Vorsteuer gesamt
$kz['66']['steuer'] = round(
$kz['61']['steuer'] + $kz['62']['steuer'] + $kz['67']['steuer'],
2
);
// Verbleibende Zahllast
$ustGesamt = $kz['83']['steuer'] + $kz['84']['steuer']
+ $kz['91']['steuer'] + $kz['94']['steuer'];
$kz['69']['steuer'] = round($ustGesamt - $kz['66']['steuer'], 2);
// Runden
foreach ($kz as &$field) {
if ($field['betrag'] !== null) $field['betrag'] = round($field['betrag'], 2);
if ($field['steuer'] !== null) $field['steuer'] = round($field['steuer'], 2);
@ -84,7 +134,7 @@ class UstvaCalculator
private function fetchBookings(int $year, int $month): array
{
return Database::fetchAll(
'SELECT bk.buchungstyp, bk.ust_satz, bk.betrag_netto, bk.betrag_ust
'SELECT bk.buchungstyp, bk.steuerart, bk.ust_satz, bk.betrag_netto, bk.betrag_ust
FROM bookings bk
JOIN fiscal_years fy ON bk.fiscal_year_id = fy.id
WHERE fy.year = ? AND MONTH(bk.buchungs_datum) = ?

View file

@ -290,7 +290,7 @@ $testMode = Setting::isTestMode();
</div>
<div class="form-group">
<label>USt-Satz (%)</label>
<select name="ust_satz">
<select name="ust_satz" id="ust_satz_select" onchange="updateSteuerartVisibility()">
<?php foreach (['0','7','19'] as $s): ?>
<option value="<?= $s ?>"<?= ($formData['ust_satz'] ?? '19') === $s ? ' selected' : '' ?>><?= $s ?> %</option>
<?php endforeach; ?>
@ -298,6 +298,28 @@ $testMode = Setting::isTestMode();
</div>
</div>
<!-- Steuerart (für Auslandsumsätze) -->
<div class="form-group" id="steuerart_group">
<label>Steuerart (Ausland / EU)</label>
<select name="steuerart" id="steuerart_select">
<?php
$steuerartOptionen = [
'normal' => 'Normal (Inland)',
'ig_lieferung' => 'Steuerfreie ig. Lieferung § 4 Nr. 1b (Kz 41)',
'export' => 'Steuerfreie Ausfuhr § 4 Nr. 1a (Kz 44)',
'ig_erwerb_19' => 'Innergemeinschaftlicher Erwerb 19 % (Kz 89/91)',
'ig_erwerb_7' => 'Innergemeinschaftlicher Erwerb 7 % (Kz 93/94)',
];
$currentSteuerart = $formData['steuerart'] ?? 'normal';
foreach ($steuerartOptionen as $val => $label): ?>
<option value="<?= $val ?>"<?= $currentSteuerart === $val ? ' selected' : '' ?>><?= $label ?></option>
<?php endforeach; ?>
</select>
<p class="text-muted text-sm" style="margin-top:.25rem">
Nur relevant bei USt-Satz 0 % für Auslandsumsätze und EU-Erwerbe.
</p>
</div>
<!-- Kategorie-Konto -->
<div class="form-group">
<label for="kategorie_konto_id">Kategorie *
@ -406,5 +428,20 @@ function filterKategorieKonten(buchungstyp) {
document.addEventListener('DOMContentLoaded', function() {
const checked = document.querySelector('input[name="buchungstyp"]:checked');
if (checked) filterKategorieKonten(checked.value);
updateSteuerartVisibility();
});
function updateSteuerartVisibility() {
const ustSatz = document.getElementById('ust_satz_select');
const steuerartGroup = document.getElementById('steuerart_group');
const steuerartSelect = document.getElementById('steuerart_select');
if (!ustSatz || !steuerartGroup) return;
if (ustSatz.value === '0') {
steuerartGroup.style.display = '';
} else {
steuerartGroup.style.display = 'none';
if (steuerartSelect) steuerartSelect.value = 'normal';
}
}
</script>

View file

@ -30,10 +30,11 @@ $fmtKz = fn($v) => $v !== null ? number_format((float)$v, 2, ',', '.') . ' €'
<tbody>
<?php
$sections = [
'Steuerpflichtige Umsätze' => ['81', '86', '35', '41', '44'],
'Umsatzsteuer auf Umsätze' => ['83', '84'],
'Vorsteuerbeträge' => ['66', '61', '62'],
'Verbleibende Zahllast' => ['69'],
'Steuerpflichtige Umsätze' => ['81', '86', '35', '41', '44'],
'Umsatzsteuer auf Umsätze' => ['83', '84'],
'Innergemeinschaftlicher Erwerb' => ['89', '91', '93', '94'],
'Vorsteuerbeträge' => ['61', '62', '67', '66'],
'Verbleibende Zahllast' => ['69'],
];
foreach ($sections as $sectionTitle => $kzNrs):
?>

View file

@ -0,0 +1,210 @@
<?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
* 41, 44, 89, 91, 93, 94 und 67 einfließen.
*/
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');
// Konten aus SKR03
$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');
// Zweites Zahlungskonto für Ausgabe-Buchungen
$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;
// ── 1. Steuerfreie ig. Lieferung → Kz 41 ──────────────────────────
$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,
'eingangs_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 → Kz 44 ───────────────────────────────
$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,
'eingangs_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/91/67 ─────────────────────────────
$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,
'eingangs_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%',
]);
// ── 4. Ig. Erwerb 7 % → Kz 93/94/67 ──────────────────────────────
$belegIgE7 = \Steuerkram\Models\Beleg::create([
'beleg_nr' => 'TEST-IGE7-' . uniqid(),
'filename' => 'test-ige7.pdf',
'storage_path' => '',
'mime_type' => 'application/pdf',
'file_hash' => hash('sha256', 'test-ige7'),
'beleg_datum' => $datum,
'eingangs_datum' => $datum,
'belegtyp' => 'Eingangsrechnung',
]);
\Steuerkram\Models\Booking::create([
'beleg_id' => $belegIgE7,
'buchungs_datum' => $datum,
'buchungstyp' => 'Ausgabe',
'steuerart' => 'ig_erwerb_7',
'kategorie_konto_id' => $ausgabeId,
'zahlungskonto_id' => $bankId,
'betrag_netto' => 300.00,
'ust_satz' => 0.0,
'buchungstext' => 'Test ig. Erwerb 7%',
]);
// ── UStVA berechnen und Kennzahlen prüfen ─────────────────────────
$calc = new \Steuerkram\Services\UstvaCalculator();
$kz = $calc->calculate($year, $month);
// Kz 41: ig. Lieferung 1000 €
$this->assert(
(float)$kz['41']['betrag'] >= 1000.00,
'Kz 41 (ig. Lieferung): Betrag ≥ 1.000 €'
);
// Kz 44: Ausfuhr 2000 €
$this->assert(
(float)$kz['44']['betrag'] >= 2000.00,
'Kz 44 (Ausfuhr): Betrag ≥ 2.000 €'
);
// Kz 89: ig. Erwerb 19% Bemessungsgrundlage 500 €
$this->assert(
(float)$kz['89']['betrag'] >= 500.00,
'Kz 89 (ig. Erwerb 19% Bmgl.): Betrag ≥ 500 €'
);
// Kz 91: USt auf ig. Erwerb 19% = 500 * 0.19 = 95 €
$this->assertEquals(
round(500.00 * 0.19, 2),
(float)$kz['91']['steuer'],
'Kz 91 (USt ig. Erwerb 19%): Steuer = 95,00 €'
);
// Kz 93: ig. Erwerb 7% Bemessungsgrundlage 300 €
$this->assert(
(float)$kz['93']['betrag'] >= 300.00,
'Kz 93 (ig. Erwerb 7% Bmgl.): Betrag ≥ 300 €'
);
// Kz 94: USt auf ig. Erwerb 7% = 300 * 0.07 = 21 €
$this->assertEquals(
round(300.00 * 0.07, 2),
(float)$kz['94']['steuer'],
'Kz 94 (USt ig. Erwerb 7%): Steuer = 21,00 €'
);
// Kz 67: Vorsteuer aus ig. Erwerb = Kz 91 + Kz 94 = 116 €
$erwartetKz67 = round((float)$kz['91']['steuer'] + (float)$kz['94']['steuer'], 2);
$this->assertEquals(
$erwartetKz67,
(float)$kz['67']['steuer'],
'Kz 67 (Vorsteuer ig. Erwerb) = Kz 91 + Kz 94'
);
// Kz 69 Plausibilitätsprüfung: ig. Erwerb neutralisiert sich durch Kz 67
// USt auf Umsätze (83+84) + ig. Erwerb (91+94) - Vorsteuer (66) = 69
$ust = (float)$kz['83']['steuer'] + (float)$kz['84']['steuer']
+ (float)$kz['91']['steuer'] + (float)$kz['94']['steuer'];
$erwartetKz69 = round($ust - (float)$kz['66']['steuer'], 2);
$this->assertEquals(
$erwartetKz69,
(float)$kz['69']['steuer'],
'Kz 69 (Zahllast) korrekt berechnet'
);
// ig. Erwerb darf NICHT in Kz 35 oder Kz 61/62 landen
// (kz35 enthält nur normale 0%-Einnahmen, kz61 nur normale Vorsteuer)
// Wir buchten keine normalen 0%-Einnahmen, also muss Kz 35 = 0 sein
$this->assertEquals(
0.0,
(float)$kz['35']['betrag'],
'Kz 35 (steuerfreie Inlandsumsätze) = 0 (kein normaler 0%-Umsatz gebucht)'
);
}
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;
}
}