diff --git a/BERICHTE.md b/BERICHTE.md index 80bd97f..932c133 100644 --- a/BERICHTE.md +++ b/BERICHTE.md @@ -41,36 +41,29 @@ Die EÜR basiert auf dem Feld `accounts.kontenart` sowie `accounts.euer_zeile` d **Datei:** `src/Services/UstvaCalculator.php` -Die UStVA basiert ausschließlich auf dem Feld `accounts.ustva_kz` des Kategorie-Kontos einer Buchung. Die Felder `buchungstyp` und `steuerart` werden nicht ausgewertet. Stornobuchungen (`is_reversal = 1`) werden ausgeschlossen. +Die UStVA kombiniert zwei Quellen (Migration 022): -### Berechnungslogik +**Quelle 1 – kontobasiert** (`accounts.ustva_kz`): reguläre Umsätze und Vorsteuer. Buchungen mit EU-Steuerarten (s.u.) werden hier ausgeschlossen. -Pro Monat wird eine einzige Aggregationsabfrage ausgeführt, die `betrag_netto` und `betrag_ust` nach `ustva_kz` des Kategorie-Kontos gruppiert: - -```sql -SELECT a.ustva_kz, SUM(betrag_netto), SUM(betrag_ust) -FROM bookings JOIN accounts a ON a.id = kategorie_konto_id -WHERE ... AND a.ustva_kz IS NOT NULL -GROUP BY a.ustva_kz -``` +**Quelle 2 – 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 | Bezeichnung | Quelle | Relevante Konten (Beispiele) | +| Kz | Bezeichnung | Quelle | Erläuterung | |---|---|---|---| -| **81** | Steuerpfl. Umsätze 19 % | `SUM(betrag_netto)` WHERE `ustva_kz='81'` | 8000, 8400, 8510, 8570, 8800, 8820, 8922 | +| **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 | +| **86** | Steuerpfl. Umsätze 7 % | `SUM(betrag_netto)` WHERE `ustva_kz='86'` | 8300, 8710, 8921 | | **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, 8160 | -| **41** | Steuerfreie ig. Lieferungen | `SUM(betrag_netto)` WHERE `ustva_kz='41'` | 8120, 8125, 8130 | -| **44** | Steuerfreie Ausfuhren | `SUM(betrag_netto)` WHERE `ustva_kz='44'` | 8150 | -| **61** | Vorsteuer 19 % | `SUM(betrag_ust)` WHERE `ustva_kz='61'` | alle Betriebsausgabe-Konten mit `steuercode='VSt19'` (z.B. 3100, 4200ff, 4900ff) | -| **62** | Vorsteuer 7 % | `SUM(betrag_ust)` WHERE `ustva_kz='62'` | alle Betriebsausgabe-Konten mit `steuercode='VSt7'` (z.B. 3110, 4610, 4680) | -| **89** | Ig. Erwerb 19 % – Bemessungsgrundlage | `SUM(betrag_netto)` WHERE `ustva_kz='89'` | 3425 | -| **91** | USt auf ig. Erwerb 19 % | `SUM(betrag_ust)` WHERE `ustva_kz='89'` | 3425 | -| **93** | Ig. Erwerb 7 % – Bemessungsgrundlage | `SUM(betrag_netto)` WHERE `ustva_kz='93'` | 3420 | -| **94** | USt auf ig. Erwerb 7 % | `SUM(betrag_ust)` WHERE `ustva_kz='93'` | 3420 | +| **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 `ustva_kz='61'` | alle Betriebsausgabe-Konten mit `steuercode='VSt19'` (Migration 022: früher kz=66) | +| **62** | Vorsteuer 7 % | `SUM(betrag_ust)` WHERE `ustva_kz='62'` | alle Betriebsausgabe-Konten mit `steuercode='VSt7'` (Migration 022: früher kz=66) | +| **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 | +| **94** | USt auf ig. Erwerb 7 % | `Kz 93 × 0,07` | berechnet | ### Abgeleitete Kennzahlen @@ -80,11 +73,10 @@ GROUP BY a.ustva_kz | **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 | -### Neue Konten in Klasse 3 (Migration 010) +### Migration 022 – Vorsteuer-Kz-Aufteilung -| Konto | Bezeichnung | ustva_kz | -|---|---|---| -| 3420 | Innergemeinschaftliche Erwerbe 7 % Vorsteuer | 93 | -| 3425 | Innergemeinschaftliche Erwerbe 19 % Vorsteuer | 89 | +Vor Migration 022 trugen alle Vorsteuer-Konten `ustva_kz='66'`, was dazu führte, dass Kz 61, 62 und 66 stets 0 ergaben. Migration 022 setzt: +- `steuercode='VSt19'` → `ustva_kz='61'` +- `steuercode='VSt7'` → `ustva_kz='62'` -Buchungen auf ig. Erwerb müssen auf Konto 3420 bzw. 3425 als Kategorie-Konto erfolgen. +Kz 66 wird weiterhin rechnerisch als `Kz 61 + Kz 62 + Kz 67` aggregiert. diff --git a/Changes.md b/Changes.md index 84c7665..6aa5117 100644 --- a/Changes.md +++ b/Changes.md @@ -2,6 +2,16 @@ --- +## 2026-04-10 – UStVA Vorsteuer-Kz-Aufteilung (Migration 022) + +### Fix +- Migration 022: Vorsteuer-Konten von `ustva_kz='66'` auf `'61'` (VSt19) bzw. `'62'` (VSt7) aufgeteilt – Kz 61, 62 und 66 wurden zuvor stets als 0 ausgewiesen +- `UstvaCalculator`: zweite Abfrage für steuerart-basierte EU-Kennzahlen (ig. Lieferung → Kz 41, Ausfuhr → Kz 44, ig. Erwerb → Kz 89/91/93/94); verhindert Doppelzählung gegenüber kontobasierter Berechnung +- `fetchBookingsForKz`: UNION-Abfrage aus Konto-Kz und steuerart für korrekten Drill-down +- Tests 4, 5, 6, 9: Kontonummern auf tatsächliche SKR03-Nummern korrigiert (8000→8400, 1400→1200) + +--- + ## 2026-04-10 – Upload-Limit auf 20 MB erhöht ### Geändert diff --git a/migrations/022_vorsteuer_kz_split.sql b/migrations/022_vorsteuer_kz_split.sql new file mode 100644 index 0000000..86cd850 --- /dev/null +++ b/migrations/022_vorsteuer_kz_split.sql @@ -0,0 +1,17 @@ +-- Migration 022: Vorsteuer-Konten ustva_kz von '66' auf '61' (VSt19) bzw. '62' (VSt7) aufteilen. +-- +-- Hintergrund: Der UstvaCalculator berechnet Kz 66 (Vorsteuer gesamt) als Summe +-- aus Kz 61 + Kz 62 + Kz 67. Alle Vorsteuer-Konten hatten bisher ustva_kz='66', +-- was dazu führte, dass Kz 61, 62 und damit auch 66 stets 0 ergaben. +-- Nach dieser Migration tragen Konten mit VSt19-Steuercode zu Kz 61 bei, +-- Konten mit VSt7 zu Kz 62. Kz 66 wird weiterhin rechnerisch aggregiert. + +UPDATE accounts +SET ustva_kz = '61' +WHERE ustva_kz = '66' + AND steuercode = 'VSt19'; + +UPDATE accounts +SET ustva_kz = '62' +WHERE ustva_kz = '66' + AND steuercode = 'VSt7'; diff --git a/src/Services/UstvaCalculator.php b/src/Services/UstvaCalculator.php index eece441..4993548 100644 --- a/src/Services/UstvaCalculator.php +++ b/src/Services/UstvaCalculator.php @@ -9,28 +9,38 @@ use Steuerkram\Database; /** * Berechnet alle ELSTER-Kennzahlen (Kz) für die Umsatzsteuervoranmeldung. * - * Maßgeblich ist ausschließlich das Feld accounts.ustva_kz des Kategorie-Kontos. + * Zwei Quellen fließen in die Berechnung ein: * - * Kontozuordnung: - * 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) - * ustva_kz = '44' → Kz 44 (betrag_netto) - * ustva_kz = '61' → Kz 61 (betrag_ust) [Betriebsausgabe-Konten mit VSt19] - * ustva_kz = '62' → Kz 62 (betrag_ust) [Betriebsausgabe-Konten mit VSt7] - * ustva_kz = '89' → Kz 89 (betrag_netto), Kz 91 (betrag_ust) [ig. Erwerb 19 %] - * ustva_kz = '93' → Kz 93 (betrag_netto), Kz 94 (betrag_ust) [ig. Erwerb 7 %] + * 1. Kontobasiert (accounts.ustva_kz des Kategorie-Kontos): + * 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 = '44' → Kz 44 (betrag_netto) [steuerfreie Ausfuhren via Konto] + * ustva_kz = '61' → Kz 61 (betrag_ust) [Vorsteuer 19 %, VSt19-Konten] + * ustva_kz = '62' → Kz 62 (betrag_ust) [Vorsteuer 7 %, VSt7-Konten] + * + * 2. Steuerart-basiert (bookings.steuerart, unabhängig vom Konto): + * steuerart = 'ig_lieferung' → Kz 41 (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. * * Abgeleitete Kennzahlen: - * 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) + * 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']; + /** - * Welche accounts.ustva_kz-Werte liefern Buchungen für die Drill-down-Ansicht einer Kennzahl. + * 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. */ private const KZ_ACCOUNT_MAP = [ '81' => ['81'], @@ -42,18 +52,28 @@ class UstvaCalculator '44' => ['44'], '61' => ['61'], '62' => ['62'], - '89' => ['89'], - '91' => ['89'], - '93' => ['93'], - '94' => ['93'], - '67' => ['89', '93'], - '66' => ['61', '62', '89', '93'], - '69' => ['81', '86', '35', '41', '44', '61', '62', '89', '93'], + ]; + + /** + * Welche steuerart-Werte liefern Buchungen für eine Kennzahl. + */ + private const KZ_STEUERART_MAP = [ + '41' => ['ig_lieferung'], + '44' => ['export'], + '89' => ['ig_erwerb_19'], + '91' => ['ig_erwerb_19'], + '93' => ['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'], ]; public function calculate(int $year, int $month): array { - $rows = Database::fetchAll( + // ── 1. Kontobasierte Kennzahlen (EU-Steuerarten ausgeschlossen) ────── + $euPlaceholders = implode(',', array_fill(0, count(self::EU_STEUERARTEN), '?')); + $accountRows = Database::fetchAll( 'SELECT a.ustva_kz, SUM(bk.betrag_netto) AS summe_netto, SUM(bk.betrag_ust) AS summe_ust @@ -62,28 +82,49 @@ 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 (bk.steuerart IS NULL OR bk.steuerart NOT IN (' . $euPlaceholders . ')) GROUP BY a.ustva_kz', - [$year, $month] + array_merge([$year, $month], self::EU_STEUERARTEN) ); $byKz = []; - foreach ($rows as $row) { + foreach ($accountRows as $row) { $byKz[$row['ustva_kz']] = [ 'netto' => (float)$row['summe_netto'], 'ust' => (float)$row['summe_ust'], ]; } - $n = fn(string $kz): float => (float)($byKz[$kz]['netto'] ?? 0.0); - $u = fn(string $kz): float => (float)($byKz[$kz]['ust'] ?? 0.0); + // ── 2. Steuerart-basierte Kennzahlen ───────────────────────────────── + $stRows = Database::fetchAll( + 'SELECT bk.steuerart, SUM(bk.betrag_netto) AS summe_netto + FROM bookings bk + JOIN fiscal_years fy ON bk.fiscal_year_id = fy.id + WHERE fy.year = ? AND MONTH(bk.buchungs_datum) = ? + AND bk.steuerart IN (' . $euPlaceholders . ') + GROUP BY bk.steuerart', + array_merge([$year, $month], self::EU_STEUERARTEN) + ); + + $bySteuerart = []; + foreach ($stRows as $row) { + $bySteuerart[$row['steuerart']] = (float)$row['summe_netto']; + } + + // ── 3. Kennzahlen zusammensetzen ───────────────────────────────────── + $n = fn(string $kz): float => (float)($byKz[$kz]['netto'] ?? 0.0); + $u = fn(string $kz): float => (float)($byKz[$kz]['ust'] ?? 0.0); + $st = fn(string $art): float => (float)($bySteuerart[$art] ?? 0.0); $kz81 = $n('81'); $kz83 = $u('81'); $kz86 = $n('86'); $kz84 = $u('86'); $kz35 = $n('35'); - $kz41 = $n('41'); - $kz44 = $n('44'); - $kz89 = $n('89'); $kz91 = $u('89'); - $kz93 = $n('93'); $kz94 = $u('93'); + $kz41 = $n('41') + $st('ig_lieferung'); + $kz44 = $n('44') + $st('export'); + $kz89 = $st('ig_erwerb_19'); + $kz91 = round($kz89 * 0.19, 2); + $kz93 = $st('ig_erwerb_7'); + $kz94 = round($kz93 * 0.07, 2); $kz61 = $u('61'); $kz62 = $u('62'); $kz67 = $kz91 + $kz94; @@ -91,50 +132,74 @@ class UstvaCalculator $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], - '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], - '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)], + '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], + '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], + '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)], ]; } + /** + * Liefert alle Buchungszeilen, die zu einer Kennzahl beitragen (Drill-down). + * Kombiniert kontobasierte und steuerart-basierte Quellen per UNION. + */ public function fetchBookingsForKz(int $year, int $month, string $kz): array { - $accountKzList = self::KZ_ACCOUNT_MAP[$kz] ?? null; - if ($accountKzList === null) { + $accountKzList = self::KZ_ACCOUNT_MAP[$kz] ?? []; + $steuerartList = self::KZ_STEUERART_MAP[$kz] ?? []; + + if (empty($accountKzList) && empty($steuerartList)) { return []; } - $placeholders = implode(',', array_fill(0, count($accountKzList), '?')); - $params = array_merge([$year, $month], $accountKzList); + $select = 'SELECT bk.id, bk.buchungs_datum, bk.buchungstyp, bk.steuerart, bk.ust_satz, + bk.betrag_netto, bk.betrag_ust, bk.betrag_brutto, bk.buchungstext, + bk.is_reversal, bk.beleg_id, bl.beleg_nr, + k.kontonummer AS kategorie_nr, k.bezeichnung AS kategorie_name, + z.kontonummer AS zahlungs_nr, z.bezeichnung AS zahlungs_name + FROM bookings bk + JOIN fiscal_years fy ON bk.fiscal_year_id = fy.id + LEFT JOIN belege bl ON bk.beleg_id = bl.id + JOIN accounts k ON bk.kategorie_konto_id = k.id + JOIN accounts z ON bk.zahlungskonto_id = z.id + WHERE fy.year = ? AND MONTH(bk.buchungs_datum) = ?'; - return Database::fetchAll( - 'SELECT bk.id, bk.buchungs_datum, bk.buchungstyp, bk.steuerart, bk.ust_satz, - bk.betrag_netto, bk.betrag_ust, bk.betrag_brutto, bk.buchungstext, - bk.is_reversal, bk.beleg_id, bl.beleg_nr, - k.kontonummer AS kategorie_nr, k.bezeichnung AS kategorie_name, - z.kontonummer AS zahlungs_nr, z.bezeichnung AS zahlungs_name - FROM bookings bk - JOIN fiscal_years fy ON bk.fiscal_year_id = fy.id - LEFT JOIN belege bl ON bk.beleg_id = bl.id - JOIN accounts k ON bk.kategorie_konto_id = k.id - JOIN accounts z ON bk.zahlungskonto_id = z.id - WHERE fy.year = ? AND MONTH(bk.buchungs_datum) = ? - AND k.ustva_kz IN (' . $placeholders . ') - ORDER BY bk.buchungs_datum, bk.id', - $params - ); + $parts = []; + $params = []; + + if (!empty($accountKzList)) { + $ph = implode(',', array_fill(0, count($accountKzList), '?')); + $euPh = implode(',', array_fill(0, count(self::EU_STEUERARTEN), '?')); + $parts[] = $select + . ' AND k.ustva_kz IN (' . $ph . ')' + . ' AND (bk.steuerart IS NULL OR bk.steuerart NOT IN (' . $euPh . '))'; + $params = array_merge($params, + [$year, $month], + $accountKzList, + self::EU_STEUERARTEN + ); + } + + if (!empty($steuerartList)) { + $ph = implode(',', array_fill(0, count($steuerartList), '?')); + $parts[] = $select . ' AND bk.steuerart IN (' . $ph . ')'; + $params = array_merge($params, [$year, $month], $steuerartList); + } + + $sql = implode(' UNION ALL ', $parts) . ' ORDER BY buchungs_datum, id'; + + return Database::fetchAll($sql, $params); } } diff --git a/tests/04_InvoiceBuchungTest.php b/tests/04_InvoiceBuchungTest.php index f3b098c..56d3b1f 100644 --- a/tests/04_InvoiceBuchungTest.php +++ b/tests/04_InvoiceBuchungTest.php @@ -16,19 +16,19 @@ class InvoiceBuchungTest extends TestCase public function run(): void { // Benötigte Konten prüfen (SKR03) - $zahlungsId = $this->findAccountId('1400'); // Forderungen (Zahlungskonto) - $kategorieId = $this->findAccountId('8000'); // Erlöse 19 % (Betriebseinnahme) + $zahlungsId = $this->findAccountId('1200'); // Bank (Zahlungskonto) + $kategorieId = $this->findAccountId('8400'); // Erlöse 19 % USt (Betriebseinnahme) if (!$zahlungsId) { - $this->assert(false, 'Forderungskonto 1400 nicht gefunden – SKR03-Seed vorhanden?'); + $this->assert(false, 'Bankkonto 1200 nicht gefunden – SKR03-Seed vorhanden?'); return; } if (!$kategorieId) { - $this->assert(false, 'Erlöskonto 8000 nicht gefunden – SKR03-Seed vorhanden?'); + $this->assert(false, 'Erlöskonto 8400 nicht gefunden – SKR03-Seed vorhanden?'); return; } - $this->assert(true, 'Benötigte Konten (1400, 8000) vorhanden'); + $this->assert(true, 'Benötigte Konten (1200, 8400) vorhanden'); // Testkunden anlegen $customerId = \Steuerkram\Models\Customer::create([ diff --git a/tests/05_TaxSummenTest.php b/tests/05_TaxSummenTest.php index 9ca8afd..766d8f5 100644 --- a/tests/05_TaxSummenTest.php +++ b/tests/05_TaxSummenTest.php @@ -19,15 +19,15 @@ class TaxSummenTest extends TestCase $datum = date('Y-m-d'); // Benötigte Konten (SKR03) - $zahlungsId = $this->findAccountId('1400'); // Forderungen (Zahlungskonto) - $erloesId = $this->findAccountId('8000'); // Erlöse 19 % (Betriebseinnahme) + $zahlungsId = $this->findAccountId('1200'); // Bank (Zahlungskonto) + $erloesId = $this->findAccountId('8400'); // Erlöse 19 % USt (Betriebseinnahme, kz=81) $ausgabeId = $this->findAccountId('4920'); // Bürobedarf (Betriebsausgabe) $bankId = $this->findAccountId('1200'); // Bank (Zahlungskonto) - // Fallback auf beliebige Konten der passenden Art + // Fallback: explizit ein Konto mit ustva_kz=81 suchen, damit kz81/83 befüllt werden if (!$erloesId) { $row = \Steuerkram\Database::fetchOne( - "SELECT id FROM accounts WHERE kontenart = 'Betriebseinnahme' AND aktiv = 1 LIMIT 1" + "SELECT id FROM accounts WHERE kontenart = 'Betriebseinnahme' AND ustva_kz = '81' AND aktiv = 1 LIMIT 1" ); $erloesId = $row ? (int)$row['id'] : null; } diff --git a/tests/06_EuerBuchungsTest.php b/tests/06_EuerBuchungsTest.php index 8b70fbe..7e9c684 100644 --- a/tests/06_EuerBuchungsTest.php +++ b/tests/06_EuerBuchungsTest.php @@ -42,12 +42,12 @@ class EuerBuchungsTest extends TestCase ); // Konten laden - $erloesId = $this->findAccountId('8000'); // Betriebseinnahme - $ausgabeId = $this->findAccountId('4600'); // Bürobedarf (Betriebsausgabe) + $erloesId = $this->findAccountId('8400'); // Erlöse 19 % USt (Betriebseinnahme) + $ausgabeId = $this->findAccountId('4600'); // Werbekosten (Betriebsausgabe) $bankId = $this->findAccountId('1200'); // Bank (Zahlungskonto) if (!$erloesId || !$ausgabeId || !$bankId) { - $this->assert(false, 'Benötigte Konten (8000, 4600, 1200) nicht gefunden'); + $this->assert(false, 'Benötigte Konten (8400, 4600, 1200) nicht gefunden'); return; } diff --git a/tests/09_InvoiceToReportsTest.php b/tests/09_InvoiceToReportsTest.php index 3249b4e..c0e9c20 100644 --- a/tests/09_InvoiceToReportsTest.php +++ b/tests/09_InvoiceToReportsTest.php @@ -20,14 +20,14 @@ class InvoiceToReportsTest extends TestCase $datum = date('Y-m-d'); // ── Konten prüfen ──────────────────────────────────────────────────── - $zahlungsId = $this->findAccountId('1400'); // Forderungen - $erloes19Id = $this->findAccountId('8000'); // Erlöse 19 % + $zahlungsId = $this->findAccountId('1200'); // Bank + $erloes19Id = $this->findAccountId('8400'); // Erlöse 19 % USt if (!$zahlungsId || !$erloes19Id) { - $this->assert(false, 'Benötigte Konten (1400, 8000) nicht gefunden – SKR03-Seed vorhanden?'); + $this->assert(false, 'Benötigte Konten (1200, 8400) nicht gefunden – SKR03-Seed vorhanden?'); return; } - $this->assert(true, 'Konten 1400 und 8000 vorhanden'); + $this->assert(true, 'Konten 1200 und 8400 vorhanden'); // ── Kunde und Rechnung anlegen ──────────────────────────────────────── $customerId = \Steuerkram\Models\Customer::create([