diff --git a/BERICHTE.md b/BERICHTE.md index 3eddbc1..80bd97f 100644 --- a/BERICHTE.md +++ b/BERICHTE.md @@ -9,16 +9,19 @@ Dieses Dokument beschreibt, wie Buchungen in die beiden steuerlichen Berichte ei **Datei:** `src/Services/EuerCalculator.php` -Die EÜR basiert ausschließlich auf dem Feld `accounts.kontenart` des Kategorie-Kontos. Das Feld `buchungstyp` wird nicht ausgewertet. Stornobuchungen (`is_reversal = 1`) werden ausgeschlossen. +Die EÜR basiert auf dem Feld `accounts.kontenart` sowie `accounts.euer_zeile` des Kategorie-Kontos. Das Feld `buchungstyp` wird nicht ausgewertet. Stornobuchungen (`is_reversal = 1`) werden ausgeschlossen. -### Spaltenauswahl nach Kontenart +### Spaltenauswahl nach Kontenart und euer_zeile -| kontenart | EÜR-Spalte | -|---|---| -| `Betriebseinnahme` | Einnahmen | -| `Betriebsausgabe` | Ausgaben | -| `Privat` | nicht in EÜR (Privatentnahmen/-einlagen beeinflussen den Gewinn nicht) | -| `Zahlungskonto`, `Verbindlichkeit` | nicht in EÜR | +| kontenart | euer_zeile | EÜR-Abschnitt | +|---|---|---| +| `Betriebseinnahme` | beliebig | Betriebseinnahmen | +| `Betriebsausgabe` | `≠ '51'` | Betriebsausgaben (fließen in `total_ausgaben` und `gewinn` ein) | +| `Betriebsausgabe` | `'51'` | Nicht abzugsfähige Betriebsausgaben (KZ 165) – eigener Abschnitt, **nicht** in `total_ausgaben` oder `gewinn` | +| `Privat` | beliebig | nicht in EÜR (Privatentnahmen/-einlagen beeinflussen den Gewinn nicht) | +| `Zahlungskonto`, `Verbindlichkeit` | beliebig | nicht in EÜR | + +**EÜR-Zeile 51 / KZ 165** entspricht den „Nicht abziehbaren Betriebsausgaben nach § 4 Abs. 5 EStG" (z.B. nicht abzugsfähige Bewirtungskosten auf Konten 4654, 4656; nicht abzugsfähige Geschenke auf 4635, 4636; nicht abzugsfähiger Arbeitszimmeranteil auf 4289). Diese Konten werden im EÜR-Bericht unterhalb der Betriebsausgaben in einem separaten Abschnitt angezeigt. ### Vorzeichen diff --git a/migrations/015_nicht_abzugsfaehig_4656_euer_zeile.sql b/migrations/015_nicht_abzugsfaehig_4656_euer_zeile.sql new file mode 100644 index 0000000..2637688 --- /dev/null +++ b/migrations/015_nicht_abzugsfaehig_4656_euer_zeile.sql @@ -0,0 +1,7 @@ +-- Migration 015: euer_zeile = '51' für Konto 4656 setzen +-- Konto 4656 "nicht abzugsfähige Bewirtungskosten VSt. 7%" gehört wie +-- 4654 und 4635/4636 zur EÜR-Zeile 51 (Nicht abziehbare Betriebsausgaben). +UPDATE accounts +SET euer_zeile = '51' +WHERE kontonummer = '4656' + AND (euer_zeile IS NULL OR euer_zeile = ''); diff --git a/src/Services/EuerCalculator.php b/src/Services/EuerCalculator.php index 63c9453..5ea807c 100644 --- a/src/Services/EuerCalculator.php +++ b/src/Services/EuerCalculator.php @@ -28,6 +28,7 @@ class EuerCalculator a.kontonummer, a.bezeichnung, a.kontenart, + a.euer_zeile, SUM(bk.betrag_netto) AS summe_netto, COUNT(bk.id) AS buchungsanzahl FROM bookings bk @@ -36,36 +37,42 @@ class EuerCalculator WHERE fy.year = ? AND bk.is_reversal = 0 AND a.kontenart IN (\'Betriebseinnahme\', \'Betriebsausgabe\') - GROUP BY a.id, a.kontonummer, a.bezeichnung, a.kontenart + GROUP BY a.id, a.kontonummer, a.bezeichnung, a.kontenart, a.euer_zeile HAVING buchungsanzahl > 0 ORDER BY a.kontonummer', [$year] ); - $einnahmen = []; - $ausgaben = []; + $einnahmen = []; + $ausgaben = []; + $nichtAbzugsfaehig = []; foreach ($rows as $row) { $netto = round((float)$row['summe_netto'], 2); if ($row['kontenart'] === 'Betriebseinnahme') { $einnahmen[] = array_merge($row, ['netto' => $netto]); + } elseif ($row['euer_zeile'] === '51') { + $nichtAbzugsfaehig[] = array_merge($row, ['netto' => $netto]); } else { $ausgaben[] = array_merge($row, ['netto' => $netto]); } } - $totalEinnahmen = array_sum(array_column($einnahmen, 'netto')); - $totalAusgaben = array_sum(array_column($ausgaben, 'netto')); - $gewinn = round($totalEinnahmen - $totalAusgaben, 2); + $totalEinnahmen = array_sum(array_column($einnahmen, 'netto')); + $totalAusgaben = array_sum(array_column($ausgaben, 'netto')); + $totalNichtAbzugsfaehig = array_sum(array_column($nichtAbzugsfaehig, 'netto')); + $gewinn = round($totalEinnahmen - $totalAusgaben, 2); return [ - 'year' => $year, - 'einnahmen' => $einnahmen, - 'ausgaben' => $ausgaben, - 'total_einnahmen' => round($totalEinnahmen, 2), - 'total_ausgaben' => round($totalAusgaben, 2), - 'gewinn' => $gewinn, + 'year' => $year, + 'einnahmen' => $einnahmen, + 'ausgaben' => $ausgaben, + 'nicht_abzugsfaehig' => $nichtAbzugsfaehig, + 'total_einnahmen' => round($totalEinnahmen, 2), + 'total_ausgaben' => round($totalAusgaben, 2), + 'total_nicht_abzugsfaehig' => round($totalNichtAbzugsfaehig, 2), + 'gewinn' => $gewinn, ]; } diff --git a/src/Services/PdfEuerService.php b/src/Services/PdfEuerService.php index a60ec00..0d26482 100644 --- a/src/Services/PdfEuerService.php +++ b/src/Services/PdfEuerService.php @@ -56,6 +56,36 @@ class PdfEuerService $gewinnLabel = $euer['gewinn'] >= 0 ? 'Überschuss (Gewinn)' : 'Fehlbetrag (Verlust)'; $gewinnClass = $euer['gewinn'] >= 0 ? 'positive' : 'negative'; + $nichtAbzugRows = ''; + foreach ($euer['nicht_abzugsfaehig'] ?? [] as $na) { + $nichtAbzugRows .= '' + . '' . $h($na['kontonummer']) . '' + . '' . $h($na['bezeichnung']) . '' + . '' . $fmt($na['netto']) . '' + . ''; + } + $nichtAbzugSection = ''; + if ($nichtAbzugRows !== '') { + $nichtAbzugSection = <<Nicht abzugsfähige Betriebsausgaben (KZ 165) + + + + + + + {$nichtAbzugRows} + + + + + + +
Kto.BezeichnungBetrag
Summe nicht abzugsfähige Kosten{$fmt($euer['total_nicht_abzugsfaehig'])}
+HTML; + } + return <<Einnahmen-Überschuss-Rechnung {$year}

{$firma} · Geschäftsjahr {$year}

@@ -92,7 +122,7 @@ class PdfEuerService - +{$nichtAbzugSection}

Erstellt am {$h(date('d.m.Y H:i'))} · Steuerkram

HTML; } @@ -118,6 +148,7 @@ h1 { font-size: 14pt; margin-bottom: 2mm; } .gewinn-row.negative td { background: #fee2e2; } .gewinn-cell { text-align: right; font-size: 11pt; } .gewinn-val { margin-left: 4mm; } +.section-heading { font-size: 11pt; margin-top: 8mm; margin-bottom: 2mm; } .generated-note { font-size: 7pt; color: #888; margin-top: 8mm; } CSS; } diff --git a/src/Views/tax/euer_show.php b/src/Views/tax/euer_show.php index 18302b9..05a286c 100644 --- a/src/Views/tax/euer_show.php +++ b/src/Views/tax/euer_show.php @@ -81,6 +81,42 @@ $fmt = fn($v) => View::formatMoney($v); - + +
+
+
Nicht abzugsfähige Betriebsausgaben (KZ 165)
+
+
+ + + + + + + + + + + + + + + + + + + + + + + +
Kto.BezeichnungBetrag
+ +
Summe nicht abzugsfähige Kosten
+
+
+ + +
Für wurden noch keine Buchungen erfasst.
diff --git a/tests/15_NichtAbzugsfaehigEuerTest.php b/tests/15_NichtAbzugsfaehigEuerTest.php new file mode 100644 index 0000000..b9f6a6d --- /dev/null +++ b/tests/15_NichtAbzugsfaehigEuerTest.php @@ -0,0 +1,145 @@ +findAccountId('1200'); + $erloesId = $this->findAccountId('8400'); // Erlöse 19% USt + $konto4654Id = $this->findAccountId('4654'); + $konto4600Id = $this->findAccountId('4600'); // normale Betriebsausgabe + + if (!$bankId || !$erloesId || !$konto4654Id || !$konto4600Id) { + $this->assert(false, 'Benötigte Konten (1200, 8400, 4654, 4600) nicht gefunden'); + return; + } + + // Prüfen, dass 4654 wirklich euer_zeile='51' hat + $kto = \Steuerkram\Database::fetchOne( + "SELECT euer_zeile FROM accounts WHERE kontonummer = '4654'" + ); + $this->assertEquals('51', $kto['euer_zeile'] ?? null, 'Konto 4654 hat euer_zeile=51'); + + // Einnahme buchen (Referenz für Gewinnberechnung) + $belegEin = \Steuerkram\Models\Beleg::create([ + 'beleg_nr' => 'TEST-KZ165-EIN-' . uniqid(), + 'filename' => 'test.pdf', + 'storage_path' => '', + 'mime_type' => 'application/pdf', + 'file_hash' => hash('sha256', 'kz165ein' . uniqid()), + 'beleg_datum' => $datum, + 'eingangs_datum' => $datum, + 'belegtyp' => 'Ausgangsrechnung', + ]); + \Steuerkram\Models\Booking::create([ + 'beleg_id' => $belegEin, + 'buchungs_datum' => $datum, + 'buchungstyp' => 'Einnahme', + 'kategorie_konto_id' => $erloesId, + 'zahlungskonto_id' => $bankId, + 'betrag_netto' => 1000.00, + 'ust_satz' => 19.0, + 'buchungstext' => 'Test Einnahme KZ165', + ]); + + // Normale Betriebsausgabe (soll in ausgaben erscheinen) + $belegAus = \Steuerkram\Models\Beleg::create([ + 'beleg_nr' => 'TEST-KZ165-AUS-' . uniqid(), + 'filename' => 'test2.pdf', + 'storage_path' => '', + 'mime_type' => 'application/pdf', + 'file_hash' => hash('sha256', 'kz165aus' . uniqid()), + 'beleg_datum' => $datum, + 'eingangs_datum' => $datum, + 'belegtyp' => 'Eingangsrechnung', + ]); + \Steuerkram\Models\Booking::create([ + 'beleg_id' => $belegAus, + 'buchungs_datum' => $datum, + 'buchungstyp' => 'Ausgabe', + 'kategorie_konto_id' => $konto4600Id, + 'zahlungskonto_id' => $bankId, + 'betrag_netto' => 200.00, + 'ust_satz' => 19.0, + 'buchungstext' => 'Test normale Ausgabe KZ165', + ]); + + // Nicht abzugsfähige Bewirtungskosten (soll in nicht_abzugsfaehig erscheinen) + $belegNa = \Steuerkram\Models\Beleg::create([ + 'beleg_nr' => 'TEST-KZ165-NA-' . uniqid(), + 'filename' => 'test3.pdf', + 'storage_path' => '', + 'mime_type' => 'application/pdf', + 'file_hash' => hash('sha256', 'kz165na' . uniqid()), + 'beleg_datum' => $datum, + 'eingangs_datum' => $datum, + 'belegtyp' => 'Eingangsrechnung', + ]); + \Steuerkram\Models\Booking::create([ + 'beleg_id' => $belegNa, + 'buchungs_datum' => $datum, + 'buchungstyp' => 'Ausgabe', + 'kategorie_konto_id' => $konto4654Id, + 'zahlungskonto_id' => $bankId, + 'betrag_netto' => 30.00, + 'ust_satz' => 19.0, + 'buchungstext' => 'Test nicht abzugsfähige Kosten', + ]); + + $euer = (new \Steuerkram\Services\EuerCalculator())->calculate((int)date('Y')); + + // 4654-Buchung muss in nicht_abzugsfaehig auftauchen + $naKonten = array_column($euer['nicht_abzugsfaehig'], 'kontonummer'); + $this->assert( + in_array('4654', $naKonten), + 'Konto 4654 erscheint in nicht_abzugsfaehig' + ); + + // 4654-Buchung darf NICHT in ausgaben sein + $ausKonten = array_column($euer['ausgaben'], 'kontonummer'); + $this->assert( + !in_array('4654', $ausKonten), + 'Konto 4654 erscheint NICHT in ausgaben' + ); + + // total_nicht_abzugsfaehig enthält den Betrag + $this->assertGreaterThan(0, (float)$euer['total_nicht_abzugsfaehig'], 'total_nicht_abzugsfaehig > 0'); + + // total_ausgaben enthält die 200 € normale Ausgabe, NICHT die 30 € nicht abzugsfähige + // (Es könnten weitere Buchungen aus anderen Tests vorhanden sein, daher nur prüfen + // dass total_ausgaben keine 30 € mehr enthält als ohne die 4654-Buchung) + $this->assert( + (float)$euer['total_nicht_abzugsfaehig'] >= 30.0, + 'total_nicht_abzugsfaehig enthält mindestens 30 €' + ); + + // Gewinn = Einnahmen - normale Ausgaben (nicht abzugsfähige zählen nicht) + $erwartetGewinn = round( + (float)$euer['total_einnahmen'] - (float)$euer['total_ausgaben'], + 2 + ); + $this->assertEquals($erwartetGewinn, (float)$euer['gewinn'], 'Gewinn = Einnahmen - normale Ausgaben'); + + // Die nicht abzugsfähigen Kosten sind im Gewinn nicht enthalten + $gewinnOhneNa = round( + (float)$euer['total_einnahmen'] - (float)$euer['total_ausgaben'] - (float)$euer['total_nicht_abzugsfaehig'], + 2 + ); + $this->assert( + (float)$euer['gewinn'] > $gewinnOhneNa || (float)$euer['total_nicht_abzugsfaehig'] > 0, + 'Nicht abzugsfähige Kosten reduzieren den Gewinn nicht' + ); + } +}