Steuerkram/src/Views/invoices/show.php

587 lines
31 KiB
PHP
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.

<?php
use Steuerkram\View;
$h = fn($v) => View::h($v);
$fmt = fn($v) => View::formatMoney($v);
$customerName = !empty($invoice['firma'])
? $invoice['firma']
: trim(($invoice['anrede'] ?? '') . ' ' . ($invoice['vorname'] ?? '') . ' ' . $invoice['nachname']);
$lzHtml = $h($invoice['leistungszeitraum'] ?? '');
$xmlHinweise = $xmlHinweise ?? [];
$xmlTooltip = empty($xmlHinweise)
? 'XML-Rechnung (EN 16931 / XRechnung 3.0) herunterladen'
: 'XML-Rechnung (EN 16931 / XRechnung 3.0) herunterladen – es fehlen Angaben, siehe Hinweis oben';
$badgeClass = match($invoice['status']) {
'Bezahlt' => 'badge-green',
'Storniert' => 'badge-red',
'Gesendet' => 'badge-yellow',
'Finalisiert' => 'badge-blue',
'Teilzahlung' => 'badge-orange',
'Überzahlt' => 'badge-purple',
default => 'badge-gray',
};
?>
<!-- Querverweise Storno -->
<?php if (!empty($originalrechnung)): ?>
<div class="alert alert-warning" style="margin-bottom:.75rem;padding:.6rem 1rem;background:#fff3cd;border-left:3px solid #f59e0b;font-size:.8rem">
Stornorechnung zu: <a href="/invoices/<?= $h($originalrechnung['id']) ?>"><strong><?= $h($originalrechnung['rechnungs_nr']) ?></strong></a>
</div>
<?php endif; ?>
<?php if (!empty($stornorechnung)): ?>
<div class="alert alert-info" style="margin-bottom:.75rem;padding:.6rem 1rem;background:#e0f2fe;border-left:3px solid #0284c7;font-size:.8rem">
Wurde storniert durch: <a href="/invoices/<?= $h($stornorechnung['id']) ?>"><strong><?= $h($stornorechnung['rechnungs_nr']) ?></strong></a>
</div>
<?php endif; ?>
<!-- Hinweis auf fehlende Daten für die XML-Rechnung (EN 16931) -->
<?php if (!empty($xmlHinweise)): ?>
<div class="alert alert-warning" style="margin-bottom:.75rem;padding:.6rem 1rem;background:#fff3cd;border-left:3px solid #f59e0b;font-size:.8rem">
<strong>XML-Rechnung (EN 16931 / XRechnung): unvollständige Daten.</strong>
Der Download ist trotzdem möglich, die Datei erfüllt die Norm aber nur eingeschränkt.
<?php foreach ($xmlHinweise as $bereich => $felder): ?>
<div style="margin-top:.4rem">
<em><?= $h($bereich) ?>:</em>
<ul style="margin:.2rem 0 0 1.2rem">
<?php foreach ($felder as $feld): ?>
<li><?= $h($feld) ?></li>
<?php endforeach; ?>
</ul>
</div>
<?php endforeach; ?>
<?php if (isset($xmlHinweise['Stammdaten'])): ?>
<div style="margin-top:.4rem">Stammdaten pflegen unter <a href="/settings">Einstellungen</a>.</div>
<?php endif; ?>
</div>
<?php endif; ?>
<!-- Aktionsleiste -->
<div class="toolbar mb-2">
<div class="toolbar-left">
<span class="badge <?= $badgeClass ?>" style="font-size:.7rem;padding:.3rem .8rem"><?= $h($invoice['status']) ?></span>
</div>
<div class="toolbar-right" style="gap:.5rem">
<?php if ($invoice['status'] === 'Entwurf'): ?>
<a href="/invoices/<?= $h($invoice['id']) ?>/edit"
style="background:none;border:none;cursor:pointer;color:var(--color-muted);padding:.2rem;line-height:1;display:inline-flex;align-items:center"
title="Bearbeiten">
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24"
fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/>
<path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"/>
</svg>
</a>
<form method="post" action="/invoices/<?= $h($invoice['id']) ?>/finalize"
data-confirm="Rechnung finalisieren? Danach kann sie nicht mehr bearbeitet werden.">
<button type="submit" class="btn btn-primary">Finalisieren &amp; Nummerieren</button>
</form>
<?php endif; ?>
<?php if ($invoice['status'] !== 'Entwurf'): ?>
<a href="/invoices/<?= $h($invoice['id']) ?>/pdf"
style="background:none;border:none;cursor:pointer;color:var(--color-muted);padding:.2rem;line-height:1;display:inline-flex;align-items:center"
title="PDF ansehen">
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24"
fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<rect x="2" y="4" width="20" height="16" rx="2"/>
<path d="M5 12s2.5-4 7-4 7 4 7 4-2.5 4-7 4-7-4-7-4z"/>
<circle cx="12" cy="12" r="1.5"/>
</svg>
</a>
<?php endif; ?>
<a href="/invoices/<?= $h($invoice['id']) ?>/xml"
style="background:none;border:none;cursor:pointer;color:<?= empty($xmlHinweise) ? 'var(--color-muted)' : 'var(--color-warning)' ?>;padding:.2rem;line-height:1;display:inline-flex;align-items:center"
title="<?= $h($xmlTooltip) ?>">
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24"
fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/>
<polyline points="14 2 14 8 20 8"/>
<text x="12" y="18.5" text-anchor="middle" font-size="6.5" font-family="sans-serif"
font-weight="700" fill="currentColor" stroke="none">XML</text>
</svg>
</a>
<?php if (!in_array($invoice['status'], ['Entwurf', 'Storniert'])): ?>
<button type="button" class="btn btn-primary"
onclick="document.getElementById('send-modal').style.display='flex'">Per E-Mail senden</button>
<?php endif; ?>
<?php if (!in_array($invoice['status'], ['Entwurf', 'Storniert']) && !empty($invoice['beleg_id'])): ?>
<button type="button" class="btn btn-success"
onclick="document.getElementById('buchungs-modal').style.display='flex'">Zahlungen</button>
<?php endif; ?>
<?php if (in_array($invoice['status'], ['Entwurf','Finalisiert','Gesendet']) && empty($invoice['storno_of_id'])): ?>
<form method="post" action="/invoices/<?= $h($invoice['id']) ?>/cancel"
data-confirm="Rechnung stornieren und Stornorechnung erstellen?">
<button type="submit" class="btn btn-danger">Stornieren</button>
</form>
<?php endif; ?>
</div>
</div>
<!-- Rechnungsvorschau (DIN 5008 ähnlich) -->
<div id="invoice-pages">
<div class="invoice-preview">
<div class="invoice-page-body">
<!-- Logo -->
<?php if (!empty($settings['logo_path'])): ?>
<?php
$logoMaxW = max(50, (int)($settings['logo_max_width'] ?? 600));
$logoMaxH = max(20, (int)($settings['logo_max_height'] ?? 100));
$logoPos = $settings['logo_position'] ?? 'center';
$logoMSide = max(0, (int)($settings['logo_margin_side'] ?? 75));
$logoMTop = max(0, (int)($settings['logo_margin_top'] ?? 6));
if ($logoPos === 'left') {
$logoWrapStyle = "text-align:left;margin-left:{$logoMSide}px;margin-top:{$logoMTop}px;margin-bottom:1.5rem";
} elseif ($logoPos === 'right') {
$logoWrapStyle = "text-align:right;margin-right:{$logoMSide}px;margin-top:{$logoMTop}px;margin-bottom:1.5rem";
} else {
$logoWrapStyle = 'text-align:center;margin-bottom:1.5rem';
}
?>
<div style="<?= $logoWrapStyle ?>">
<img src="/settings/logo/file" alt="Logo"
style="max-width:<?= $logoMaxW ?>px;max-height:<?= $logoMaxH ?>px">
</div>
<?php endif; ?>
<!-- Adressbereich (DIN 5008: 50% = 85mm bei vollem A4-Preview) -->
<div class="invoice-address" style="margin-top:1cm;margin-bottom:1.5rem">
<div>
<div style="font-size:6pt;color:#666;border-bottom:1px solid #ccc;padding-bottom:2px;margin-bottom:12px">
<?= $h($settings['firma'] ?? '') ?> · <?= $h($settings['strasse'] ?? '') ?> · <?= $h(($settings['plz'] ?? '') . ' ' . ($settings['ort'] ?? '')) ?>
</div>
<div style="line-height:1.5;font-size:10pt">
<?= $h($customerName) ?><br>
<?= $h($invoice['strasse']) ?><br>
<?= $h($invoice['plz']) ?> <?= $h($invoice['ort']) ?>
<?php if ($invoice['land'] !== 'DE'): ?><br><?= $h($invoice['land']) ?><?php endif; ?>
</div>
</div>
<div style="text-align:right;font-size:17pt">
<?= !empty($invoice['storno_of_id']) ? 'STORNORECHNUNG' : 'Rechnung' ?>
<?php if (!empty($invoice['storno_of_id']) && !empty($originalrechnung)): ?>
<div style="font-size:.65rem;margin-top:.3rem;font-weight:normal">
zu Rechnung Nr. <strong><?= $h($originalrechnung['rechnungs_nr']) ?></strong>
vom <?= $h(View::formatDate($originalrechnung['rechnungs_datum'] ?? '')) ?>
</div>
<?php endif; ?>
</div>
</div>
<!-- Meta-Tabelle -->
<table style="width:100%;table-layout:fixed;border-collapse:collapse;margin-top:2cm;margin-bottom:1.5rem;font-size:.7rem">
<thead>
<tr>
<th style="width:20%;padding:.4rem .5rem;text-align:left;font-weight:bold">Betreff</th>
<th style="width:20%;padding:.4rem .5rem;text-align:left;font-weight:bold">Rechnungsnummer</th>
<th style="width:20%;padding:.4rem .5rem;text-align:left;font-weight:bold">Kundennummer</th>
<th style="width:20%;padding:.4rem .5rem;text-align:left;font-weight:bold">Leistungszeitraum</th>
<th style="width:20%;padding:.4rem .5rem;text-align:right;font-weight:bold">Datum</th>
</tr>
</thead>
<tbody>
<tr>
<td style="padding:.4rem .5rem;white-space:nowrap"><?= $h($invoice['betreff']) ?></td>
<td style="padding:.4rem .5rem;white-space:nowrap;font-weight:700"><?= $h($invoice['rechnungs_nr'] ?? 'Entwurf') ?></td>
<td style="padding:.4rem .5rem;white-space:nowrap"><?= $h($invoice['kundennummer']) ?></td>
<td style="padding:.4rem .5rem"><?= $lzHtml ?: '–' ?></td>
<td style="padding:.4rem .5rem;text-align:right"><?= $h(View::formatDate($invoice['rechnungs_datum'])) ?></td>
</tr>
</tbody>
</table>
<!-- Positionen -->
<table id="invoice-pos-table" style="width:100%;table-layout:fixed;border-collapse:collapse;margin-bottom:1.5rem;font-size:.7rem">
<thead>
<tr style="background:#f0f0f0">
<th style="padding:.5rem;text-align:left;width:6%">Pos.</th>
<th style="padding:.5rem;text-align:left">Bezeichnung</th>
<th style="padding:.5rem;text-align:right;width:11%">Menge</th>
<th style="padding:.5rem;text-align:left;width:10%">Einheit</th>
<th style="padding:.5rem;text-align:right;width:8%">USt.</th>
<th style="padding:.5rem;text-align:right;width:15%">Einzelpreis</th>
<th style="padding:.5rem;text-align:right;width:16%">Gesamt</th>
</tr>
</thead>
<tbody>
<?php foreach ($lines as $line): ?>
<?php if (($line['type'] ?? 'position') === 'heading'): ?>
<tr style="border-bottom:1px solid #eee">
<td colspan="7" style="padding:.4rem .5rem;font-weight:700"><?= nl2br($h($line['bezeichnung'])) ?></td>
</tr>
<?php else: ?>
<tr style="border-bottom:1px solid #eee">
<td style="padding:.4rem .5rem"><?= $h($line['position']) ?></td>
<td style="padding:.4rem .5rem"><?= nl2br($h($line['bezeichnung'])) ?></td>
<td style="padding:.4rem .5rem;text-align:right"><?= number_format((float)$line['menge'], 1, ',', '.') ?></td>
<td style="padding:.4rem .5rem"><?= $h($line['einheit']) ?></td>
<td style="padding:.4rem .5rem;text-align:right"><?= number_format((float)$line['ust_satz'], 0) ?> %</td>
<td style="padding:.4rem .5rem;text-align:right"><?= $fmt($line['einzelpreis']) ?></td>
<td style="padding:.4rem .5rem;text-align:right;font-weight:600"><?= $fmt($line['betrag_netto']) ?></td>
</tr>
<?php endif; ?>
<?php endforeach; ?>
</tbody>
</table>
<!-- Summen + Zahlungshinweis -->
<div id="invoice-summary">
<div style="display:flex;justify-content:flex-end;margin-bottom:1.5rem;padding-right:.5rem">
<table style="width:auto">
<tr><td style="padding:.3rem 0">Nettobetrag:</td><td style="text-align:right;padding:.3rem 0;width:7.5em"><?= $fmt($invoice['betrag_netto']) ?></td></tr>
<tr><td style="padding:.3rem 0">zzgl. Umsatzsteuer:</td><td style="text-align:right;padding:.3rem 0"><?= $fmt($invoice['betrag_ust']) ?></td></tr>
<tr style="font-weight:700;font-size:.82rem">
<td style="padding:.5rem 0">Rechnungsbetrag:</td>
<td style="text-align:right;padding:.5rem 0"><?= $fmt($invoice['betrag_brutto']) ?></td>
</tr>
</table>
</div>
<?php if (empty($invoice['storno_of_id'])): ?>
<div style="background:#f0f4ff;border-left:3px solid #2563eb;padding:.75rem 1rem;margin-bottom:2rem;font-size:.7rem">
Bitte überweisen Sie den Rechnungsbetrag innerhalb <?= $h($invoice['payment_days'] ?? 14) ?> Tagen auf das unten genannte Konto.
<strong>Fällig am: <?= View::formatDate($invoice['faellig_datum']) ?></strong>
</div>
<?php endif; ?>
</div>
</div><!-- /.invoice-page-body -->
<!-- Fußzeile -->
<div class="invoice-footer-bar">
<div style="border-top:1px solid #ccc;padding-top:.75rem;display:grid;grid-template-columns:1fr 1fr 1fr auto;gap:1rem;font-size:.58rem;color:#555">
<div style="min-width:0">
<strong><?= $h($settings['firma'] ?? '') ?></strong><br>
<?php if (trim((string)($settings['inhaber'] ?? '')) !== ''): ?>Inh. <?= $h($settings['inhaber']) ?><br><?php endif; ?>
<?= $h($settings['strasse'] ?? '') ?><br>
<?= $h(($settings['plz'] ?? '') . ' ' . ($settings['ort'] ?? '')) ?><br>
USt-ID: <?= $h($settings['ust_id'] ?? '') ?>
</div>
<div style="white-space:nowrap">
Tel: <?= $h($settings['telefon'] ?? '') ?><br>
E-Mail: <?= $h($settings['email'] ?? '') ?><br>
Web: <?= $h($settings['website'] ?? '') ?>
</div>
<div style="white-space:nowrap">
<?= $h($settings['bank_name'] ?? '') ?><br>
IBAN: <?= $h($settings['iban'] ?? '') ?><br>
BIC: <?= $h($settings['bic'] ?? '') ?>
</div>
<div style="text-align:right;white-space:nowrap">Seite <span class="page-num">1</span> von <span class="page-total">1</span></div>
</div>
</div>
</div><!-- /.invoice-preview -->
</div><!-- /#invoice-pages -->
<script>
(function () {
'use strict';
// 1cm in px: Referenzelement messen, damit Browserzoom berücksichtigt wird
function getCmPx() {
var d = document.createElement('div');
d.style.cssText = 'position:absolute;visibility:hidden;width:1cm';
document.body.appendChild(d);
var px = d.offsetWidth;
document.body.removeChild(d);
return px;
}
function paginate() {
var wrap = document.getElementById('invoice-pages');
var page1 = wrap.querySelector('.invoice-preview');
var body1 = page1.querySelector('.invoice-page-body');
var footer1 = page1.querySelector('.invoice-footer-bar');
var posTable = document.getElementById('invoice-pos-table');
var summary = document.getElementById('invoice-summary');
if (!posTable || !summary) return;
var cmPx = getCmPx();
var a4H = 29.7 * cmPx;
var padTop = parseFloat(getComputedStyle(page1).paddingTop);
var padBot = parseFloat(getComputedStyle(page1).paddingBottom);
var footerH = footer1.offsetHeight;
var availH = a4H - padTop - padBot - footerH;
// Passt alles auf Seite 1?
if (body1.offsetHeight <= availH) {
return;
}
// Alle Zeilen sichern, tbody leeren
var tbody = posTable.querySelector('tbody');
var rows = Array.from(tbody.querySelectorAll('tr'));
rows.forEach(function (r) { tbody.removeChild(r); });
// summary aus body1 herauslösen
body1.removeChild(summary);
// Hilfsfunktionen
function makeFooter() {
var f = footer1.cloneNode(true);
return f;
}
function makePage() {
var p = document.createElement('div');
p.className = 'invoice-preview';
var b = document.createElement('div');
b.className = 'invoice-page-body';
p.appendChild(b);
p.appendChild(makeFooter());
wrap.appendChild(p);
return b;
}
function makeContTable(targetBody) {
var tbl = posTable.cloneNode(false);
tbl.removeAttribute('id');
var thead = posTable.querySelector('thead').cloneNode(true);
var tb = document.createElement('tbody');
tbl.appendChild(thead);
tbl.appendChild(tb);
targetBody.appendChild(tbl);
return tb;
}
// Zeilen seitenweise verteilen
var pages = [page1];
var currentBody = body1;
var currentTbody = tbody; // zeigt auf tbody in posTable auf Seite 1
for (var i = 0; i < rows.length; i++) {
currentTbody.appendChild(rows[i]);
var currentPage = pages[pages.length - 1];
var usedH = currentBody.offsetHeight;
if (usedH > availH) {
// Zeile wieder entfernen, neue Seite beginnen
currentTbody.removeChild(rows[i]);
var newBody = makePage();
pages.push(newBody.parentNode); // .invoice-preview
currentBody = newBody;
currentTbody = makeContTable(newBody);
currentTbody.appendChild(rows[i]);
}
}
// summary auf letzte Seite; falls dort kein Platz, nochmal neue Seite
currentBody.appendChild(summary);
var lastPage = pages[pages.length - 1];
if (currentBody.offsetHeight > availH) {
var extraBody = makePage();
pages.push(extraBody.parentNode);
extraBody.appendChild(summary);
}
// Seitenzahlen aktualisieren
var total = wrap.querySelectorAll('.invoice-preview').length;
wrap.querySelectorAll('.page-num').forEach(function (el, i) {
el.textContent = String(i + 1);
});
wrap.querySelectorAll('.page-total').forEach(function (el) {
el.textContent = String(total);
});
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', paginate);
} else {
paginate();
}
}());
</script>
<!-- E-Mail-Senden-Modal -->
<div id="send-modal" style="display:none;position:fixed;inset:0;background:rgba(0,0,0,.45);z-index:1000;align-items:center;justify-content:center">
<div style="background:#fff;border-radius:8px;padding:1.5rem;width:420px;max-width:95vw;box-shadow:0 8px 32px rgba(0,0,0,.2)">
<h3 style="margin:0 0 1rem">Rechnung per E-Mail senden</h3>
<form method="post" action="/invoices/<?= $h($invoice['id']) ?>/send">
<div class="form-group">
<label>Empfänger *</label>
<input type="email" name="email_to" required
value="<?= $h($invoice['email'] ?? '') ?>"
placeholder="empfaenger@example.com">
</div>
<div class="form-group">
<label>CC 1 <span style="font-weight:normal;color:#888">(optional)</span></label>
<input type="email" name="email_cc1" placeholder="cc1@example.com"
value="<?= $h($customer['email_cc1'] ?? '') ?>">
</div>
<div class="form-group">
<label>CC 2 <span style="font-weight:normal;color:#888">(optional)</span></label>
<input type="email" name="email_cc2" placeholder="cc2@example.com"
value="<?= $h($customer['email_cc2'] ?? '') ?>">
</div>
<div style="display:flex;gap:.75rem;justify-content:flex-end;margin-top:1.25rem">
<button type="button" class="btn btn-secondary"
onclick="document.getElementById('send-modal').style.display='none'">Abbrechen</button>
<button type="submit" class="btn btn-primary">Senden</button>
</div>
</form>
</div>
</div>
<!-- Zahlungs-Modal -->
<?php if (!in_array($invoice['status'], ['Entwurf', 'Storniert']) && !empty($invoice['beleg_id'])): ?>
<?php
$kontenGruppen = [];
foreach ($accounts as $acc) { $kontenGruppen[$acc['kontenart']][] = $acc; }
?>
<div id="buchungs-modal" style="display:none;position:fixed;inset:0;background:rgba(0,0,0,.45);z-index:1000;align-items:flex-start;justify-content:center;padding:2rem 1rem;overflow-y:auto">
<div style="background:#fff;border-radius:8px;width:100%;max-width:720px;box-shadow:0 8px 32px rgba(0,0,0,.2)">
<div style="display:flex;align-items:center;justify-content:space-between;padding:1rem 1.25rem;border-bottom:1px solid #e5e7eb">
<h3 style="margin:0">Zahlungen – <?= $h($invoice['rechnungs_nr']) ?></h3>
<button onclick="document.getElementById('buchungs-modal').style.display='none'"
style="background:none;border:none;font-size:1.5rem;cursor:pointer;color:#6b7280;line-height:1">&times;</button>
</div>
<!-- Vorhandene Buchungen -->
<div style="padding:1rem 1.25rem;border-bottom:1px solid #e5e7eb;overflow-x:auto">
<table style="width:100%;font-size:.75rem;border-collapse:collapse">
<thead>
<tr style="background:#f0f0f0">
<th style="padding:.4rem .5rem;text-align:left">Datum</th>
<th style="padding:.4rem .5rem;text-align:left">Kategorie</th>
<th style="padding:.4rem .5rem;text-align:left">Zahlungskonto</th>
<th style="padding:.4rem .5rem;text-align:right">Netto</th>
<th style="padding:.4rem .5rem;text-align:right">USt</th>
<th style="padding:.4rem .5rem;text-align:right">Brutto</th>
<th style="padding:.4rem .5rem">Text</th>
</tr>
</thead>
<tbody>
<?php foreach ($bookings as $bk):
$bkStorno = (bool)$bk['is_reversal'] || !empty($bk['reversed_by']);
?>
<tr style="border-bottom:1px solid #eee;<?= $bkStorno ? 'opacity:.5;background:#fff5f5' : '' ?>">
<td style="padding:.35rem .5rem;white-space:nowrap"><?= $h(View::formatDate($bk['buchungs_datum'])) ?></td>
<td style="padding:.35rem .5rem;font-size:.7rem"><?= $h($bk['kategorie_nr']) ?><br><span style="color:#6b7280"><?= $h($bk['kategorie_name']) ?></span></td>
<td style="padding:.35rem .5rem;font-size:.7rem"><?= $h($bk['zahlungs_nr']) ?><br><span style="color:#6b7280"><?= $h($bk['zahlungs_name']) ?></span></td>
<td style="padding:.35rem .5rem;text-align:right;font-family:monospace"><?= $fmt($bk['betrag_netto']) ?></td>
<td style="padding:.35rem .5rem;text-align:right"><?= number_format((float)$bk['ust_satz'], 0) ?> %</td>
<td style="padding:.35rem .5rem;text-align:right;font-family:monospace;font-weight:600"><?= $fmt($bk['betrag_brutto']) ?></td>
<td style="padding:.35rem .5rem;color:#6b7280;font-size:.7rem">
<?= $h($bk['buchungstext']) ?>
<?php if ((bool)$bk['is_reversal']): ?><span style="color:#dc2626;font-size:.65rem">[Storno]</span>
<?php elseif (!empty($bk['reversed_by'])): ?><span style="color:#dc2626;font-size:.65rem">[Storniert]</span><?php endif; ?>
</td>
</tr>
<?php endforeach; ?>
<?php if (empty($bookings)): ?>
<tr><td colspan="7" style="padding:1rem;text-align:center;color:#9ca3af">Noch keine Buchungen.</td></tr>
<?php endif; ?>
</tbody>
<?php
$gezahlt = \Steuerkram\Models\Booking::sumZahlungen($bookings);
$offen = round((float)$invoice['betrag_brutto'] - $gezahlt, 2);
?>
<tfoot>
<tr style="border-top:2px solid #e5e7eb">
<td colspan="5" style="padding:.4rem .5rem;text-align:right">Rechnungsbetrag</td>
<td style="padding:.4rem .5rem;text-align:right;font-family:monospace"><?= $fmt($invoice['betrag_brutto']) ?></td>
<td></td>
</tr>
<tr>
<td colspan="5" style="padding:.4rem .5rem;text-align:right">Gezahlt</td>
<td style="padding:.4rem .5rem;text-align:right;font-family:monospace"><?= $fmt($gezahlt) ?></td>
<td></td>
</tr>
<tr>
<td colspan="5" style="padding:.4rem .5rem;text-align:right;font-weight:600"><?= $offen < 0 ? 'Überzahlt' : 'Offen' ?></td>
<td style="padding:.4rem .5rem;text-align:right;font-family:monospace;font-weight:600"><?= $fmt(abs($offen)) ?></td>
<td></td>
</tr>
</tfoot>
</table>
</div>
<!-- Neue Buchung erfassen -->
<?php
// Vorbelegung: noch offener Restbetrag, umgerechnet auf netto mit dem USt-Satz der Rechnung
$ustQuote = (float)$invoice['betrag_netto'] != 0.0
? (float)$invoice['betrag_ust'] / (float)$invoice['betrag_netto']
: 0.0;
$vorschlagNet = $offen > 0
? round($offen / (1 + $ustQuote), 2)
: abs((float)$invoice['betrag_netto']);
$ustVorgabe = (string)(int)round($ustQuote * 100);
if (!in_array($ustVorgabe, ['0', '7', '19'], true)) $ustVorgabe = '19';
?>
<div style="padding:1rem 1.25rem">
<div style="font-size:.8rem;font-weight:600;margin-bottom:.75rem">Neue Buchung erfassen</div>
<form method="post" action="/invoices/<?= $h($invoice['id']) ?>/bookings">
<input type="hidden" name="_from_modal" value="1">
<div style="display:grid;grid-template-columns:1fr 1fr;gap:.75rem;margin-bottom:.75rem">
<div>
<label style="font-size:.75rem;font-weight:600;display:block;margin-bottom:.25rem">Zahldatum *</label>
<input type="date" name="buchungs_datum" value="<?= date('Y-m-d') ?>" required style="width:100%">
</div>
<div>
<label style="font-size:.75rem;font-weight:600;display:block;margin-bottom:.25rem">Nettobetrag (€) *</label>
<input type="number" name="betrag_netto" step="0.01" min="0.01" required
value="<?= $h(number_format(abs($vorschlagNet), 2, '.', '')) ?>"
style="width:100%">
</div>
</div>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:.75rem;margin-bottom:.75rem">
<div>
<label style="font-size:.75rem;font-weight:600;display:block;margin-bottom:.25rem">Kategorie-Konto *</label>
<select name="kategorie_konto_id" required style="width:100%">
<option value="">– Konto wählen –</option>
<?php foreach ($kontenGruppen['Betriebseinnahme'] ?? [] as $acc): ?>
<option value="<?= $h($acc['id']) ?>"><?= $h($acc['kontonummer']) ?> – <?= $h($acc['bezeichnung']) ?></option>
<?php endforeach; ?>
</select>
</div>
<div>
<label style="font-size:.75rem;font-weight:600;display:block;margin-bottom:.25rem">Zahlungskonto *</label>
<select name="zahlungskonto_id" required style="width:100%">
<option value="">– Konto wählen –</option>
<?php foreach ($kontenGruppen['Zahlungskonto'] ?? [] as $acc): ?>
<option value="<?= $h($acc['id']) ?>"><?= $h($acc['kontonummer']) ?> – <?= $h($acc['bezeichnung']) ?></option>
<?php endforeach; ?>
</select>
</div>
</div>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:.75rem;margin-bottom:.75rem">
<div>
<label style="font-size:.75rem;font-weight:600;display:block;margin-bottom:.25rem">USt-Satz (%)</label>
<select name="ust_satz" style="width:100%">
<?php foreach (['0','7','19'] as $s): ?>
<option value="<?= $s ?>"<?= $s === $ustVorgabe ? ' selected' : '' ?>><?= $s ?> %</option>
<?php endforeach; ?>
</select>
</div>
<div>
<label style="font-size:.75rem;font-weight:600;display:block;margin-bottom:.25rem">Buchungstext *</label>
<input type="text" name="buchungstext" required
value="Ausgangsrechnung <?= $h($invoice['rechnungs_nr'] ?? '') ?>"
style="width:100%">
</div>
</div>
<div style="display:flex;gap:.75rem;justify-content:flex-end">
<button type="button" class="btn btn-secondary"
onclick="document.getElementById('buchungs-modal').style.display='none'">Schließen</button>
<button type="submit" class="btn btn-primary">Buchung erfassen</button>
</div>
</form>
</div>
</div>
</div>
<script>
(function () {
var flash = document.querySelector('.flash-error, .flash-success');
if (flash) {
var ref = document.referrer || '';
if (ref.indexOf('/invoices/<?= (int)$invoice['id'] ?>/bookings') !== -1) {
document.getElementById('buchungs-modal').style.display = 'flex';
}
}
}());
</script>
<?php endif; ?>