Revert "Fix: UStVA-Kennziffern auf offizielle ELSTER-Definitionen korrigiert"
This reverts commit bbfb41232e.
This commit is contained in:
parent
41d64dc6f6
commit
c6f7f4c0c2
11 changed files with 216 additions and 171 deletions
11
Changes.md
11
Changes.md
|
|
@ -18,17 +18,6 @@
|
|||
|
||||
---
|
||||
|
||||
## 2026-04-26 – Fix UStVA: Kennziffern auf offizielle ELSTER-Definitionen korrigiert
|
||||
|
||||
### Geändert
|
||||
- `src/Services/UstvaCalculator.php`: Kz 66 ist jetzt die primäre Inlandsvorsteuer (§§ 14, 14a) – alle Ausgabe-Buchungen mit Vorsteuer fließen direkt in Kz 66 (buchungsbasiert, unabhängig vom USt-Satz). Kz 61 ist jetzt Vorsteuer aus ig. Erwerben (§ 3d) = Kz89×0,19 + Kz93×0,07. Kz 62 (Einfuhrumsatzsteuer) und Kz 67 (§ 13b) werden auf 0 gesetzt. Kz 83 ist jetzt das Ergebnis (Verbleibende Vorauszahlung / Überschuss) statt der USt auf 19 %-Umsätze. Kz 35/44 (veraltet seit Migration 017) und Kz 91/94 (wurden fälschlich als USt-Beträge auf ig. Erwerb genutzt) entfernt. Kz 43 (Ausfuhren) und Kz 48 (steuerfreie Umsätze ohne VSt-Abzug) hinzugefügt.
|
||||
- `src/Services/ElsterXmlService.php`: Betragsfelder auf korrekte Kz aktualisiert (43/48 statt 35/44); Kz39-Export (Sondervorauszahlung) durch Kz83-Export (Vorauszahlung/Überschuss) ersetzt.
|
||||
- `src/Views/tax/ustva_show.php`: Abschnitte und Beschriftungen auf korrekte ELSTER-Kennziffern angepasst.
|
||||
- `src/Views/tax/ustva_kz_bookings.php`: Referenz auf Kz 69 → Kz 83 umgestellt.
|
||||
- `src/Controllers/UstvaController.php`: Whitelist gültiger Kennziffern aktualisiert.
|
||||
- `docs/berechnungslogik.md`: UStVA-Abschnitt vollständig neu beschrieben.
|
||||
|
||||
---
|
||||
|
||||
## 2026-04-26 – KI-Diagnose: Erreichbarkeitsprüfung und Fehlerlog für Ollama
|
||||
|
||||
|
|
|
|||
|
|
@ -43,31 +43,44 @@ Die EÜR basiert auf dem Feld `accounts.kontenart` sowie `accounts.euer_zeile` d
|
|||
|
||||
Die UStVA kombiniert drei Quellen:
|
||||
|
||||
**Quelle 1 – kontobasiert** (`accounts.ustva_kz`): Umsatz- und steuerfreie Konten. Buchungen mit EU-Steuerarten werden hier ausgeschlossen.
|
||||
**Quelle 1 – kontobasiert** (`accounts.ustva_kz`): reguläre Umsätze. Buchungen mit EU-Steuerarten werden hier ausgeschlossen. Kz 61/62 sind aus dieser Quelle entfernt – `ustva_kz='61'/'62'` wird ignoriert.
|
||||
|
||||
**Quelle 2 – buchungsbasiert** (`buchungstyp='Ausgabe'`, `betrag_ust != 0`): Alle inländischen Eingangsrechnungen mit Vorsteuer → **Kz 66** (§§ 14, 14a UStG). Buchungen mit EU-Steuerarten sind ausgenommen. Der Wert `ustva_kz` auf Vorsteuer-Konten wird nicht ausgewertet.
|
||||
**Quelle 2 – buchungsbasiert** (`bookings.ust_satz`): Vorsteuer Kz 61/62. Alle Ausgabe-Buchungen mit `betrag_ust != 0` fließen anhand ihres `ust_satz` direkt in Kz 61 (19 %) bzw. Kz 62 (7 %) ein, unabhängig davon welches Konto gebucht wurde. Buchungen mit EU-Steuerarten sind ausgenommen.
|
||||
|
||||
**Quelle 3 – steuerartbasiert** (`bookings.steuerart`): EU-Sonderfälle (ig. Lieferung, Ausfuhr, ig. Erwerb). Diese Buchungen landen unabhängig vom Kategorie-Konto in den richtigen Kennzahlen.
|
||||
|
||||
### Kontozuordnung und Kennzahlen
|
||||
|
||||
| Kz | ELSTER-Bezeichnung | Quelle | Erläuterung |
|
||||
| Kz | Bezeichnung | Quelle | Erläuterung |
|
||||
|---|---|---|---|
|
||||
| **81** | Steuerpfl. Umsätze 19 % | `SUM(betrag_netto)` WHERE `ustva_kz='81'` | 8400, 8570, 8720, 8801, 8820, 8922 |
|
||||
| **83** | USt auf steuerpfl. Umsätze 19 % | `SUM(betrag_ust)` WHERE `ustva_kz='81'` | s.o. |
|
||||
| **86** | Steuerpfl. Umsätze 7 % | `SUM(betrag_netto)` WHERE `ustva_kz='86'` | 8300, 8710, 8921 |
|
||||
| **41** | Steuerfreie ig. Lieferungen (§ 4 Nr. 1b) | kontobasiert (`ustva_kz='41'`) **+** `steuerart='ig_lieferung'` | 8125; oder Buchung mit steuerart |
|
||||
| **43** | Steuerfreie Ausfuhren (§ 4 Nr. 1a) | kontobasiert (`ustva_kz='43'`) **+** `steuerart='export'` | 8120; oder Buchung mit steuerart |
|
||||
| **48** | Steuerfreie Umsätze ohne Vorsteuerabzug | `SUM(betrag_netto)` WHERE `ustva_kz='48'` | 8100, 8105, 8110 |
|
||||
| **84** | USt auf steuerpfl. Umsätze 7 % | `SUM(betrag_ust)` WHERE `ustva_kz='86'` | s.o. |
|
||||
| **35** | Steuerfreie Umsätze | `SUM(betrag_netto)` WHERE `ustva_kz='35'` | 8100, 8105, 8110 |
|
||||
| **41** | Steuerfreie ig. Lieferungen | kontobasiert (`ustva_kz='41'`) **+** `steuerart='ig_lieferung'` | 8125; oder Buchung mit steuerart |
|
||||
| **44** | Steuerfreie Ausfuhren | kontobasiert (`ustva_kz='44'`) **+** `steuerart='export'` | 8120; oder Buchung mit steuerart |
|
||||
| **61** | Vorsteuer 19 % | `SUM(betrag_ust)` WHERE `buchungstyp='Ausgabe' AND ROUND(ust_satz)=19` | buchungsbasiert – kein spezielles VSt19-Konto nötig |
|
||||
| **62** | Vorsteuer 7 % | `SUM(betrag_ust)` WHERE `buchungstyp='Ausgabe' AND ROUND(ust_satz)=7` | buchungsbasiert – kein spezielles VSt7-Konto nötig |
|
||||
| **89** | Ig. Erwerb 19 % – Bemessungsgrundlage | `SUM(betrag_netto)` WHERE `steuerart='ig_erwerb_19'` | kein spezielles Konto nötig |
|
||||
| **91** | USt auf ig. Erwerb 19 % | `Kz 89 × 0,19` | berechnet |
|
||||
| **93** | Ig. Erwerb 7 % – Bemessungsgrundlage | `SUM(betrag_netto)` WHERE `steuerart='ig_erwerb_7'` | kein spezielles Konto nötig |
|
||||
| **66** | Vorsteuer aus Lieferungen/Leistungen (§§ 14, 14a) | `SUM(betrag_ust)` WHERE `buchungstyp='Ausgabe' AND betrag_ust != 0` | buchungsbasiert – alle inländischen VSt-Beträge unabhängig vom Satz |
|
||||
| **61** | Vorsteuer aus ig. Erwerben (§ 3d) | `Kz 89 × 0,19 + Kz 93 × 0,07` | berechnet; ig. Erwerb ist USt-neutral (hebt sich in Kz 83 auf) |
|
||||
| **62** | Vorsteuer aus Einfuhrumsatzsteuer | 0 | in diesem System nicht vorgesehen |
|
||||
| **67** | Vorsteuer aus Leistungen nach § 13b | 0 | in diesem System nicht vorgesehen |
|
||||
| **83** | Verbleibende Umsatzsteuer-Vorauszahlung / Überschuss | `ust19 + ust7 − Kz 66 − Kz 61` | Ergebnis; ust19/ust7 = intern aus `betrag_ust` von 81/86-Konten |
|
||||
| **94** | USt auf ig. Erwerb 7 % | `Kz 93 × 0,07` | berechnet |
|
||||
|
||||
### Kennziffern-Bedeutung (ELSTER)
|
||||
### Abgeleitete Kennzahlen
|
||||
|
||||
Kz 83 ist laut ELSTER-Hilfe das **Ergebnis der Voranmeldung** (Verbleibende Vorauszahlung oder Überschuss), nicht – wie in früheren Systemversionen – die USt auf 19 %-Umsätze. Die ELSTER-Kennziffern 83, 61, 62, 66, 67 wurden mit dieser Änderung (2026-04-26) auf die offiziellen ELSTER-Definitionen korrigiert.
|
||||
| Kz | Bezeichnung | Berechnung |
|
||||
|---|---|---|
|
||||
| **67** | Vorsteuer aus ig. Erwerb | Kz 91 + Kz 94 |
|
||||
| **66** | Vorsteuerbeträge gesamt | Kz 61 + Kz 62 + Kz 67 |
|
||||
| **69** | Verbleibende USt-Vorauszahlung / Erstattung | (Kz 83 + Kz 84 + Kz 91 + Kz 94) − Kz 66 |
|
||||
|
||||
Kz 35 und Kz 44 wurden in Migration 017 auf Kz 48 (steuerfreie Umsätze ohne VSt-Abzug) bzw. Kz 43 (Ausfuhren) umgestellt und werden nicht mehr verwendet.
|
||||
### Migration 022 – Vorsteuer-Kz-Aufteilung (historisch)
|
||||
|
||||
Vor Migration 022 trugen alle Vorsteuer-Konten `ustva_kz='66'`. Migration 022 setzte `ustva_kz='61'/'62'` je nach `steuercode`. Diese kontobasierte Zuordnung war erforderlich, weil alle Ausgaben eines Kontos denselben USt-Satz hatten.
|
||||
|
||||
### Buchungsbasierte Vorsteuer (ab 2026)
|
||||
|
||||
Die kontobasierte Zuordnung über `ustva_kz='61'/'62'` wurde zugunsten einer buchungsbasierten Zuordnung aufgegeben: `UstvaCalculator` wertet jetzt direkt `bookings.ust_satz` aus (`buchungstyp='Ausgabe'`, `betrag_ust != 0`). Damit reicht ein einzelnes Aufwandskonto für 19 % und 7 % USt aus. Der Wert `ustva_kz='61'/'62'` auf Konten wird nicht mehr ausgewertet.
|
||||
|
||||
Kz 66 wird weiterhin rechnerisch als `Kz 61 + Kz 62 + Kz 67` aggregiert.
|
||||
|
|
|
|||
|
|
@ -104,7 +104,7 @@ class UstvaController
|
|||
$month = (int)$params['month'];
|
||||
$kz = $params['kz'] ?? '';
|
||||
|
||||
$validKz = ['81','86','41','43','48','89','93','66','61','62','67','83'];
|
||||
$validKz = ['81','83','86','84','35','41','44','89','91','93','94','61','62','67','66','69'];
|
||||
if (!in_array($kz, $validKz, true)) {
|
||||
View::redirect("/tax/ustva/{$year}/{$month}", 'Ungültige Kennzahl.', 'error');
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -114,12 +114,12 @@ class ElsterXmlService
|
|||
$ustva->appendChild($dom->createElement('Steuernummer', $steuernummer));
|
||||
|
||||
// Kz-Felder: Bemessungsgrundlagen ganzzahlig, Steuerbeträge mit 2 Dezimalstellen.
|
||||
// Kz83 (Vorauszahlung/Überschuss) wird aus den Komponenten von ELSTER berechnet,
|
||||
// muss aber dennoch mit übertragen werden.
|
||||
// Kz83/Kz84 werden nicht übertragen (serverseitig aus Kz81/Kz86 abgeleitet).
|
||||
// Kz69 (Zahllast) wird als Kz39 übertragen.
|
||||
$kzBetrag = fn(float $v): string => (string)(int)round($v);
|
||||
$kzSteuer = fn(float $v): string => number_format($v, 2, '.', '');
|
||||
|
||||
$betragsfelder = ['81', '86', '41', '43', '48', '89', '93'];
|
||||
$betragsfelder = ['81', '86', '35', '41', '44', '89', '93'];
|
||||
foreach ($betragsfelder as $nr) {
|
||||
$val = (float)($kz[$nr]['betrag'] ?? 0.0);
|
||||
if ($val != 0.0) {
|
||||
|
|
@ -127,7 +127,7 @@ class ElsterXmlService
|
|||
}
|
||||
}
|
||||
|
||||
$steuerfelder = ['66', '61', '62', '67'];
|
||||
$steuerfelder = ['61', '62', '67', '66'];
|
||||
foreach ($steuerfelder as $nr) {
|
||||
$val = (float)($kz[$nr]['steuer'] ?? 0.0);
|
||||
if ($val != 0.0) {
|
||||
|
|
@ -135,10 +135,10 @@ class ElsterXmlService
|
|||
}
|
||||
}
|
||||
|
||||
// Kz83: Verbleibende Umsatzsteuer-Vorauszahlung (positiv) / Überschuss (negativ)
|
||||
$kz83val = (float)($kz['83']['steuer'] ?? 0.0);
|
||||
if ($kz83val != 0.0) {
|
||||
$ustva->appendChild($dom->createElement('Kz83', $kzSteuer($kz83val)));
|
||||
// Zahllast → Kz39 (nur bei positivem Wert; bei Erstattung weglassen, da kein Vorzeichen erlaubt)
|
||||
$zahllast = (float)($kz['69']['steuer'] ?? 0.0);
|
||||
if ($zahllast > 0.0) {
|
||||
$ustva->appendChild($dom->createElement('Kz39', $kzSteuer($zahllast)));
|
||||
}
|
||||
|
||||
return $dom->saveXML();
|
||||
|
|
|
|||
|
|
@ -9,72 +9,76 @@ use Steuerkram\Database;
|
|||
/**
|
||||
* Berechnet alle ELSTER-Kennzahlen (Kz) für die Umsatzsteuervoranmeldung.
|
||||
*
|
||||
* Drei Quellen fließen in die Berechnung ein:
|
||||
* Zwei Quellen fließen in die Berechnung ein:
|
||||
*
|
||||
* 1. Kontobasiert (accounts.ustva_kz des Kategorie-Kontos):
|
||||
* ustva_kz = '81' → Kz 81 (betrag_netto); intern: ust19 (betrag_ust)
|
||||
* ustva_kz = '86' → Kz 86 (betrag_netto); intern: ust7 (betrag_ust)
|
||||
* ustva_kz = '81' → Kz 81 (betrag_netto), Kz 83 (betrag_ust)
|
||||
* ustva_kz = '86' → Kz 86 (betrag_netto), Kz 84 (betrag_ust)
|
||||
* ustva_kz = '35' → Kz 35 (betrag_netto)
|
||||
* ustva_kz = '41' → Kz 41 (betrag_netto) [steuerfreie ig. Lieferungen via Konto]
|
||||
* ustva_kz = '43' → Kz 43 (betrag_netto) [steuerfreie Ausfuhren § 4 Nr. 1a]
|
||||
* ustva_kz = '48' → Kz 48 (betrag_netto) [steuerfreie Umsätze ohne Vorsteuerabzug]
|
||||
* ustva_kz = '44' → Kz 44 (betrag_netto) [steuerfreie Ausfuhren via Konto]
|
||||
* (Kz 61/62 werden NICHT mehr über ustva_kz geroutet)
|
||||
*
|
||||
* 2. Buchungsbasiert (buchungstyp='Ausgabe', betrag_ust ≠ 0):
|
||||
* Alle inländischen Eingangsrechnungen → Kz 66 (§§ 14, 14a UStG)
|
||||
* 2. Buchungsbasiert (bookings.ust_satz, für Vorsteuer):
|
||||
* buchungstyp = 'Ausgabe', ust_satz = 19 → Kz 61 (betrag_ust)
|
||||
* buchungstyp = 'Ausgabe', ust_satz = 7 → Kz 62 (betrag_ust)
|
||||
* Damit entfällt die Notwendigkeit separater VSt19/VSt7-Konten.
|
||||
*
|
||||
* 3. Steuerart-basiert (bookings.steuerart, unabhängig vom Konto):
|
||||
* steuerart = 'ig_lieferung' → Kz 41 (betrag_netto)
|
||||
* steuerart = 'export' → Kz 43 (betrag_netto)
|
||||
* steuerart = 'ig_erwerb_19' → Kz 89 (betrag_netto)
|
||||
* steuerart = 'ig_erwerb_7' → Kz 93 (betrag_netto)
|
||||
* steuerart = 'export' → Kz 44 (betrag_netto)
|
||||
* steuerart = 'ig_erwerb_19' → Kz 89 (betrag_netto), Kz 91 (netto × 0,19)
|
||||
* steuerart = 'ig_erwerb_7' → Kz 93 (betrag_netto), Kz 94 (netto × 0,07)
|
||||
* Buchungen mit diesen steuerart-Werten bleiben aus der kontobasierten Berechnung
|
||||
* ausgeschlossen, um Doppelzählung zu vermeiden.
|
||||
* Buchungen mit EU-Steuerarten fließen ebenfalls NICHT in Kz 61/62.
|
||||
*
|
||||
* Abgeleitete Kennzahlen:
|
||||
* Kz 61 = Kz89 × 0,19 + Kz93 × 0,07 (Vorsteuer aus ig. Erwerben, § 3d UStG)
|
||||
* Kz 62 = 0 (Einfuhrumsatzsteuer – in diesem System n.v.)
|
||||
* Kz 67 = 0 (§ 13b-Vorsteuer – in diesem System n.v.)
|
||||
* Kz 83 = ust19 + ust7 − Kz66 − Kz61 − Kz62 − Kz67
|
||||
* = ust19 + ust7 − Kz66 (ig. Erwerb ist USt-neutral)
|
||||
* Kz 67 = Kz 91 + Kz 94 (Vorsteuer aus ig. Erwerb)
|
||||
* Kz 66 = Kz 61 + Kz 62 + Kz 67 (Vorsteuer gesamt)
|
||||
* Kz 69 = (Kz 83 + Kz 84 + Kz 91 + Kz 94) − Kz 66 (Zahllast)
|
||||
*/
|
||||
class UstvaCalculator
|
||||
{
|
||||
/** Steuerart-Werte die AUSSCHLIESSLICH steuerart-basiert berechnet werden. */
|
||||
private const EU_STEUERARTEN = ['ig_lieferung', 'export', 'ig_erwerb_19', 'ig_erwerb_7'];
|
||||
|
||||
/** Vorsteuer-Kennzahl je USt-Satz (buchungsbasiert). */
|
||||
private const VST_SATZ_KZ = [19 => '61', 7 => '62'];
|
||||
|
||||
/**
|
||||
* Kontobasiertes Drill-down-Mapping.
|
||||
* Kz 83 (Zahllast) enthält auch die Einnahmen-Konten 81/86.
|
||||
* Welche accounts.ustva_kz-Werte liefern Buchungen für die Drill-down-Ansicht.
|
||||
* Nur für den kontobasierten Teil; EU-Steuerarten kommen aus KZ_STEUERART_MAP.
|
||||
* Kz 61/62 sind hier nicht mehr enthalten (buchungsbasiert via VST_SATZ_KZ).
|
||||
*/
|
||||
private const KZ_ACCOUNT_MAP = [
|
||||
'81' => ['81'],
|
||||
'83' => ['81'],
|
||||
'86' => ['86'],
|
||||
'84' => ['86'],
|
||||
'35' => ['35'],
|
||||
'41' => ['41'],
|
||||
'43' => ['43'],
|
||||
'48' => ['48'],
|
||||
'83' => ['81', '86'],
|
||||
'44' => ['44'],
|
||||
];
|
||||
|
||||
/**
|
||||
* Steuerart-basiertes Drill-down-Mapping.
|
||||
* Welche steuerart-Werte liefern Buchungen für eine Kennzahl.
|
||||
*/
|
||||
private const KZ_STEUERART_MAP = [
|
||||
'41' => ['ig_lieferung'],
|
||||
'43' => ['export'],
|
||||
'44' => ['export'],
|
||||
'89' => ['ig_erwerb_19'],
|
||||
'91' => ['ig_erwerb_19'],
|
||||
'93' => ['ig_erwerb_7'],
|
||||
'61' => ['ig_erwerb_19', 'ig_erwerb_7'],
|
||||
'83' => ['ig_lieferung', 'export', 'ig_erwerb_19', 'ig_erwerb_7'],
|
||||
'94' => ['ig_erwerb_7'],
|
||||
'67' => ['ig_erwerb_19', 'ig_erwerb_7'],
|
||||
'66' => ['ig_erwerb_19', 'ig_erwerb_7'],
|
||||
'69' => ['ig_lieferung', 'export', 'ig_erwerb_19', 'ig_erwerb_7'],
|
||||
];
|
||||
|
||||
/**
|
||||
* Kennzahlen, deren Drill-down alle inländischen Ausgaben mit Vorsteuer enthält.
|
||||
* (buchungsbasiert: buchungstyp='Ausgabe', betrag_ust != 0, keine EU-Steuerarten)
|
||||
*/
|
||||
private const KZ_INCLUDES_VST = ['66', '83'];
|
||||
|
||||
public function calculate(int $year, int $month): array
|
||||
{
|
||||
// ── 1. Kontobasierte Kennzahlen (EU-Steuerarten ausgeschlossen) ──────────
|
||||
// ── 1. Kontobasierte Kennzahlen (EU-Steuerarten und Kz 61/62 ausgeschlossen) ──
|
||||
$euPlaceholders = implode(',', array_fill(0, count(self::EU_STEUERARTEN), '?'));
|
||||
$accountRows = Database::fetchAll(
|
||||
'SELECT a.ustva_kz,
|
||||
|
|
@ -85,6 +89,7 @@ class UstvaCalculator
|
|||
JOIN accounts a ON a.id = bk.kategorie_konto_id
|
||||
WHERE fy.year = ? AND MONTH(bk.buchungs_datum) = ?
|
||||
AND a.ustva_kz IS NOT NULL AND a.ustva_kz != \'\'
|
||||
AND a.ustva_kz NOT IN (\'61\', \'62\')
|
||||
AND (bk.steuerart IS NULL OR bk.steuerart NOT IN (' . $euPlaceholders . '))
|
||||
GROUP BY a.ustva_kz',
|
||||
array_merge([$year, $month], self::EU_STEUERARTEN)
|
||||
|
|
@ -98,19 +103,26 @@ class UstvaCalculator
|
|||
];
|
||||
}
|
||||
|
||||
// ── 2. Buchungsbasierte Vorsteuer Kz 66 (alle inländischen Eingangsrechnungen) ──
|
||||
$vstRow = Database::fetchOne(
|
||||
'SELECT SUM(bk.betrag_ust) AS summe_ust
|
||||
// ── 2. Buchungsbasierte Vorsteuer Kz 61/62 (via ust_satz) ────────────
|
||||
$vstRows = Database::fetchAll(
|
||||
'SELECT bk.ust_satz, SUM(bk.betrag_ust) AS summe_ust
|
||||
FROM bookings bk
|
||||
JOIN fiscal_years fy ON bk.fiscal_year_id = fy.id
|
||||
WHERE fy.year = ? AND MONTH(bk.buchungs_datum) = ?
|
||||
AND bk.buchungstyp = \'Ausgabe\'
|
||||
AND bk.betrag_ust != 0
|
||||
AND (bk.steuerart IS NULL OR bk.steuerart NOT IN (' . $euPlaceholders . '))',
|
||||
AND (bk.steuerart IS NULL OR bk.steuerart NOT IN (' . $euPlaceholders . '))
|
||||
GROUP BY bk.ust_satz',
|
||||
array_merge([$year, $month], self::EU_STEUERARTEN)
|
||||
);
|
||||
|
||||
$byKz['66'] = ['netto' => 0.0, 'ust' => (float)($vstRow['summe_ust'] ?? 0.0)];
|
||||
foreach ($vstRows as $row) {
|
||||
$satz = (int)round((float)$row['ust_satz']);
|
||||
$kzNr = self::VST_SATZ_KZ[$satz] ?? null;
|
||||
if ($kzNr !== null) {
|
||||
$byKz[$kzNr] = ['netto' => 0.0, 'ust' => (float)$row['summe_ust']];
|
||||
}
|
||||
}
|
||||
|
||||
// ── 3. Steuerart-basierte Kennzahlen ─────────────────────────────────
|
||||
$stRows = Database::fetchAll(
|
||||
|
|
@ -133,37 +145,38 @@ class UstvaCalculator
|
|||
$u = fn(string $kz): float => (float)($byKz[$kz]['ust'] ?? 0.0);
|
||||
$st = fn(string $art): float => (float)($bySteuerart[$art] ?? 0.0);
|
||||
|
||||
$kz81 = $n('81');
|
||||
$ust19 = $u('81'); // interne USt aus 19 %-Umsätzen (kein eigenständiges ELSTER-Kz)
|
||||
$kz86 = $n('86');
|
||||
$ust7 = $u('86'); // interne USt aus 7 %-Umsätzen (kein eigenständiges ELSTER-Kz)
|
||||
$kz81 = $n('81'); $kz83 = $u('81');
|
||||
$kz86 = $n('86'); $kz84 = $u('86');
|
||||
$kz35 = $n('35');
|
||||
$kz41 = $n('41') + $st('ig_lieferung');
|
||||
$kz43 = $n('43') + $st('export');
|
||||
$kz48 = $n('48');
|
||||
$kz44 = $n('44') + $st('export');
|
||||
$kz89 = $st('ig_erwerb_19');
|
||||
$kz91 = round($kz89 * 0.19, 2);
|
||||
$kz93 = $st('ig_erwerb_7');
|
||||
$kz66 = $u('66'); // § 14, 14a
|
||||
$kz61 = round($kz89 * 0.19, 2) + round($kz93 * 0.07, 2); // § 3d Satz 1
|
||||
$kz62 = 0.0; // Einfuhrumsatzsteuer n.v.
|
||||
$kz67 = 0.0; // § 13b-Vorsteuer n.v.
|
||||
// ig. Erwerb-USt gilt gleichzeitig als Output- und als Vorsteuer (kz61) → neutral
|
||||
$igErwerbUst = round($kz89 * 0.19, 2) + round($kz93 * 0.07, 2); // = kz61
|
||||
$kz83 = ($ust19 + $ust7 + $igErwerbUst) - ($kz66 + $kz61 + $kz62 + $kz67);
|
||||
$kz94 = round($kz93 * 0.07, 2);
|
||||
$kz61 = $u('61');
|
||||
$kz62 = $u('62');
|
||||
$kz67 = $kz91 + $kz94;
|
||||
$kz66 = $kz61 + $kz62 + $kz67;
|
||||
$kz69 = ($kz83 + $kz84 + $kz91 + $kz94) - $kz66;
|
||||
|
||||
return [
|
||||
'81' => ['name' => 'Steuerpfl. Umsätze 19 %', 'betrag' => round($kz81, 2), 'steuer' => null],
|
||||
'83' => ['name' => 'USt auf steuerpfl. Umsätze 19 %', 'betrag' => null, 'steuer' => round($kz83, 2)],
|
||||
'86' => ['name' => 'Steuerpfl. Umsätze 7 %', 'betrag' => round($kz86, 2), 'steuer' => null],
|
||||
'43' => ['name' => 'Steuerfreie Ausfuhren (§ 4 Nr. 1a)', 'betrag' => round($kz43, 2), 'steuer' => null],
|
||||
'48' => ['name' => 'Steuerfreie Umsätze ohne Vorsteuerabzug', 'betrag' => round($kz48, 2), 'steuer' => null],
|
||||
'84' => ['name' => 'USt auf steuerpfl. Umsätze 7 %', 'betrag' => null, 'steuer' => round($kz84, 2)],
|
||||
'35' => ['name' => 'Steuerfreie Umsätze (ohne Vorsteuerabzug)', 'betrag' => round($kz35, 2), 'steuer' => null],
|
||||
'41' => ['name' => 'Steuerfreie ig. Lieferungen (§ 4 Nr. 1b)', 'betrag' => round($kz41, 2), 'steuer' => null],
|
||||
'44' => ['name' => 'Steuerfreie Ausfuhren (§ 4 Nr. 1a)', 'betrag' => round($kz44, 2), 'steuer' => null],
|
||||
'89' => ['name' => 'Ig. Erwerb 19 % – Bemessungsgrundlage', 'betrag' => round($kz89, 2), 'steuer' => null],
|
||||
'91' => ['name' => 'USt auf ig. Erwerb 19 %', 'betrag' => null, 'steuer' => round($kz91, 2)],
|
||||
'93' => ['name' => 'Ig. Erwerb 7 % – Bemessungsgrundlage', 'betrag' => round($kz93, 2), 'steuer' => null],
|
||||
'66' => ['name' => 'Vorsteuer aus Lieferungen/Leistungen (§§ 14, 14a)', 'betrag' => null, 'steuer' => round($kz66, 2)],
|
||||
'61' => ['name' => 'Vorsteuer aus ig. Erwerben (§ 3d UStG)', 'betrag' => null, 'steuer' => round($kz61, 2)],
|
||||
'62' => ['name' => 'Vorsteuer aus Einfuhrumsatzsteuer', 'betrag' => null, 'steuer' => round($kz62, 2)],
|
||||
'67' => ['name' => 'Vorsteuer aus Leistungen nach § 13b UStG', 'betrag' => null, 'steuer' => round($kz67, 2)],
|
||||
'83' => ['name' => 'Verbleibende Umsatzsteuer-Vorauszahlung / Überschuss', 'betrag' => null, 'steuer' => round($kz83, 2)],
|
||||
'_ust' => ['name' => 'Umsatzsteuer gesamt (intern)', 'betrag' => null, 'steuer' => round($ust19 + $ust7, 2)],
|
||||
'94' => ['name' => 'USt auf ig. Erwerb 7 %', 'betrag' => null, 'steuer' => round($kz94, 2)],
|
||||
'61' => ['name' => 'Vorsteuer aus Eingangsrechnungen 19 %', 'betrag' => null, 'steuer' => round($kz61, 2)],
|
||||
'62' => ['name' => 'Vorsteuer aus Eingangsrechnungen 7 %', 'betrag' => null, 'steuer' => round($kz62, 2)],
|
||||
'67' => ['name' => 'Vorsteuer aus ig. Erwerb', 'betrag' => null, 'steuer' => round($kz67, 2)],
|
||||
'66' => ['name' => 'Vorsteuerbeträge gesamt', 'betrag' => null, 'steuer' => round($kz66, 2)],
|
||||
'69' => ['name' => 'Verbleibende USt-Vorauszahlung/Erstattung', 'betrag' => null, 'steuer' => round($kz69, 2)],
|
||||
];
|
||||
}
|
||||
|
||||
|
|
@ -175,9 +188,11 @@ class UstvaCalculator
|
|||
{
|
||||
$accountKzList = self::KZ_ACCOUNT_MAP[$kz] ?? [];
|
||||
$steuerartList = self::KZ_STEUERART_MAP[$kz] ?? [];
|
||||
$includesVst = in_array($kz, self::KZ_INCLUDES_VST, true);
|
||||
|
||||
if (empty($accountKzList) && empty($steuerartList) && !$includesVst) {
|
||||
// Kz 61/62: buchungsbasiert via ust_satz
|
||||
$vstSatz = array_search($kz, self::VST_SATZ_KZ, true);
|
||||
|
||||
if (empty($accountKzList) && empty($steuerartList) && $vstSatz === false) {
|
||||
return [];
|
||||
}
|
||||
|
||||
|
|
@ -211,14 +226,16 @@ class UstvaCalculator
|
|||
);
|
||||
}
|
||||
|
||||
if ($includesVst) {
|
||||
if ($vstSatz !== false) {
|
||||
$euPh = implode(',', array_fill(0, count(self::EU_STEUERARTEN), '?'));
|
||||
$parts[] = $select
|
||||
. ' AND bk.buchungstyp = \'Ausgabe\''
|
||||
. ' AND bk.betrag_ust != 0'
|
||||
. ' AND ROUND(bk.ust_satz) = ?'
|
||||
. ' AND (bk.steuerart IS NULL OR bk.steuerart NOT IN (' . $euPh . '))';
|
||||
$params = array_merge($params,
|
||||
[$year, $month],
|
||||
[$vstSatz],
|
||||
self::EU_STEUERARTEN
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,10 +3,10 @@ use Steuerkram\View;
|
|||
$h = fn($v) => View::h($v);
|
||||
$fmtKz = fn($v) => number_format((float)$v, 2, ',', '.') . ' €';
|
||||
|
||||
// Für Kz 83: vorzeichenbehafteter USt-Beitrag zur Zahllast.
|
||||
// Für Kz 69: vorzeichenbehafteter USt-Beitrag zur Zahllast.
|
||||
// Einnahmen mit USt = positiv, Ausgaben mit Vorsteuer = negativ,
|
||||
// ig_erwerb heben sich gegenseitig auf → 0.
|
||||
$ustBeitragKz83 = function(array $b): float {
|
||||
$ustBeitragKz69 = function(array $b): float {
|
||||
$steuerart = $b['steuerart'] ?? 'normal';
|
||||
$ustSatz = (float)$b['ust_satz'];
|
||||
$betragUst = (float)$b['betrag_ust'];
|
||||
|
|
@ -16,7 +16,7 @@ $ustBeitragKz83 = function(array $b): float {
|
|||
if ($b['buchungstyp'] === 'Ausgabe' && $steuerart === 'normal' && $betragUst != 0) {
|
||||
return -$betragUst; // Vorsteuer → Vorzeichen umkehren
|
||||
}
|
||||
return 0.0; // steuerfreie Umsätze und ig_erwerb (Netto-Beitrag 0, da neutral)
|
||||
return 0.0; // steuerfreie Umsätze und ig_erwerb (KZ91-KZ94 = KZ67, Netto-Beitrag 0)
|
||||
};
|
||||
|
||||
// Ausgaben werden als negative Beträge angezeigt (Geld fließt ab).
|
||||
|
|
@ -49,7 +49,7 @@ $signedBetrag = fn(array $b, string $field): float =>
|
|||
<th>Kategorie</th>
|
||||
<th>Zahlungskonto</th>
|
||||
<th class="td-right">Netto</th>
|
||||
<th class="td-right"><?= $kz === '83' ? 'USt-Beitrag' : 'USt' ?></th>
|
||||
<th class="td-right"><?= $kz === '69' ? 'USt-Beitrag' : 'USt' ?></th>
|
||||
<th class="td-right">Brutto</th>
|
||||
<th>Anlage</th>
|
||||
</tr>
|
||||
|
|
@ -60,7 +60,7 @@ $signedBetrag = fn(array $b, string $field): float =>
|
|||
$sumUst = 0.0;
|
||||
$sumBrutto = 0.0;
|
||||
foreach ($bookings as $b):
|
||||
$ust = $kz === '83' ? $ustBeitragKz83($b) : $signedBetrag($b, 'betrag_ust');
|
||||
$ust = $kz === '69' ? $ustBeitragKz69($b) : $signedBetrag($b, 'betrag_ust');
|
||||
$netto = $signedBetrag($b, 'betrag_netto');
|
||||
$brutto = $signedBetrag($b, 'betrag_brutto');
|
||||
$sumNetto += $netto;
|
||||
|
|
|
|||
|
|
@ -30,10 +30,11 @@ $fmtKz = fn($v) => $v !== null ? number_format((float)$v, 2, ',', '.') . ' €'
|
|||
<tbody>
|
||||
<?php
|
||||
$sections = [
|
||||
'Lieferungen und sonstige Leistungen' => ['81', '86', '43', '48', '41'],
|
||||
'Innergemeinschaftlicher Erwerb' => ['89', '93'],
|
||||
'Abziehbare Vorsteuerbeträge' => ['66', '61', '62', '67'],
|
||||
'Verbleibende Zahllast' => ['83'],
|
||||
'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):
|
||||
?>
|
||||
|
|
@ -45,7 +46,7 @@ $fmtKz = fn($v) => $v !== null ? number_format((float)$v, 2, ',', '.') . ' €'
|
|||
<?php foreach ($kzNrs as $nr):
|
||||
if (!isset($kz[$nr])) continue;
|
||||
$field = $kz[$nr];
|
||||
$isResult = in_array($nr, ['83']);
|
||||
$isResult = in_array($nr, ['69']);
|
||||
$isNegative = $isResult && ($field['steuer'] ?? 0) < 0;
|
||||
?>
|
||||
<tr style="<?= $isResult ? 'font-weight:700;background:#f0f4ff' : '' ?>">
|
||||
|
|
@ -108,16 +109,16 @@ $fmtKz = fn($v) => $v !== null ? number_format((float)$v, 2, ',', '.') . ' €'
|
|||
</tr>
|
||||
<tr>
|
||||
<td style="padding:.35rem 0;color:var(--color-muted)">USt gesamt</td>
|
||||
<td class="td-right td-mono"><?= $fmtKz($kz['_ust']['steuer'] ?? 0) ?></td>
|
||||
<td class="td-right td-mono"><?= $fmtKz(($kz['83']['steuer'] ?? 0) + ($kz['84']['steuer'] ?? 0)) ?></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding:.35rem 0;color:var(--color-muted)">Vorsteuer</td>
|
||||
<td class="td-right td-mono" style="color:var(--color-success)">– <?= $fmtKz($kz['66']['steuer'] ?? 0) ?></td>
|
||||
</tr>
|
||||
<tr style="border-top:2px solid #000;font-weight:700">
|
||||
<td style="padding:.5rem 0"><?= ($kz['83']['steuer'] ?? 0) >= 0 ? 'Vorauszahlung' : 'Erstattung' ?></td>
|
||||
<td class="td-right td-mono" style="color:<?= ($kz['83']['steuer'] ?? 0) >= 0 ? 'var(--color-danger)' : 'var(--color-success)' ?>">
|
||||
<?= $fmtKz($kz['83']['steuer'] ?? 0) ?>
|
||||
<td style="padding:.5rem 0"><?= ($kz['69']['steuer'] ?? 0) >= 0 ? 'Vorauszahlung' : 'Erstattung' ?></td>
|
||||
<td class="td-right td-mono" style="color:<?= ($kz['69']['steuer'] ?? 0) >= 0 ? 'var(--color-danger)' : 'var(--color-success)' ?>">
|
||||
<?= $fmtKz($kz['69']['steuer'] ?? 0) ?>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
|
|
|||
|
|
@ -111,15 +111,30 @@ class TaxSummenTest extends TestCase
|
|||
'UStVA Kz 81 (Umsätze 19%): Betrag enthält Einnahme ≥ 500 €'
|
||||
);
|
||||
|
||||
// Kz 66: reguläre Inlandsvorsteuer (§§ 14, 14a) – muss mindestens 38 € enthalten
|
||||
// Kz 83: USt auf Umsätze 19% – abgeleitet (19% von Kz81-Betrag)
|
||||
$this->assert(
|
||||
(float)$kz['66']['steuer'] >= 38.00,
|
||||
'UStVA Kz 66 (Vorsteuer §§ 14, 14a): Steuerbetrag enthält Ausgabe ≥ 38 €'
|
||||
(float)$kz['83']['steuer'] >= 95.00,
|
||||
'UStVA Kz 83 (USt 19%): Steuerbetrag ≥ 95 €'
|
||||
);
|
||||
|
||||
// Kz 83: Zahllast = _ust - Kz66 (USt gesamt minus Vorsteuer)
|
||||
$erwKz83 = round((float)$kz['_ust']['steuer'] - (float)$kz['66']['steuer'], 2);
|
||||
$this->assertEquals($erwKz83, (float)$kz['83']['steuer'], 'UStVA Kz 83 (Zahllast) korrekt berechnet');
|
||||
// Kz 61: Vorsteuer 19% – muss mindestens 38 € enthalten
|
||||
$this->assert(
|
||||
(float)$kz['61']['steuer'] >= 38.00,
|
||||
'UStVA Kz 61 (Vorsteuer 19%): Steuerbetrag enthält Ausgabe ≥ 38 €'
|
||||
);
|
||||
|
||||
// Kz 66: Vorsteuer gesamt – muss ≥ Kz 61 sein
|
||||
$this->assert(
|
||||
(float)$kz['66']['steuer'] >= (float)$kz['61']['steuer'],
|
||||
'UStVA Kz 66 (Vorsteuer gesamt): ≥ Kz 61'
|
||||
);
|
||||
|
||||
// Kz 69 Berechnung: USt gesamt - Vorsteuer gesamt
|
||||
$erwKz69 = round(
|
||||
((float)$kz['83']['steuer'] + (float)$kz['84']['steuer']) - (float)$kz['66']['steuer'],
|
||||
2
|
||||
);
|
||||
$this->assertEquals($erwKz69, (float)$kz['69']['steuer'], 'UStVA Kz 69 (Zahllast) korrekt berechnet');
|
||||
|
||||
// ── EÜR prüfen ────────────────────────────────────────────────────
|
||||
$euerCalc = new \Steuerkram\Services\EuerCalculator();
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ 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, 43, 89, 93 und 61 einfließen.
|
||||
* 41, 44, 89, 91, 93, 94 und 67 einfließen.
|
||||
*/
|
||||
class UstvaAuslandTest extends TestCase
|
||||
{
|
||||
|
|
@ -36,11 +36,6 @@ class UstvaAuslandTest extends TestCase
|
|||
);
|
||||
$bankId = $bankId ? (int)$bankId['id'] : $zahlungId;
|
||||
|
||||
// Baseline Kz83 VOR den Test-Buchungen (reale Buchungen im laufenden Monat)
|
||||
$calcPre = new \Steuerkram\Services\UstvaCalculator();
|
||||
$kzPre = $calcPre->calculate($year, $month);
|
||||
$kz83Pre = (float)$kzPre['83']['steuer'];
|
||||
|
||||
// ── 1. Steuerfreie ig. Lieferung → Kz 41 ──────────────────────────
|
||||
$belegIgL = \Steuerkram\Models\Beleg::create([
|
||||
'beleg_nr' => 'TEST-IGL-' . uniqid(),
|
||||
|
|
@ -63,7 +58,7 @@ class UstvaAuslandTest extends TestCase
|
|||
'buchungstext' => 'Test ig. Lieferung EU',
|
||||
]);
|
||||
|
||||
// ── 2. Steuerfreie Ausfuhr → Kz 43 ───────────────────────────────
|
||||
// ── 2. Steuerfreie Ausfuhr → Kz 44 ───────────────────────────────
|
||||
$belegExp = \Steuerkram\Models\Beleg::create([
|
||||
'beleg_nr' => 'TEST-EXP-' . uniqid(),
|
||||
'filename' => 'test-exp.pdf',
|
||||
|
|
@ -85,7 +80,7 @@ class UstvaAuslandTest extends TestCase
|
|||
'buchungstext' => 'Test Ausfuhr Drittland',
|
||||
]);
|
||||
|
||||
// ── 3. Ig. Erwerb 19 % → Kz 89 (Bemessungsgrundlage), Kz 61 (Vorsteuer) ──
|
||||
// ── 3. Ig. Erwerb 19 % → Kz 89/91/67 ─────────────────────────────
|
||||
$belegIgE19 = \Steuerkram\Models\Beleg::create([
|
||||
'beleg_nr' => 'TEST-IGE19-' . uniqid(),
|
||||
'filename' => 'test-ige19.pdf',
|
||||
|
|
@ -107,7 +102,7 @@ class UstvaAuslandTest extends TestCase
|
|||
'buchungstext' => 'Test ig. Erwerb 19%',
|
||||
]);
|
||||
|
||||
// ── 4. Ig. Erwerb 7 % → Kz 93 (Bemessungsgrundlage), Kz 61 (Vorsteuer) ───
|
||||
// ── 4. Ig. Erwerb 7 % → Kz 93/94/67 ──────────────────────────────
|
||||
$belegIgE7 = \Steuerkram\Models\Beleg::create([
|
||||
'beleg_nr' => 'TEST-IGE7-' . uniqid(),
|
||||
'filename' => 'test-ige7.pdf',
|
||||
|
|
@ -139,10 +134,10 @@ class UstvaAuslandTest extends TestCase
|
|||
'Kz 41 (ig. Lieferung): Betrag ≥ 1.000 €'
|
||||
);
|
||||
|
||||
// Kz 43: Ausfuhr 2000 €
|
||||
// Kz 44: Ausfuhr 2000 €
|
||||
$this->assert(
|
||||
(float)$kz['43']['betrag'] >= 2000.00,
|
||||
'Kz 43 (Ausfuhr): Betrag ≥ 2.000 €'
|
||||
(float)$kz['44']['betrag'] >= 2000.00,
|
||||
'Kz 44 (Ausfuhr): Betrag ≥ 2.000 €'
|
||||
);
|
||||
|
||||
// Kz 89: ig. Erwerb 19% Bemessungsgrundlage 500 €
|
||||
|
|
@ -151,33 +146,52 @@ class UstvaAuslandTest extends TestCase
|
|||
'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 61: Vorsteuer aus ig. Erwerben (§ 3d) = 500*0.19 + 300*0.07 = 95 + 21 = 116 €
|
||||
$erwartetKz61 = round(500.00 * 0.19, 2) + round(300.00 * 0.07, 2);
|
||||
// Kz 94: USt auf ig. Erwerb 7% = 300 * 0.07 = 21 €
|
||||
$this->assertEquals(
|
||||
$erwartetKz61,
|
||||
(float)$kz['61']['steuer'],
|
||||
'Kz 61 (Vorsteuer ig. Erwerb § 3d): Steuer = 116,00 €'
|
||||
round(300.00 * 0.07, 2),
|
||||
(float)$kz['94']['steuer'],
|
||||
'Kz 94 (USt ig. Erwerb 7%): Steuer = 21,00 €'
|
||||
);
|
||||
|
||||
// Kz 83 (Zahllast): ig. Erwerb/Lieferung/Ausfuhr sind USt-neutral (betrag_ust=0)
|
||||
// → Zahllast darf sich gegenüber Baseline nicht verändert haben
|
||||
// Kz 67: Vorsteuer aus ig. Erwerb = Kz 91 + Kz 94 = 116 €
|
||||
$erwartetKz67 = round((float)$kz['91']['steuer'] + (float)$kz['94']['steuer'], 2);
|
||||
$this->assertEquals(
|
||||
round($kz83Pre, 2),
|
||||
(float)$kz['83']['steuer'],
|
||||
'Kz 83 (Zahllast) unverändert – alle EU-Buchungen sind USt-neutral'
|
||||
$erwartetKz67,
|
||||
(float)$kz['67']['steuer'],
|
||||
'Kz 67 (Vorsteuer ig. Erwerb) = Kz 91 + Kz 94'
|
||||
);
|
||||
|
||||
// ig. Erwerb darf NICHT in Kz 48 landen (steuerfreie Umsätze ohne VSt-Abzug)
|
||||
// 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['48']['betrag'],
|
||||
'Kz 48 (steuerfreie Inlandsumsätze) = 0 (kein steuerfreier Inlandsumsatz gebucht)'
|
||||
(float)$kz['35']['betrag'],
|
||||
'Kz 35 (steuerfreie Inlandsumsätze) = 0 (kein normaler 0%-Umsatz gebucht)'
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -6,33 +6,29 @@ use Steuerkram\Services\ElsterXmlService;
|
|||
|
||||
class ElsterXmlKz39Test extends TestCase
|
||||
{
|
||||
protected string $testName = 'Test 13: UStVA XML – Kz83 Vorauszahlung/Überschuss';
|
||||
protected string $testName = 'Test 13: UStVA XML – Kz39 Vorzeichen-Validierung';
|
||||
|
||||
public function run(): void
|
||||
{
|
||||
$service = new ElsterXmlService();
|
||||
|
||||
// Fall 1: Zahllast positiv → Kz83 muss vorhanden und positiv sein
|
||||
$kzZahllast = ['83' => ['steuer' => 150.00]];
|
||||
// Fall 1: Zahllast positiv → Kz39 muss vorhanden und positiv sein
|
||||
$kzZahllast = ['69' => ['steuer' => 150.00]];
|
||||
$xml = $service->generate(2026, 1, $kzZahllast);
|
||||
$this->assert(str_contains($xml, '<Kz83>'), 'Kz83 bei Zahllast vorhanden');
|
||||
$this->assert(str_contains($xml, '<Kz83>150.00</Kz83>'), 'Kz83 enthält korrekten Betrag bei Zahllast');
|
||||
$this->assert(str_contains($xml, '<Kz39>'), 'Kz39 bei Zahllast vorhanden');
|
||||
$this->assert(str_contains($xml, '<Kz39>150.00</Kz39>'), 'Kz39 ohne Vorzeichen bei Zahllast');
|
||||
|
||||
// Fall 2: Erstattung (negativ) → Kz83 muss vorhanden sein (Überschuss)
|
||||
$kzErstattung = ['83' => ['steuer' => -50.00]];
|
||||
// Fall 2: Erstattung (negativ) → Kz39 darf nicht vorkommen
|
||||
$kzErstattung = ['69' => ['steuer' => -50.00]];
|
||||
$xml = $service->generate(2026, 1, $kzErstattung);
|
||||
$this->assert(str_contains($xml, '<Kz83>'), 'Kz83 bei Erstattung vorhanden');
|
||||
$this->assert(str_contains($xml, '<Kz83>-50.00</Kz83>'), 'Kz83 enthält negativen Betrag bei Erstattung');
|
||||
$this->assert(!str_contains($xml, '<Kz39>'), 'Kz39 bei Erstattung nicht vorhanden');
|
||||
|
||||
// Fall 3: Zahllast = 0 → Kz83 weglassen
|
||||
$kzNull = ['83' => ['steuer' => 0.0]];
|
||||
// Fall 3: Zahllast = 0 → Kz39 weglassen
|
||||
$kzNull = ['69' => ['steuer' => 0.0]];
|
||||
$xml = $service->generate(2026, 1, $kzNull);
|
||||
$this->assert(!str_contains($xml, '<Kz83>'), 'Kz83 bei Nullbetrag nicht vorhanden');
|
||||
$this->assert(!str_contains($xml, '<Kz39>'), 'Kz39 bei Nullbetrag nicht vorhanden');
|
||||
|
||||
// Fall 4: Kz39 (Sondervorauszahlung) darf NICHT mehr als Zahllast exportiert werden
|
||||
$this->assert(!str_contains($xml, '<Kz39>'), 'Kz39 (Sondervorauszahlung) wird nicht exportiert');
|
||||
|
||||
// Fall 5: XML ist valides XML
|
||||
// Fall 4: XML ist valides XML
|
||||
$doc = new \DOMDocument();
|
||||
$loaded = @$doc->loadXML($xml);
|
||||
$this->assert($loaded !== false, 'Generiertes XML ist wohlgeformt');
|
||||
|
|
|
|||
|
|
@ -93,12 +93,12 @@ class StornoBuchungTest extends TestCase
|
|||
);
|
||||
|
||||
// ── UStVA: Vorsteuer-KZ hebt sich auf ─────────────────────────
|
||||
// Kz 66 enthält alle inländischen Ausgaben mit Vorsteuer (buchungsbasiert).
|
||||
// Kz 61 enthält Vorsteuer aus dem Ausgabe-Konto (ustva_kz='61').
|
||||
// Original: +19, Storno: -19 → Summe = 0 (oder weniger als andere Tests einbringen).
|
||||
// Da weitere Tests parallel laufen können, prüfen wir nur, dass die Storno-Buchung
|
||||
// als eigenständige Zeile in fetchBookingsForKz erscheint.
|
||||
$ustvaCalc = new \Steuerkram\Services\UstvaCalculator();
|
||||
$bookings = $ustvaCalc->fetchBookingsForKz($year, $month, '66');
|
||||
$bookings = $ustvaCalc->fetchBookingsForKz($year, $month, '61');
|
||||
|
||||
$reversalFound = false;
|
||||
foreach ($bookings as $b) {
|
||||
|
|
@ -109,6 +109,6 @@ class StornoBuchungTest extends TestCase
|
|||
break;
|
||||
}
|
||||
}
|
||||
$this->assert($reversalFound, 'UStVA-Drilldown Kz 66: Stornobuchung erscheint in Detailansicht');
|
||||
$this->assert($reversalFound, 'UStVA-Drilldown Kz 61: Stornobuchung erscheint in Detailansicht');
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue