diff --git a/src/Controllers/InvoiceController.php b/src/Controllers/InvoiceController.php index 83c6553..96b3349 100644 --- a/src/Controllers/InvoiceController.php +++ b/src/Controllers/InvoiceController.php @@ -169,12 +169,18 @@ class InvoiceController { $id = (int)$params['id']; try { + \Steuerkram\Database::beginTransaction(); $nr = Invoice::finalize($id); - View::redirect("/invoices/{$id}", "Rechnung {$nr} wurde finalisiert."); + $invoice = Invoice::find($id); + $belegNr = $this->createBelegAndBookings($invoice); + \Steuerkram\Database::commit(); + View::redirect("/invoices/{$id}", "Rechnung {$nr} wurde finalisiert und gebucht (Beleg {$belegNr})."); } catch (\PDOException $e) { + \Steuerkram\Database::rollBack(); error_log('[Steuerkram] PDO-Fehler in InvoiceController::finalize(): ' . $e->getMessage()); View::redirect("/invoices/{$id}", 'Datenbankfehler beim Finalisieren der Rechnung.', 'error'); } catch (\Exception $e) { + \Steuerkram\Database::rollBack(); View::redirect("/invoices/{$id}", $e->getMessage(), 'error'); } } @@ -208,72 +214,7 @@ class InvoiceController try { \Steuerkram\Database::beginTransaction(); - - // Create Beleg for this invoice - $belegNr = Beleg::generateBelegNr(); - $belegId = Beleg::create([ - 'beleg_nr' => $belegNr, - 'filename' => $invoice['rechnungs_nr'] . '.pdf', - 'storage_path' => '', // no physical file for outgoing invoices - 'mime_type' => 'application/pdf', - 'file_hash' => hash('sha256', $invoice['rechnungs_nr']), - 'beleg_datum' => $invoice['rechnungs_datum'], - 'eingangs_datum' => $invoice['rechnungs_datum'], - 'rechnungs_nr' => $invoice['rechnungs_nr'], - 'betrag_brutto' => $invoice['betrag_brutto'], - 'belegtyp' => 'Ausgangsrechnung', - 'notes' => 'Automatisch erstellt aus Rechnung ' . $invoice['rechnungs_nr'], - ]); - - $debitorKonto = \Steuerkram\Database::fetchOne( - 'SELECT id FROM accounts WHERE kontonummer = ?', ['1400'] - ); - - // Erlöse-Konten nach USt-Satz: 8000 (19%), 8200 (7%), 8400 (0%/steuerfrei) - $erloesKonten = []; - foreach (\Steuerkram\Database::fetchAll( - "SELECT id, kontonummer FROM accounts WHERE kontonummer IN ('8000','8200','8400')" - ) as $row) { - $erloesKonten[$row['kontonummer']] = $row['id']; - } - - // Buchungen pro USt-Satz aus den Rechnungszeilen - $lines = Invoice::lines($id); - $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) { - $habenId = $erloesKonten['8000'] ?? null; - } elseif (abs($satz - 7) < 0.01) { - $habenId = $erloesKonten['8200'] ?? null; - } else { - $habenId = $erloesKonten['8400'] ?? null; - } - if (!$habenId) { - throw new \RuntimeException("Erlöskonto für USt-Satz {$satz}% nicht gefunden."); - } - - Booking::create([ - 'beleg_id' => $belegId, - 'buchungs_datum' => $invoice['rechnungs_datum'], - 'soll_konto_id' => $debitorKonto['id'], - 'haben_konto_id' => $habenId, - 'betrag_netto' => round($netto, 2), - 'ust_satz' => $satz, - 'buchungstext' => 'Ausgangsrechnung ' . $invoice['rechnungs_nr'], - ]); - } - - // Link beleg to invoice - \Steuerkram\Database::execute( - 'UPDATE invoices SET beleg_id=? WHERE id=?', [$belegId, $id] - ); - + $belegNr = $this->createBelegAndBookings($invoice); \Steuerkram\Database::commit(); View::redirect("/invoices/{$id}", 'Rechnung erfolgreich gebucht (Beleg ' . $belegNr . ').'); } catch (\PDOException $e) { @@ -286,6 +227,77 @@ class InvoiceController } } + private function createBelegAndBookings(array $invoice): string + { + $id = (int)$invoice['id']; + + $belegNr = Beleg::generateBelegNr(); + $belegId = Beleg::create([ + 'beleg_nr' => $belegNr, + 'filename' => $invoice['rechnungs_nr'] . '.pdf', + 'storage_path' => '', // no physical file for outgoing invoices + 'mime_type' => 'application/pdf', + 'file_hash' => hash('sha256', $invoice['rechnungs_nr']), + 'beleg_datum' => $invoice['rechnungs_datum'], + 'eingangs_datum' => $invoice['rechnungs_datum'], + 'rechnungs_nr' => $invoice['rechnungs_nr'], + 'betrag_brutto' => $invoice['betrag_brutto'], + 'belegtyp' => 'Ausgangsrechnung', + 'notes' => 'Automatisch erstellt aus Rechnung ' . $invoice['rechnungs_nr'], + ]); + + $debitorKonto = \Steuerkram\Database::fetchOne( + 'SELECT id FROM accounts WHERE kontonummer = ?', ['1400'] + ); + if (!$debitorKonto) { + throw new \RuntimeException('Debitorenkonto 1400 nicht gefunden.'); + } + + $erloesKonten = []; + foreach (\Steuerkram\Database::fetchAll( + "SELECT id, kontonummer FROM accounts WHERE kontonummer IN ('8000','8200','8400')" + ) as $row) { + $erloesKonten[$row['kontonummer']] = $row['id']; + } + + $lines = Invoice::lines($id); + $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) { + $habenId = $erloesKonten['8000'] ?? null; + } elseif (abs($satz - 7) < 0.01) { + $habenId = $erloesKonten['8200'] ?? null; + } else { + $habenId = $erloesKonten['8400'] ?? null; + } + if (!$habenId) { + throw new \RuntimeException("Erlöskonto für USt-Satz {$satz}% nicht gefunden."); + } + + Booking::create([ + 'beleg_id' => $belegId, + 'buchungs_datum' => $invoice['rechnungs_datum'], + 'soll_konto_id' => $debitorKonto['id'], + 'haben_konto_id' => $habenId, + 'betrag_netto' => round($netto, 2), + 'ust_satz' => $satz, + 'buchungstext' => 'Ausgangsrechnung ' . $invoice['rechnungs_nr'], + ]); + } + + \Steuerkram\Database::execute( + 'UPDATE invoices SET beleg_id=? WHERE id=?', [$belegId, $id] + ); + + return $belegNr; + } + public function markPaid(array $params): void { Invoice::markPaid((int)$params['id']); diff --git a/src/Views/invoices/show.php b/src/Views/invoices/show.php index 344b281..baab101 100644 --- a/src/Views/invoices/show.php +++ b/src/Views/invoices/show.php @@ -39,12 +39,6 @@ $badgeClass = match($invoice['status']) { PDF herunterladen - -
- -
-