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:
parent
2942b663ec
commit
08b99e3992
2 changed files with 79 additions and 73 deletions
|
|
@ -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']);
|
||||
|
|
|
|||
|
|
@ -39,12 +39,6 @@ $badgeClass = match($invoice['status']) {
|
|||
<?php if ($invoice['status'] !== 'Entwurf'): ?>
|
||||
<a href="/invoices/<?= $h($invoice['id']) ?>/pdf" class="btn btn-primary">PDF herunterladen</a>
|
||||
<?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'): ?>
|
||||
<form method="post" action="/invoices/<?= $h($invoice['id']) ?>/mark-paid">
|
||||
<button type="submit" class="btn btn-success">Als bezahlt markieren</button>
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue