Fix #2: Ausgangsrechnung automatisch buchen beim Finalisieren

Beim Finalisieren einer Rechnung wurde bisher kein Beleg und keine
Buchungszeilen erstellt – der Nutzer musste danach manuell "Buchen"
klicken, was nicht offensichtlich war.

- finalize() erstellt jetzt atomar Beleg + Buchungszeilen (via
  createBelegAndBookings) innerhalb derselben Transaktion
- Buchungslogik aus book() in private Methode extrahiert (DRY),
  book()-Route bleibt für bereits finalisierte, noch ungebuchte
  Rechnungen erhalten
- "Buchen"-Button in der Show-View entfernt (nicht mehr nötig)
This commit is contained in:
fds2610 2026-03-25 21:11:30 +01:00
parent 2942b663ec
commit 08b99e3992
2 changed files with 79 additions and 73 deletions

View file

@ -169,12 +169,18 @@ class InvoiceController
{ {
$id = (int)$params['id']; $id = (int)$params['id'];
try { try {
\Steuerkram\Database::beginTransaction();
$nr = Invoice::finalize($id); $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) { } catch (\PDOException $e) {
\Steuerkram\Database::rollBack();
error_log('[Steuerkram] PDO-Fehler in InvoiceController::finalize(): ' . $e->getMessage()); error_log('[Steuerkram] PDO-Fehler in InvoiceController::finalize(): ' . $e->getMessage());
View::redirect("/invoices/{$id}", 'Datenbankfehler beim Finalisieren der Rechnung.', 'error'); View::redirect("/invoices/{$id}", 'Datenbankfehler beim Finalisieren der Rechnung.', 'error');
} catch (\Exception $e) { } catch (\Exception $e) {
\Steuerkram\Database::rollBack();
View::redirect("/invoices/{$id}", $e->getMessage(), 'error'); View::redirect("/invoices/{$id}", $e->getMessage(), 'error');
} }
} }
@ -208,8 +214,23 @@ class InvoiceController
try { try {
\Steuerkram\Database::beginTransaction(); \Steuerkram\Database::beginTransaction();
$belegNr = $this->createBelegAndBookings($invoice);
\Steuerkram\Database::commit();
View::redirect("/invoices/{$id}", 'Rechnung erfolgreich gebucht (Beleg ' . $belegNr . ').');
} catch (\PDOException $e) {
\Steuerkram\Database::rollBack();
error_log('[Steuerkram] PDO-Fehler in InvoiceController::book(): ' . $e->getMessage());
View::redirect("/invoices/{$id}", 'Datenbankfehler beim Buchen der Rechnung.', 'error');
} catch (\Exception $e) {
\Steuerkram\Database::rollBack();
View::redirect("/invoices/{$id}", $e->getMessage(), 'error');
}
}
private function createBelegAndBookings(array $invoice): string
{
$id = (int)$invoice['id'];
// Create Beleg for this invoice
$belegNr = Beleg::generateBelegNr(); $belegNr = Beleg::generateBelegNr();
$belegId = Beleg::create([ $belegId = Beleg::create([
'beleg_nr' => $belegNr, 'beleg_nr' => $belegNr,
@ -228,8 +249,10 @@ class InvoiceController
$debitorKonto = \Steuerkram\Database::fetchOne( $debitorKonto = \Steuerkram\Database::fetchOne(
'SELECT id FROM accounts WHERE kontonummer = ?', ['1400'] 'SELECT id FROM accounts WHERE kontonummer = ?', ['1400']
); );
if (!$debitorKonto) {
throw new \RuntimeException('Debitorenkonto 1400 nicht gefunden.');
}
// Erlöse-Konten nach USt-Satz: 8000 (19%), 8200 (7%), 8400 (0%/steuerfrei)
$erloesKonten = []; $erloesKonten = [];
foreach (\Steuerkram\Database::fetchAll( foreach (\Steuerkram\Database::fetchAll(
"SELECT id, kontonummer FROM accounts WHERE kontonummer IN ('8000','8200','8400')" "SELECT id, kontonummer FROM accounts WHERE kontonummer IN ('8000','8200','8400')"
@ -237,7 +260,6 @@ class InvoiceController
$erloesKonten[$row['kontonummer']] = $row['id']; $erloesKonten[$row['kontonummer']] = $row['id'];
} }
// Buchungen pro USt-Satz aus den Rechnungszeilen
$lines = Invoice::lines($id); $lines = Invoice::lines($id);
$gruppenNetto = []; $gruppenNetto = [];
foreach ($lines as $line) { foreach ($lines as $line) {
@ -269,21 +291,11 @@ class InvoiceController
]); ]);
} }
// Link beleg to invoice
\Steuerkram\Database::execute( \Steuerkram\Database::execute(
'UPDATE invoices SET beleg_id=? WHERE id=?', [$belegId, $id] 'UPDATE invoices SET beleg_id=? WHERE id=?', [$belegId, $id]
); );
\Steuerkram\Database::commit(); return $belegNr;
View::redirect("/invoices/{$id}", 'Rechnung erfolgreich gebucht (Beleg ' . $belegNr . ').');
} catch (\PDOException $e) {
\Steuerkram\Database::rollBack();
error_log('[Steuerkram] PDO-Fehler in InvoiceController::book(): ' . $e->getMessage());
View::redirect("/invoices/{$id}", 'Datenbankfehler beim Buchen der Rechnung.', 'error');
} catch (\Exception $e) {
\Steuerkram\Database::rollBack();
View::redirect("/invoices/{$id}", $e->getMessage(), 'error');
}
} }
public function markPaid(array $params): void public function markPaid(array $params): void

View file

@ -39,12 +39,6 @@ $badgeClass = match($invoice['status']) {
<?php if ($invoice['status'] !== 'Entwurf'): ?> <?php if ($invoice['status'] !== 'Entwurf'): ?>
<a href="/invoices/<?= $h($invoice['id']) ?>/pdf" class="btn btn-primary">PDF herunterladen</a> <a href="/invoices/<?= $h($invoice['id']) ?>/pdf" class="btn btn-primary">PDF herunterladen</a>
<?php endif; ?> <?php endif; ?>
<?php if ($invoice['status'] === 'Gesendet' && empty($invoice['beleg_id'])): ?>
<form method="post" action="/invoices/<?= $h($invoice['id']) ?>/book"
data-confirm="Rechnung jetzt buchen?">
<button type="submit" class="btn btn-success">Buchen</button>
</form>
<?php endif; ?>
<?php if ($invoice['status'] === 'Gesendet'): ?> <?php if ($invoice['status'] === 'Gesendet'): ?>
<form method="post" action="/invoices/<?= $h($invoice['id']) ?>/mark-paid"> <form method="post" action="/invoices/<?= $h($invoice['id']) ?>/mark-paid">
<button type="submit" class="btn btn-success">Als bezahlt markieren</button> <button type="submit" class="btn btn-success">Als bezahlt markieren</button>