From 1950b3c8924b2fca274e75eb693465266277df59 Mon Sep 17 00:00:00 2001 From: fds2610 Date: Fri, 27 Mar 2026 07:21:05 +0100 Subject: [PATCH] =?UTF-8?q?Fix=20#8:=20Ausgangsrechnungen=20erscheinen=20j?= =?UTF-8?q?etzt=20in=20Dashboard,=20E=C3=9CR=20und=20UStVA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Changes.md | 13 + scripts/seed-testdata.php | 480 ++++++++++++++++++++++++++++++ tests/09_InvoiceToReportsTest.php | 140 +++++++++ 3 files changed, 633 insertions(+) create mode 100755 scripts/seed-testdata.php create mode 100644 tests/09_InvoiceToReportsTest.php diff --git a/Changes.md b/Changes.md index a1fa175..0a9ea0e 100644 --- a/Changes.md +++ b/Changes.md @@ -2,6 +2,19 @@ --- +## 2026-03-27 – Ausgangsrechnungen in Dashboard/EÜR/UStVA (#8) + +### Behoben +- **Seed-Skript**: `scripts/seed-testdata.php` rief `Invoice::finalize()` auf, ohne anschließend Beleg und Buchungen anzulegen – Ausgangsrechnungen fehlten daher in allen Auswertungen +- **Ursache**: `Invoice::finalize()` (Model) setzt nur Status und Rechnungsnummer; die Buchungserstellung liegt in `InvoiceController::createBelegAndBookings()` und wurde im Seed nicht aufgerufen +- **Fix**: Seed-Skript legt nach `Invoice::finalize()` nun Beleg und zugehörige Buchungen an (analog zur Controller-Logik), sodass Ausgangsrechnungen korrekt in EÜR, UStVA und Dashboard erscheinen + +### Betrifft +- Skript: `scripts/seed-testdata.php` +- Test: `tests/09_InvoiceToReportsTest.php` (neu – End-to-End-Test Ausgangsrechnung → EÜR/UStVA/Dashboard) + +--- + ## 2026-03-26 – Lieferantenmanagement (#7) ### Neu diff --git a/scripts/seed-testdata.php b/scripts/seed-testdata.php new file mode 100755 index 0000000..eef2a4f --- /dev/null +++ b/scripts/seed-testdata.php @@ -0,0 +1,480 @@ +#!/usr/bin/env php + 'TechVentures GmbH', + 'anrede' => 'Herr', + 'vorname' => 'Michael', + 'nachname' => 'Schmidt', + 'strasse' => 'Goethestraße 15', + 'plz' => '60313', + 'ort' => 'Frankfurt am Main', + 'land' => 'DE', + 'email' => 'm.schmidt@techventures.de', + 'telefon' => '+49 69 123456-0', + 'ust_id' => 'DE234567890', + 'payment_days' => 30, + 'notes' => 'Hauptkunde – IT-Beratung und Digitalisierungsprojekte', +]); +echo "Kunde angelegt: TechVentures GmbH (ID {$customerId})\n"; + +// ── Eingangsbelege ──────────────────────────────────────────────────────────── +// Schema je Beleg: +// [datum, lieferant, ext_rechnungs_nr, belegtyp, notes, [ +// [konto_id, betrag_netto, ust_satz, buchungstext], +// ... +// ]] +$belegDaten = [ + // ══════════════════════════════════════ + // Januar 2026 + // ══════════════════════════════════════ + [ + '2026-01-08', 'Deutsche Bahn AG', 'DB-2026-010891', + 'Eingangsrechnung', 'Geschäftsreise Frankfurt–Hamburg', + [ + [$reise, 89.90, 19, 'Zugticket Frankfurt–Hamburg (Hinfahrt, 1. Klasse Flexpreis)'], + [$reise, 84.50, 19, 'Zugticket Hamburg–Frankfurt (Rückfahrt, 1. Klasse Flexpreis)'], + ], + ], + [ + '2026-01-09', 'Best Western Hotel Hamburg', 'BWH-2026-0342', + 'Eingangsrechnung', '2 Nächte inkl. Frühstück, Kundentermin Hamburg', + [ + [$reise, 189.00, 19, 'Übernachtung Best Western Hamburg – 2 Nächte (EZ)'], + [$bewirt, 28.00, 19, 'Frühstück Best Western Hamburg (2 Tage)'], + ], + ], + [ + '2026-01-09', 'Hafen Grill Hamburg', 'HG-001-2026', + 'Quittung', 'Geschäftsessen mit Kunden TechVentures (3 Personen)', + [ + [$bewirt, 67.50, 19, 'Geschäftsessen Hafen Grill Hamburg – Projektbesprechung'], + ], + ], + [ + '2026-01-09', 'FREE NOW GmbH', 'FN-2026-884231', + 'Quittung', 'Taxi Hamburg Hafen nach Hotel', + [ + [$reise, 22.30, 19, 'Taxi Hamburg – Hafen Grill zum Best Western Hotel'], + ], + ], + [ + '2026-01-20', 'Deutsche Telekom AG', 'TEL-2026-01-774883', + 'Eingangsrechnung', 'Mobilfunk Januar 2026', + [ + [$telefon, 39.50, 19, 'Mobilfunkvertrag Telekom Business – Januar 2026'], + ], + ], + [ + '2026-01-01', 'Microsoft Ireland Operations Ltd.', 'MSFT-2026-01-00123', + 'Eingangsrechnung', 'Microsoft 365 Business – Monatslizenz', + [ + [$edv, 10.90, 19, 'Microsoft 365 Business Premium – Monatsgebühr Januar 2026'], + ], + ], + [ + '2026-01-15', 'Amazon Business', 'AMZ-BIZ-2026-0089123', + 'Eingangsrechnung', 'Bürobedarf Januar', + [ + [$buerob, 45.38, 19, 'Bürobedarf: Tonerkartusche HP, Druckpapier A4 (2×500 Blatt)'], + ], + ], + [ + '2026-01-22', 'Deutsche Bahn AG', 'DB-2026-022745', + 'Eingangsrechnung', 'Dienstreise Stuttgart–Berlin', + [ + [$reise, 145.00, 19, 'ICE Stuttgart–Berlin (Hinfahrt, 1. Klasse Flexpreis)'], + [$reise, 4.50, 19, 'Sitzplatzreservierung ICE Stuttgart–Berlin'], + ], + ], + [ + '2026-01-23', 'Restaurant Unter den Linden GmbH', 'UDL-2026-0018', + 'Quittung', 'Strategiemeeting Berlin (4 Personen)', + [ + [$bewirt, 89.20, 19, 'Geschäftsessen Unter den Linden Berlin – Strategiemeeting mit Projektpartnern'], + ], + ], + [ + '2026-01-23', 'Starbucks Coffee Germany GmbH', 'SB-23012026', + 'Quittung', 'Arbeitsgespräch Berlin', + [ + [$bewirt, 18.40, 19, 'Starbucks Berlin – Arbeitsgespräch mit Projektleiter TechVentures'], + ], + ], + + // ══════════════════════════════════════ + // Februar 2026 + // ══════════════════════════════════════ + [ + '2026-02-11', 'Sixt SE', 'SIXT-2026-0229812', + 'Eingangsrechnung', 'Mietwagen Kundenbesuch München (3 Tage)', + [ + [$reise, 195.00, 19, 'Mietwagen Sixt VW Passat – 3 Tage München'], + [$reise, 52.00, 19, 'Kraftstoff Mietwagen Sixt München'], + ], + ], + [ + '2026-02-11', 'Hilton Frankfurt City Centre', 'HLT-2026-0452', + 'Eingangsrechnung', 'Übernachtung für Kundentermin Frankfurt', + [ + [$reise, 215.00, 19, 'Übernachtung Hilton Frankfurt City Centre – 2 Nächte (EZ)'], + ], + ], + [ + '2026-02-12', 'Restaurant Metropol Frankfurt', 'METRO-2026-013', + 'Quittung', 'Geschäftsessen Projektplanung Q1 (3 Personen)', + [ + [$bewirt, 112.50, 19, 'Geschäftsessen Restaurant Metropol – Projektplanung Q1 mit Auftraggeber'], + ], + ], + [ + '2026-02-18', 'Deutsche Lufthansa AG', 'LH-2026-0748221', + 'Eingangsrechnung', 'Flug Stuttgart–Hamburg Hin/Rück (Kundenpräsentation)', + [ + [$reise, 189.00, 19, 'Lufthansa Flug STR–HAM Hin/Rück – Economy Flex'], + ], + ], + [ + '2026-02-20', '1&1 Versatel GmbH', '1U1-2026-02-883421', + 'Eingangsrechnung', 'DSL-Anschluss Büro Februar 2026', + [ + [$inet, 34.95, 19, 'DSL Business-Anschluss Büro – Februar 2026'], + ], + ], + [ + '2026-02-01', 'Slack Technologies, Inc.', 'SLK-2026-02-00091', + 'Eingangsrechnung', 'Slack Pro Monatsgebühr', + [ + [$edv, 7.25, 19, 'Slack Pro Workspace – Monatsgebühr Februar 2026'], + ], + ], + [ + '2026-02-01', 'Deutsche Bahn AG', 'DB-BC100-2026-02', + 'Eingangsrechnung', 'BahnCard 100 erste Klasse – Monatsanteil', + [ + [$reise, 308.40, 19, 'BahnCard 100 erste Klasse – Monatsanteil Februar 2026'], + ], + ], + [ + '2026-02-25', 'IHK Frankfurt am Main', 'IHK-SEM-2026-0881', + 'Eingangsrechnung', 'Fortbildung „Agiles Projektmanagement" (1 Tag)', + [ + [$fortb, 295.00, 19, 'Seminar „Agiles Projektmanagement in der Beratungspraxis" – IHK Frankfurt'], + [$fachlit, 24.30, 7, 'Seminarunterlagen und Skript zur Fortbildung'], + ], + ], + [ + '2026-02-26', 'La Dolce Vita GmbH', 'LDV-2026-0197', + 'Quittung', 'Projektabschlussdinner mit Kunden (5 Personen)', + [ + [$bewirt, 95.80, 19, 'Geschäftsessen La Dolce Vita – Projektabschlussdinner TechVentures'], + ], + ], + [ + '2026-02-10', 'Staples Deutschland GmbH', 'STP-2026-0228817', + 'Eingangsrechnung', 'Büromaterial Februar', + [ + [$buerob, 38.49, 19, 'Büromaterial: Ordner, Hefter, Schreibutensilien'], + ], + ], + + // ══════════════════════════════════════ + // März 2026 + // ══════════════════════════════════════ + [ + '2026-03-04', 'Deutsche Bahn AG', 'DB-2026-030428', + 'Eingangsrechnung', 'Dienstreise Frankfurt–Berlin (Kundentermin)', + [ + [$reise, 155.00, 19, 'ICE Frankfurt–Berlin Hin/Rück – 1. Klasse Flexpreis'], + ], + ], + [ + '2026-03-05', 'Scandic Hotels Deutschland GmbH', 'SCA-BER-2026-0041', + 'Eingangsrechnung', 'Übernachtung Berlin Kundentermin (2 Nächte)', + [ + [$reise, 142.00, 19, 'Übernachtung Scandic Hotel Berlin – 2 Nächte (EZ)'], + [$reise, 18.00, 19, 'Parkhaus Scandic Hotel Berlin – 2 Tage'], + ], + ], + [ + '2026-03-05', 'Restaurant Grüne Pause Berlin', 'GP-2026-0067', + 'Quittung', 'Q1-Strategiereview Berlin (3 Personen)', + [ + [$bewirt, 72.40, 19, 'Geschäftsessen Grüne Pause Berlin – Q1-Strategiereview mit Führungskräften'], + ], + ], + [ + '2026-03-05', 'Uber Germany GmbH', 'UBR-2026-03050001', + 'Quittung', 'Fahrt zum Kunden Berlin', + [ + [$reise, 18.90, 19, 'Uber – Fahrt Scandic Hotel Berlin zum Kunden (TechVentures Berlin Office)'], + ], + ], + [ + '2026-03-20', 'Vodafone GmbH', 'VOD-2026-03-449871', + 'Eingangsrechnung', 'Mobilfunk März 2026', + [ + [$telefon, 41.18, 19, 'Mobilfunkvertrag Vodafone Business – März 2026'], + ], + ], + [ + '2026-03-01', 'Zoom Video Communications, Inc.', 'ZM-2026-03-0012', + 'Eingangsrechnung', 'Zoom Pro – Jahresabo März-Anteil', + [ + [$edv, 12.49, 19, 'Zoom Pro Meetings – Monatsanteil März 2026 (Jahresabonnement)'], + ], + ], + [ + '2026-03-17', 'Deutsche Bahn AG', 'DB-2026-031744', + 'Eingangsrechnung', 'Dienstreise Stuttgart–München (Workshop)', + [ + [$reise, 89.00, 19, 'ICE Stuttgart–München (Hinfahrt, 1. Klasse Flexpreis)'], + [$reise, 82.00, 19, 'ICE München–Stuttgart (Rückfahrt, 1. Klasse Flexpreis)'], + ], + ], + [ + '2026-03-12', 'Springer Fachmedien Wiesbaden GmbH', 'SFM-2026-0041229', + 'Eingangsrechnung', 'Fachliteratur Unternehmensberatung', + [ + [$fachlit, 34.90, 7, 'Fachbuch „Unternehmensberatung digital – Strategien und Methoden" (5. Aufl.)'], + ], + ], + [ + '2026-03-20', 'Restaurant Seeblick Frankfurt GmbH', 'SEE-2026-0038', + 'Quittung', 'Quartals-Review Q1 2026 mit Kunden (4 Personen)', + [ + [$bewirt, 95.00, 19, 'Geschäftsessen Restaurant Seeblick – Q1-Quartals-Review TechVentures'], + [$bewirt, 23.50, 19, 'Getränke und Weinbegleitung – Quartals-Review TechVentures'], + ], + ], + [ + '2026-03-25', 'Staples Deutschland GmbH', 'STP-2026-0332541', + 'Eingangsrechnung', 'Büromaterial März', + [ + [$buerob, 42.15, 19, 'Büromaterial: Kopierpapier, Druckerpatronen, Briefumschläge'], + ], + ], +]; + +// ── Belege und Buchungen anlegen ────────────────────────────────────────────── +$belegCount = 0; +$buchungCount = 0; + +foreach ($belegDaten as $i => $b) { + [$datum, $lieferant, $extNr, $belegtyp, $notes, $zeilen] = $b; + + $belegNr = Beleg::generateBelegNr(); + $belegId = Beleg::create([ + 'beleg_nr' => $belegNr, + 'filename' => str_replace(['/', ' '], ['-', '_'], $lieferant) . '_' . $extNr . '.pdf', + 'storage_path' => '', + 'mime_type' => 'application/pdf', + 'file_hash' => hash('sha256', $belegNr . $extNr . $lieferant), + 'beleg_datum' => $datum, + 'eingangs_datum' => $datum, + 'rechnungs_nr' => $extNr, + 'betrag_brutto' => null, // wird aus Buchungszeilen berechnet + 'belegtyp' => $belegtyp, + 'notes' => $notes, + ]); + + foreach ($zeilen as $zeile) { + [$kontoId, $netto, $ust, $buchungstext] = $zeile; + Booking::create([ + 'beleg_id' => $belegId, + 'buchungs_datum' => $datum, + 'buchungstyp' => 'Ausgabe', + 'kategorie_konto_id' => $kontoId, + 'zahlungskonto_id' => $bank, + 'betrag_netto' => $netto, + 'ust_satz' => $ust, + 'steuerart' => 'normal', + 'buchungstext' => $buchungstext, + ]); + $buchungCount++; + } + + $belegCount++; + $zeilenAnz = count($zeilen); + $zeilenTxt = $zeilenAnz === 1 ? '1 Zeile' : "{$zeilenAnz} Zeilen"; + echo sprintf(" [%02d] %s – %s (%s, %s)\n", $belegCount, $belegNr, $lieferant, $datum, $zeilenTxt); +} + +echo "\n{$belegCount} Eingangsbelege mit {$buchungCount} Buchungszeilen angelegt.\n\n"; + +// ── Erlöskonten für Ausgangsrechnungs-Buchungen ─────────────────────────────── +$forderungen = kontoId('1400'); // Forderungen aus Lieferungen und Leistungen +$erloes19 = kontoId('8000'); // Erlöse 19 % USt +$erloes7 = kontoId('8200'); // Erlöse 7 % (8200 entspricht Mapping im Controller) +$erloes0 = kontoId('8400'); // Erlöse steuerfrei/0 % + +// ── Ausgangsrechnungen ──────────────────────────────────────────────────────── +$rechnungen = [ + [ + 'datum' => '2026-01-31', + 'von' => '2025-12-01', + 'bis' => '2025-12-31', + 'betreff' => 'Unternehmensberatung Dezember 2025 – Strategieentwicklung', + 'status' => 'Bezahlt', + 'notes' => 'Projektnummer: TV-2025-Q4-STRAT', + 'lines' => [ + ['bezeichnung' => 'Strategieberatung und Konzeptentwicklung', 'menge' => 15, 'einheit' => 'h', 'einzelpreis' => 150.00, 'ust_satz' => 19], + ['bezeichnung' => 'Workshop-Moderation (ganztägig)', 'menge' => 2, 'einheit' => 'Tag', 'einzelpreis' => 1200.00, 'ust_satz' => 19], + ['bezeichnung' => 'Ergebnispräsentation und Abschlussbericht', 'menge' => 4, 'einheit' => 'h', 'einzelpreis' => 150.00, 'ust_satz' => 19], + ], + ], + [ + 'datum' => '2026-02-28', + 'von' => '2026-01-01', + 'bis' => '2026-01-31', + 'betreff' => 'Unternehmensberatung Januar 2026 – Change Management', + 'status' => 'Bezahlt', + 'notes' => 'Projektnummer: TV-2026-Q1-CHANGE', + 'lines' => [ + ['bezeichnung' => 'Projektbegleitung Change Management', 'menge' => 20, 'einheit' => 'h', 'einzelpreis' => 150.00, 'ust_satz' => 19], + ['bezeichnung' => 'Stakeholder-Interviews und Analyse', 'menge' => 8, 'einheit' => 'h', 'einzelpreis' => 150.00, 'ust_satz' => 19], + ['bezeichnung' => 'Reisekostenpauschale (weiterberechnet)', 'menge' => 1, 'einheit' => 'Psch.', 'einzelpreis' => 380.00, 'ust_satz' => 19], + ], + ], + [ + 'datum' => '2026-03-25', + 'von' => '2026-02-01', + 'bis' => '2026-02-28', + 'betreff' => 'Unternehmensberatung Februar 2026 – Digitalisierungsstrategie', + 'status' => 'Gesendet', + 'notes' => 'Projektnummer: TV-2026-Q1-DIGIT; Zahlungsziel 30 Tage', + 'lines' => [ + ['bezeichnung' => 'Digitalisierungsberatung und Roadmap-Entwicklung', 'menge' => 25, 'einheit' => 'h', 'einzelpreis' => 150.00, 'ust_satz' => 19], + ['bezeichnung' => 'Kundenpräsentation und Management Summary', 'menge' => 5, 'einheit' => 'h', 'einzelpreis' => 150.00, 'ust_satz' => 19], + ], + ], +]; + +foreach ($rechnungen as $r) { + $invId = Invoice::create([ + 'customer_id' => $customerId, + 'betreff' => $r['betreff'], + 'leistungszeitraum_von' => $r['von'], + 'leistungszeitraum_bis' => $r['bis'], + 'rechnungs_datum' => $r['datum'], + 'notes' => $r['notes'], + 'lines' => $r['lines'], + ]); + + $rechnungsNr = Invoice::finalize($invId); + $inv = Invoice::find($invId); + + // Beleg und Buchungen anlegen (analog InvoiceController::createBelegAndBookings) + $belegNr = Beleg::generateBelegNr(); + $belegId = Beleg::create([ + 'beleg_nr' => $belegNr, + 'filename' => $rechnungsNr . '.pdf', + 'storage_path' => '', + 'mime_type' => 'application/pdf', + 'file_hash' => hash('sha256', $rechnungsNr), + 'beleg_datum' => $inv['rechnungs_datum'], + 'eingangs_datum' => $inv['rechnungs_datum'], + 'rechnungs_nr' => $rechnungsNr, + 'betrag_brutto' => $inv['betrag_brutto'], + 'belegtyp' => 'Ausgangsrechnung', + 'notes' => 'Automatisch erstellt aus Rechnung ' . $rechnungsNr, + ]); + + $lines = Invoice::lines($invId); + $gruppenNetto = []; + foreach ($lines as $line) { + $satz = (float)$line['ust_satz']; + $netto = round((float)$line['menge'] * (float)$line['einzelpreis'], 2); + $gruppenNetto[$satz] = ($gruppenNetto[$satz] ?? 0.0) + $netto; + } + + foreach ($gruppenNetto as $satz => $netto) { + if (abs($satz - 19) < 0.01) $kategorieId = $erloes19; + elseif (abs($satz - 7) < 0.01) $kategorieId = $erloes7; + else $kategorieId = $erloes0; + + Booking::create([ + 'beleg_id' => $belegId, + 'buchungs_datum' => $inv['rechnungs_datum'], + 'buchungstyp' => 'Einnahme', + 'kategorie_konto_id' => $kategorieId, + 'zahlungskonto_id' => $forderungen, + 'betrag_netto' => round($netto, 2), + 'ust_satz' => $satz, + 'buchungstext' => 'Ausgangsrechnung ' . $rechnungsNr, + ]); + } + + Database::execute('UPDATE invoices SET beleg_id=? WHERE id=?', [$belegId, $invId]); + + if ($r['status'] === 'Bezahlt') { + Invoice::markPaid($invId); + } + + $status = $inv['status'] ?? $r['status']; + if ($r['status'] === 'Bezahlt') $status = 'Bezahlt'; + echo sprintf( + " %s | %s | Netto: %s € | Status: %s | Beleg: %s\n", + $rechnungsNr, + $r['datum'], + number_format((float)($inv['betrag_netto'] ?? 0), 2, ',', '.'), + $status, + $belegNr + ); +} + +echo "\n3 Ausgangsrechnungen für Kunde TechVentures GmbH angelegt.\n"; +echo "\nTestdaten erfolgreich erstellt.\n"; diff --git a/tests/09_InvoiceToReportsTest.php b/tests/09_InvoiceToReportsTest.php new file mode 100644 index 0000000..3249b4e --- /dev/null +++ b/tests/09_InvoiceToReportsTest.php @@ -0,0 +1,140 @@ +findAccountId('1400'); // Forderungen + $erloes19Id = $this->findAccountId('8000'); // Erlöse 19 % + + if (!$zahlungsId || !$erloes19Id) { + $this->assert(false, 'Benötigte Konten (1400, 8000) nicht gefunden – SKR03-Seed vorhanden?'); + return; + } + $this->assert(true, 'Konten 1400 und 8000 vorhanden'); + + // ── Kunde und Rechnung anlegen ──────────────────────────────────────── + $customerId = \Steuerkram\Models\Customer::create([ + 'nachname' => 'ReportTest', + 'firma' => 'Report GmbH', + 'strasse' => 'Musterstraße 1', + 'plz' => '10115', + 'ort' => 'Berlin', + 'payment_days' => 14, + ]); + + $invoiceId = \Steuerkram\Models\Invoice::create([ + 'customer_id' => $customerId, + 'betreff' => 'Testrechnung für Report-Integration', + 'rechnungs_datum' => $datum, + 'leistungszeitraum_von' => date('Y-m-01'), + 'leistungszeitraum_bis' => date('Y-m-t'), + 'lines' => [ + [ + 'bezeichnung' => 'Beratungsleistung', + 'menge' => 10, + 'einheit' => 'h', + 'einzelpreis' => 100.00, + 'ust_satz' => 19.0, + ], + ], + ]); + $this->assertGreaterThan(0, $invoiceId, 'Rechnung angelegt'); + + // ── Finalisieren (Modell-Methode, wie in InvoiceController::finalize) ── + $rechnungsNr = \Steuerkram\Models\Invoice::finalize($invoiceId); + $this->assertNotEmpty($rechnungsNr, 'Rechnungsnummer nach Finalisierung vorhanden'); + + $invoice = \Steuerkram\Models\Invoice::find($invoiceId); + $this->assertEquals('Gesendet', $invoice['status'] ?? null, 'Status nach Finalisierung: Gesendet'); + + // ── Beleg und Buchungen anlegen (wie InvoiceController::createBelegAndBookings) ── + $belegNr = \Steuerkram\Models\Beleg::generateBelegNr(); + $belegId = \Steuerkram\Models\Beleg::create([ + 'beleg_nr' => $belegNr, + 'filename' => $rechnungsNr . '.pdf', + 'storage_path' => '', + 'mime_type' => 'application/pdf', + 'file_hash' => hash('sha256', $rechnungsNr), + 'beleg_datum' => $invoice['rechnungs_datum'], + 'eingangs_datum' => $invoice['rechnungs_datum'], + 'rechnungs_nr' => $rechnungsNr, + 'betrag_brutto' => $invoice['betrag_brutto'], + 'belegtyp' => 'Ausgangsrechnung', + 'notes' => 'Test Ausgangsrechnung', + ]); + $this->assertGreaterThan(0, $belegId, 'Beleg angelegt'); + + $lines = \Steuerkram\Models\Invoice::lines($invoiceId); + $this->assertGreaterThan(0, count($lines), 'Rechnungspositionen vorhanden'); + + $gruppenNetto = []; + foreach ($lines as $line) { + $satz = (float)$line['ust_satz']; + $netto = round((float)$line['menge'] * (float)$line['einzelpreis'], 2); + $gruppenNetto[$satz] = ($gruppenNetto[$satz] ?? 0.0) + $netto; + } + + foreach ($gruppenNetto as $satz => $netto) { + $kategorieId = $erloes19Id; // vereinfacht für Test mit 19 % + \Steuerkram\Models\Booking::create([ + 'beleg_id' => $belegId, + 'buchungs_datum' => $invoice['rechnungs_datum'], + 'buchungstyp' => 'Einnahme', + 'kategorie_konto_id' => $kategorieId, + 'zahlungskonto_id' => $zahlungsId, + 'betrag_netto' => round($netto, 2), + 'ust_satz' => $satz, + 'buchungstext' => 'Ausgangsrechnung ' . $rechnungsNr, + ]); + } + + \Steuerkram\Database::execute( + 'UPDATE invoices SET beleg_id=? WHERE id=?', [$belegId, $invoiceId] + ); + $this->assert(true, 'Buchungen und Beleg-Verknüpfung erstellt'); + + // ── EÜR prüfen ─────────────────────────────────────────────────────── + $euer = (new \Steuerkram\Services\EuerCalculator())->calculate($year); + $this->assert( + (float)$euer['total_einnahmen'] >= 1000.00, + 'EÜR: Einnahmen ≥ 1000 € (Ausgangsrechnung eingeflossen)' + ); + + // ── UStVA prüfen ───────────────────────────────────────────────────── + $kz = (new \Steuerkram\Services\UstvaCalculator())->calculate($year, $month); + $this->assert( + (float)$kz['81']['betrag'] >= 1000.00, + 'UStVA Kz 81 (Umsätze 19 %): Betrag ≥ 1000 € (Ausgangsrechnung eingeflossen)' + ); + + // ── Dashboard-Abfrage prüfen ───────────────────────────────────────── + $umsatz = \Steuerkram\Database::fetchOne( + 'SELECT COALESCE(SUM(bk.betrag_netto),0) as total + FROM bookings bk + JOIN fiscal_years fy ON bk.fiscal_year_id = fy.id + WHERE bk.buchungstyp = \'Einnahme\' AND bk.is_reversal = 0 AND fy.year = ?', + [$year] + ); + $this->assert( + (float)($umsatz['total'] ?? 0) >= 1000.00, + 'Dashboard-Abfrage: Umsatz ≥ 1000 € (Ausgangsrechnung eingeflossen)' + ); + } +}