diff --git a/BERICHTE.md b/BERICHTE.md index 932c133..b84bb83 100644 --- a/BERICHTE.md +++ b/BERICHTE.md @@ -41,11 +41,13 @@ Die EÜR basiert auf dem Feld `accounts.kontenart` sowie `accounts.euer_zeile` d **Datei:** `src/Services/UstvaCalculator.php` -Die UStVA kombiniert zwei Quellen (Migration 022): +Die UStVA kombiniert drei Quellen: -**Quelle 1 – kontobasiert** (`accounts.ustva_kz`): reguläre Umsätze und Vorsteuer. Buchungen mit EU-Steuerarten (s.u.) 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 – steuerartbasiert** (`bookings.steuerart`): EU-Sonderfälle (ig. Lieferung, Ausfuhr, ig. Erwerb). Diese Buchungen landen unabhängig vom Kategorie-Konto in den richtigen Kennzahlen. +**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 @@ -58,8 +60,8 @@ Die UStVA kombiniert zwei Quellen (Migration 022): | **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) | +| **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 | @@ -73,10 +75,12 @@ Die UStVA kombiniert zwei Quellen (Migration 022): | **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 | -### Migration 022 – Vorsteuer-Kz-Aufteilung +### Migration 022 – Vorsteuer-Kz-Aufteilung (historisch) -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'` +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. diff --git a/Changes.md b/Changes.md index 32422d7..ccdd5de 100644 --- a/Changes.md +++ b/Changes.md @@ -2,6 +2,15 @@ --- +## 2026-04-11 – Buchungsbasierte Vorsteuer Kz 61/62 in UStVA + +### Geändert +- `UstvaCalculator`: Kz 61 und 62 werden nicht mehr über `accounts.ustva_kz` geroutet, sondern direkt aus `bookings.ust_satz` ermittelt (`buchungstyp='Ausgabe'`, `betrag_ust != 0`). Damit reicht ein einziges Aufwandskonto für beide USt-Sätze – separate VSt19-/VSt7-Konten sind nicht mehr erforderlich +- `fetchBookingsForKz`: Drill-down für Kz 61/62 nutzt jetzt ebenfalls buchungsbasierte Filterung +- `BERICHTE.md`: Routing-Logik für Kz 61/62 dokumentiert + +--- + ## 2026-04-10 – Nextcloud-Belegimport via WebDAV ### Neu diff --git a/migrations/027_anlagevermögen_kontenart.sql b/migrations/027_anlagevermögen_kontenart.sql new file mode 100644 index 0000000..2d646f9 --- /dev/null +++ b/migrations/027_anlagevermögen_kontenart.sql @@ -0,0 +1,16 @@ +-- Migration 027: Kontenart-ENUM um Anlagevermögen erweitern und Konten 0001–0480 zuweisen + +ALTER TABLE accounts + MODIFY COLUMN kontenart ENUM( + 'Betriebseinnahme', + 'Betriebsausgabe', + 'Anlagevermögen', + 'Zahlungskonto', + 'Verbindlichkeit', + 'Privat' + ) NOT NULL; + +UPDATE accounts +SET kontenart = 'Anlagevermögen' +WHERE CAST(kontonummer AS UNSIGNED) <= 480 + AND kontonummer REGEXP '^[0-9]+$'; diff --git a/src/Services/UstvaCalculator.php b/src/Services/UstvaCalculator.php index 4993548..4d1aa04 100644 --- a/src/Services/UstvaCalculator.php +++ b/src/Services/UstvaCalculator.php @@ -17,16 +17,21 @@ use Steuerkram\Database; * 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] + * (Kz 61/62 werden NICHT mehr über ustva_kz geroutet) * - * 2. Steuerart-basiert (bookings.steuerart, unabhängig vom Konto): + * 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 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 67 = Kz 91 + Kz 94 (Vorsteuer aus ig. Erwerb) @@ -38,9 +43,13 @@ 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']; + /** * 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'], @@ -50,8 +59,6 @@ class UstvaCalculator '35' => ['35'], '41' => ['41'], '44' => ['44'], - '61' => ['61'], - '62' => ['62'], ]; /** @@ -71,7 +78,7 @@ class UstvaCalculator 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, @@ -82,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) @@ -95,7 +103,28 @@ class UstvaCalculator ]; } - // ── 2. Steuerart-basierte Kennzahlen ───────────────────────────────── + // ── 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 . ')) + GROUP BY bk.ust_satz', + array_merge([$year, $month], self::EU_STEUERARTEN) + ); + + 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( 'SELECT bk.steuerart, SUM(bk.betrag_netto) AS summe_netto FROM bookings bk @@ -111,7 +140,7 @@ class UstvaCalculator $bySteuerart[$row['steuerart']] = (float)$row['summe_netto']; } - // ── 3. Kennzahlen zusammensetzen ───────────────────────────────────── + // ── 4. 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); @@ -153,14 +182,17 @@ class UstvaCalculator /** * Liefert alle Buchungszeilen, die zu einer Kennzahl beitragen (Drill-down). - * Kombiniert kontobasierte und steuerart-basierte Quellen per UNION. + * Kombiniert kontobasierte, buchungsbasierte und steuerart-basierte Quellen per UNION. */ public function fetchBookingsForKz(int $year, int $month, string $kz): array { $accountKzList = self::KZ_ACCOUNT_MAP[$kz] ?? []; $steuerartList = self::KZ_STEUERART_MAP[$kz] ?? []; - if (empty($accountKzList) && empty($steuerartList)) { + // Kz 61/62: buchungsbasiert via ust_satz + $vstSatz = array_search($kz, self::VST_SATZ_KZ, true); + + if (empty($accountKzList) && empty($steuerartList) && $vstSatz === false) { return []; } @@ -192,6 +224,20 @@ class UstvaCalculator ); } + 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 + ); + } + if (!empty($steuerartList)) { $ph = implode(',', array_fill(0, count($steuerartList), '?')); $parts[] = $select . ' AND bk.steuerart IN (' . $ph . ')'; diff --git a/src/Views/accounts/form.php b/src/Views/accounts/form.php index 2de49c6..79d38c6 100644 --- a/src/Views/accounts/form.php +++ b/src/Views/accounts/form.php @@ -22,7 +22,7 @@ - + @@ -47,6 +47,7 @@ $badgeClass = match($acc['kontenart']) { 'Betriebseinnahme' => 'badge-green', 'Betriebsausgabe' => 'badge-red', + 'Anlagevermögen' => 'badge-purple', 'Zahlungskonto' => 'badge-blue', 'Verbindlichkeit' => 'badge-yellow', default => 'badge-gray', diff --git a/src/Views/belege/detail.php b/src/Views/belege/detail.php index 22823b4..a37633e 100644 --- a/src/Views/belege/detail.php +++ b/src/Views/belege/detail.php @@ -382,7 +382,7 @@ $suppliers = $suppliers ?? [];