Steuerkram/scripts/seed-testdata.php

480 lines
20 KiB
PHP
Executable file
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/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";