From 7a893ceab8d9e9571e6b79d5f99a92efbe7f7cad Mon Sep 17 00:00:00 2001 From: fds2610 Date: Wed, 1 Apr 2026 00:14:07 +0200 Subject: [PATCH] Fix #20: Bewirtungsbuchungen auf 4650/4651 automatisch 70/30 auf Abzugskonto splitten --- Changes.md | 41 +++++ migrations/016_bewirtungsfelder_bookings.sql | 10 ++ src/Controllers/BookingController.php | 68 +++++-- src/Models/Booking.php | 7 +- src/Views/belege/detail.php | 47 ++++- tests/16_BewirtungssplitTest.php | 178 +++++++++++++++++++ 6 files changed, 337 insertions(+), 14 deletions(-) create mode 100644 migrations/016_bewirtungsfelder_bookings.sql create mode 100644 tests/16_BewirtungssplitTest.php diff --git a/Changes.md b/Changes.md index 77a48a4..311b149 100644 --- a/Changes.md +++ b/Changes.md @@ -2,6 +2,47 @@ --- +## 2026-04-01 – Feature: Bewirtungsbelege (Konten 4650/4651) mit automatischem Split + +### Neu +- Buchungen auf Konto 4650 (Bewirtungskosten VSt. 19%) oder 4651 (Bewirtungskosten VSt. 7%) werden automatisch aufgeteilt: 70 % auf das gewählte Konto, 30 % auf das Abzugskonto (4654 bzw. 4656) +- Neue Felder `bewirtungsgrund` und `teilnehmerliste` in der `bookings`-Tabelle (Migration 016) +- Im Buchungsformular erscheinen die beiden Felder automatisch, wenn 4650/4651 als Kategorie gewählt wird +- Vorhandene Buchungen zeigen Bewirtungsgrund und Teilnehmerliste in der Buchungsübersicht an + +## 2026-04-01 – Bugfix: Nicht abzugsfähige Kosten (4654/4656) separat in EÜR unter KZ 165 + +### Behoben +- Konten mit `euer_zeile = '51'` (4654, 4656, 4635, 4636, 4289) fließen nicht mehr in `total_ausgaben` und `gewinn` ein +- Sie erscheinen in einem eigenen Abschnitt „Nicht abzugsfähige Betriebsausgaben (KZ 165)" in Web und PDF +- Migration 015 setzt `euer_zeile = '51'` für Konto 4656 (fehlte bisher) + +--- + +## 2026-03-30 – Bugfix: 30 % Eigenanteil fließt jetzt korrekt in UStVA KZ 81/83 + +### Behoben +- `Booking::upsertEigenanteil30`: statt zwei Buchungen (8922 Netto + 1776 USt-Korrektur) wird + nur noch eine einzige Buchung auf 8922 mit echtem `ust_satz` und `betrag_ust` erzeugt +- `upsertKorrekturbuchung`: neue Parameter `ustSatz` und `betragUst`; UPDATE und INSERT setzen + jetzt `betrag_ust` und `betrag_brutto` korrekt +- Vorher: `betrag_ust = 0` auf 8922 → KZ 83 blieb leer; 1776-Buchung hatte keine UStVA-Wirkung + +--- + +## 2026-03-30 – Kontenrahmen-Reset: DATEV SKR03 2026 (IST-Versteuerung) + +### Geändert +- Alle Buchungen und Belege gelöscht (Testdaten) +- Gesamter Kontenrahmen durch DATEV SKR03 2026 (gültig ab 2026-01-01) ersetzt +- Neue Spalte `euer_zeile` in Tabelle `accounts`: Zeilennummer der Anlage EÜR +- 145 Konten neu angelegt: Klassen 0, 1, 3, 4, 8 (ohne Klasse 9 / ohne Verbindlichkeiten) +- Verfahren: IST-Versteuerung; Konto 1390 „Verrechnungskonto Ist-Versteuerung" enthalten +- UStVA-Kennzeichen korrekt nach DATEV: 81/86 (Umsätze), 61/62 (Vorsteuer), 35/41 (steuerfrei) +- Migration: `013_reset_kontenrahmen_skr03_2026.sql` + +--- + ## 2026-03-30 – KI-gestützte Belegerkennung (experimentell) + 30 % Eigenanteil Telefon/Internet ### Neu diff --git a/migrations/016_bewirtungsfelder_bookings.sql b/migrations/016_bewirtungsfelder_bookings.sql new file mode 100644 index 0000000..3cfad23 --- /dev/null +++ b/migrations/016_bewirtungsfelder_bookings.sql @@ -0,0 +1,10 @@ +-- Migration 016: Bewirtungsfelder in bookings-Tabelle +-- Speichert Bewirtungsgrund und Teilnehmerliste für Buchungen auf +-- Konten 4650/4651 (Bewirtungskosten 70 % abzugsfähig). +ALTER TABLE bookings + ADD COLUMN IF NOT EXISTS bewirtungsgrund TEXT NULL + COMMENT 'Anlass/Grund der Bewirtung (§ 4 Abs. 5 Nr. 2 EStG)' + AFTER buchungstext, + ADD COLUMN IF NOT EXISTS teilnehmerliste TEXT NULL + COMMENT 'Teilnehmer der Bewirtung (§ 4 Abs. 5 Nr. 2 EStG)' + AFTER bewirtungsgrund; diff --git a/src/Controllers/BookingController.php b/src/Controllers/BookingController.php index 636dabd..dcef41f 100644 --- a/src/Controllers/BookingController.php +++ b/src/Controllers/BookingController.php @@ -109,21 +109,33 @@ class BookingController try { $data['beleg_id'] = $belegId; - Booking::create($data); - $eigenanteil = isset($data['eigenanteil_30']) ? 1 : 0; - \Steuerkram\Database::execute( - 'UPDATE belege SET eigenanteil_30 = ?, updated_at = NOW() WHERE id = ?', - [$eigenanteil, $belegId] + // Bewirtungskonto-Split: 4650→4654, 4651→4656 + $konto = \Steuerkram\Database::fetchOne( + 'SELECT kontonummer FROM accounts WHERE id = ?', + [(int)$data['kategorie_konto_id']] ); + $kontonummer = $konto['kontonummer'] ?? ''; - if ($eigenanteil === 1) { - Booking::upsertEigenanteil30( - $belegId, - (float)$data['betrag_netto'], - (float)($data['ust_satz'] ?? 0), - $data['buchungs_datum'] + if ($kontonummer === '4650' || $kontonummer === '4651') { + $this->bucheBewirtungssplit($data, $kontonummer); + } else { + Booking::create($data); + + $eigenanteil = isset($data['eigenanteil_30']) ? 1 : 0; + \Steuerkram\Database::execute( + 'UPDATE belege SET eigenanteil_30 = ?, updated_at = NOW() WHERE id = ?', + [$eigenanteil, $belegId] ); + + if ($eigenanteil === 1) { + Booking::upsertEigenanteil30( + $belegId, + (float)$data['betrag_netto'], + (float)($data['ust_satz'] ?? 0), + $data['buchungs_datum'] + ); + } } View::redirect("/belege/{$belegId}", 'Buchung erfasst.'); @@ -156,6 +168,40 @@ class BookingController ]); } + private function bucheBewirtungssplit(array $data, string $kontonummer): void + { + // Abzugskonto ermitteln + $abzugskontonummer = $kontonummer === '4650' ? '4654' : '4656'; + $abzugskonto = \Steuerkram\Database::fetchOne( + 'SELECT id FROM accounts WHERE kontonummer = ?', + [$abzugskontonummer] + ); + if (!$abzugskonto) { + throw new \RuntimeException("Abzugskonto {$abzugskontonummer} nicht gefunden."); + } + + $nettoGesamt = round((float)$data['betrag_netto'], 2); + $ustSatz = (float)($data['ust_satz'] ?? 0); + + $netto70 = round($nettoGesamt * 0.7, 2); + $netto30 = round($nettoGesamt - $netto70, 2); + + // Buchung 1: 70 % auf Bewirtungskonto (abzugsfähig) + Booking::create(array_merge($data, [ + 'betrag_netto' => $netto70, + 'bewirtungsgrund' => $data['bewirtungsgrund'] ?? null, + 'teilnehmerliste' => $data['teilnehmerliste'] ?? null, + ])); + + // Buchung 2: 30 % auf Abzugskonto (nicht abzugsfähig, § 4 Abs. 5 EStG) + Booking::create(array_merge($data, [ + 'kategorie_konto_id' => $abzugskonto['id'], + 'betrag_netto' => $netto30, + 'bewirtungsgrund' => $data['bewirtungsgrund'] ?? null, + 'teilnehmerliste' => $data['teilnehmerliste'] ?? null, + ])); + } + public function destroy(array $params): void { if (!Setting::isTestMode()) { diff --git a/src/Models/Booking.php b/src/Models/Booking.php index dfafd98..b04cec5 100644 --- a/src/Models/Booking.php +++ b/src/Models/Booking.php @@ -104,8 +104,9 @@ class Booking Database::execute( 'INSERT INTO bookings (buchungs_nr, beleg_id, buchungs_datum, buchungstyp, kategorie_konto_id, zahlungskonto_id, - betrag_netto, ust_satz, steuerart, betrag_ust, betrag_brutto, buchungstext, fiscal_year_id) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)', + betrag_netto, ust_satz, steuerart, betrag_ust, betrag_brutto, + buchungstext, bewirtungsgrund, teilnehmerliste, fiscal_year_id) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)', [ $nr, (int)$data['beleg_id'], @@ -119,6 +120,8 @@ class Booking $ust, $brutto, $data['buchungstext'], + $data['bewirtungsgrund'] ?? null, + $data['teilnehmerliste'] ?? null, $fy['id'], ] ); diff --git a/src/Views/belege/detail.php b/src/Views/belege/detail.php index 45962db..4e43b1b 100644 --- a/src/Views/belege/detail.php +++ b/src/Views/belege/detail.php @@ -214,6 +214,12 @@ $suppliers = $suppliers ?? []; Storniert + +
Grund: + + +
Teilnehmer: + @@ -371,7 +377,7 @@ $suppliers = $suppliers ?? []; + +
+ + +
+ +