Fix #8: Ausgangsrechnungen erscheinen jetzt in Dashboard, EÜR und UStVA
This commit is contained in:
parent
29295989aa
commit
1950b3c892
3 changed files with 633 additions and 0 deletions
13
Changes.md
13
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
|
||||
|
|
|
|||
480
scripts/seed-testdata.php
Executable file
480
scripts/seed-testdata.php
Executable file
|
|
@ -0,0 +1,480 @@
|
|||
#!/usr/bin/env php
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Testdaten-Seed für Steuerkram
|
||||
*
|
||||
* Erzeugt:
|
||||
* - 1 Kunde (TechVentures GmbH)
|
||||
* - 30 Eingangsbelege mit je 1–2 Buchungszeilen (Jan–Mär 2026)
|
||||
* Schwerpunkt: Reisekosten und Geschäftsessen (freiberuflicher Unternehmensberater)
|
||||
* - 3 Ausgangsrechnungen (Januar, Februar, März 2026)
|
||||
*
|
||||
* Verwendung:
|
||||
* php scripts/seed-testdata.php
|
||||
*/
|
||||
|
||||
$projectRoot = dirname(__DIR__);
|
||||
if (!file_exists($projectRoot . '/vendor/autoload.php')) {
|
||||
fwrite(STDERR, "Fehler: vendor/autoload.php nicht gefunden. Bitte 'composer install' ausführen.\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
require $projectRoot . '/vendor/autoload.php';
|
||||
\Steuerkram\Config::load();
|
||||
|
||||
use Steuerkram\Database;
|
||||
use Steuerkram\Models\Beleg;
|
||||
use Steuerkram\Models\Booking;
|
||||
use Steuerkram\Models\Customer;
|
||||
use Steuerkram\Models\Invoice;
|
||||
|
||||
/** Konto-ID anhand der Kontonummer ermitteln */
|
||||
function kontoId(string $nr): int
|
||||
{
|
||||
$row = Database::fetchOne('SELECT id FROM accounts WHERE kontonummer = ?', [$nr]);
|
||||
if (!$row) {
|
||||
throw new \RuntimeException("Konto {$nr} nicht in der Datenbank gefunden.");
|
||||
}
|
||||
return (int)$row['id'];
|
||||
}
|
||||
|
||||
// ── Konten laden ─────────────────────────────────────────────────────────────
|
||||
$bank = kontoId('1200'); // Bank (Zahlungskonto)
|
||||
$reise = kontoId('4460'); // Reisekosten Arbeitnehmer
|
||||
$bewirt = kontoId('4540'); // Bewirtungskosten
|
||||
$telefon = kontoId('4630'); // Telefon
|
||||
$inet = kontoId('4640'); // Internetkosten
|
||||
$buerob = kontoId('4600'); // Bürobedarf
|
||||
$edv = kontoId('4660'); // EDV-Kosten (Software, Lizenzen)
|
||||
$fortb = kontoId('4670'); // Betriebliche Fortbildungskosten
|
||||
$fachlit = kontoId('4680'); // Fachliteratur
|
||||
echo "Konten geladen.\n";
|
||||
|
||||
// ── Kunde anlegen ─────────────────────────────────────────────────────────────
|
||||
$customerId = Customer::create([
|
||||
'firma' => '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";
|
||||
140
tests/09_InvoiceToReportsTest.php
Normal file
140
tests/09_InvoiceToReportsTest.php
Normal file
|
|
@ -0,0 +1,140 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Test 9: Ausgangsrechnung → Dashboard / EÜR / UStVA.
|
||||
*
|
||||
* Prüft den vollständigen Pfad: Ausgangsrechnung anlegen, finalisieren,
|
||||
* Buchung erzeugen und sicherstellen, dass die Werte im Dashboard,
|
||||
* in der EÜR und in der UStVA erscheinen.
|
||||
*/
|
||||
class InvoiceToReportsTest extends TestCase
|
||||
{
|
||||
protected string $testName = 'Test 9: Ausgangsrechnung → Dashboard/EÜR/UStVA';
|
||||
|
||||
public function run(): void
|
||||
{
|
||||
$year = (int)date('Y');
|
||||
$month = (int)date('n');
|
||||
$datum = date('Y-m-d');
|
||||
|
||||
// ── Konten prüfen ────────────────────────────────────────────────────
|
||||
$zahlungsId = $this->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)'
|
||||
);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue