Fix: Rechnungsansicht überarbeitet (Layout, Schriftgrößen, Summenblock, Enter-Verhalten)
This commit is contained in:
parent
2fe6406f97
commit
84cbbe8401
9 changed files with 442 additions and 154 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -5,3 +5,4 @@ backups/
|
|||
.env
|
||||
*.log
|
||||
.claude/
|
||||
*.csv
|
||||
|
|
|
|||
2
migrations/037_invoice_lines_type.sql
Normal file
2
migrations/037_invoice_lines_type.sql
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
ALTER TABLE invoice_lines
|
||||
ADD COLUMN IF NOT EXISTS type VARCHAR(20) NOT NULL DEFAULT 'position';
|
||||
|
|
@ -446,9 +446,12 @@ td { padding: .65rem 1rem; vertical-align: middle; }
|
|||
}
|
||||
|
||||
.invoice-preview {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background: #fff;
|
||||
padding: 2cm 2cm 2.5cm;
|
||||
padding: 2cm 2cm 20px;
|
||||
max-width: 21cm;
|
||||
min-height: 29.7cm;
|
||||
margin: 0 auto;
|
||||
box-shadow: var(--shadow);
|
||||
font-size: 11pt;
|
||||
|
|
@ -456,6 +459,47 @@ td { padding: .65rem 1rem; vertical-align: middle; }
|
|||
color: #000;
|
||||
}
|
||||
|
||||
.invoice-page-body {
|
||||
/* natürliche Höhe, kein flex-stretch */
|
||||
}
|
||||
|
||||
.invoice-footer-bar {
|
||||
margin-top: auto; /* immer am unteren Rand der A4-Seite */
|
||||
}
|
||||
|
||||
#invoice-pages {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1cm;
|
||||
}
|
||||
|
||||
.invoice-form-table th,
|
||||
.invoice-form-table td { padding-left: 5px; padding-right: 5px; vertical-align: top; }
|
||||
.invoice-form-table th:first-child,
|
||||
.invoice-form-table td:first-child { padding-left: 1rem; }
|
||||
#invoice-pos-table td { vertical-align: top; }
|
||||
|
||||
.line-bez-input {
|
||||
min-height: unset;
|
||||
resize: none;
|
||||
overflow: hidden;
|
||||
line-height: 1.4;
|
||||
padding-top: 2px;
|
||||
padding-bottom: 2px;
|
||||
}
|
||||
|
||||
.add-line-opt {
|
||||
display: block;
|
||||
width: 100%;
|
||||
padding: .35rem .75rem;
|
||||
text-align: left;
|
||||
background: transparent;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.add-line-opt:hover { background: #f0f0f0; }
|
||||
|
||||
/*
|
||||
* Adressbereich: DIN 5008-Proportionen (50% = 85mm bei vollem A4-Preview).
|
||||
* Breakpoint: nav (220px) + page-body-padding (45px) + A4-max-width (794px) = 1059px.
|
||||
|
|
|
|||
|
|
@ -137,23 +137,55 @@ document.addEventListener('input', (e) => {
|
|||
}
|
||||
});
|
||||
|
||||
// ── Rechnungszeile hinzufügen ─────────────────────────────────
|
||||
let lineCounter = 0;
|
||||
// ── Rechnungszeile / Überschrift hinzufügen ───────────────────
|
||||
|
||||
function showAddLineDialog(btn) {
|
||||
document.getElementById('add-line-popup')?.remove();
|
||||
|
||||
const popup = document.createElement('div');
|
||||
popup.id = 'add-line-popup';
|
||||
popup.style.cssText = 'position:absolute;z-index:200;background:#fff;border:1px solid #ccc;border-radius:5px;box-shadow:0 2px 8px rgba(0,0,0,.12);overflow:hidden;font-size:.875rem';
|
||||
popup.innerHTML = `
|
||||
<button class="add-line-opt" onclick="document.getElementById('add-line-popup').remove();addInvoiceHeading()">Überschrift</button>
|
||||
<button class="add-line-opt" onclick="document.getElementById('add-line-popup').remove();addInvoiceLine()">Neue Position</button>`;
|
||||
document.body.appendChild(popup);
|
||||
|
||||
const r = btn.getBoundingClientRect();
|
||||
popup.style.top = (window.scrollY + r.bottom + 3) + 'px';
|
||||
popup.style.left = r.left + 'px';
|
||||
popup.style.minWidth = r.width + 'px';
|
||||
|
||||
setTimeout(() => {
|
||||
document.addEventListener('click', function close(e) {
|
||||
if (!popup.contains(e.target) && e.target !== btn) {
|
||||
popup.remove();
|
||||
document.removeEventListener('click', close);
|
||||
}
|
||||
});
|
||||
}, 0);
|
||||
}
|
||||
|
||||
function _nextLineIdx() {
|
||||
const tbody = document.getElementById('invoice-lines-body');
|
||||
return tbody ? tbody.querySelectorAll('tr').length : 0;
|
||||
}
|
||||
|
||||
function addInvoiceLine() {
|
||||
lineCounter++;
|
||||
const tbody = document.getElementById('invoice-lines-body');
|
||||
if (!tbody) return;
|
||||
|
||||
const idx = tbody.querySelectorAll('.invoice-line-row').length;
|
||||
const idx = _nextLineIdx();
|
||||
const row = document.createElement('tr');
|
||||
row.className = 'invoice-line-row';
|
||||
row.innerHTML = `
|
||||
<td><span class="line-pos text-muted">${idx + 1}</span></td>
|
||||
<td><input type="text" name="lines[${idx}][bezeichnung]" required placeholder="Leistungsbeschreibung" style="min-width:200px"></td>
|
||||
<td><input type="number" name="lines[${idx}][menge]" value="1" min="0.001" step="0.001" style="width:80px" required></td>
|
||||
<td><span class="line-pos text-muted"></span></td>
|
||||
<td>
|
||||
<select name="lines[${idx}][einheit]" style="width:80px">
|
||||
<input type="hidden" name="lines[${idx}][type]" value="position">
|
||||
<textarea name="lines[${idx}][bezeichnung]" required placeholder="Leistungsbeschreibung" rows="1" class="line-bez-input" style="width:100%"></textarea>
|
||||
</td>
|
||||
<td><input type="number" name="lines[${idx}][menge]" value="1" min="0.001" step="0.001" style="width:100%" required></td>
|
||||
<td>
|
||||
<select name="lines[${idx}][einheit]" style="width:100%">
|
||||
<option value="Stk.">Stk.</option>
|
||||
<option value="h">h</option>
|
||||
<option value="Tage">Tage</option>
|
||||
|
|
@ -163,34 +195,63 @@ function addInvoiceLine() {
|
|||
</select>
|
||||
</td>
|
||||
<td>
|
||||
<select name="lines[${idx}][ust_satz]" style="width:80px">
|
||||
<select name="lines[${idx}][ust_satz]" style="width:100%">
|
||||
<option value="19">19 %</option>
|
||||
<option value="7">7 %</option>
|
||||
<option value="0">0 %</option>
|
||||
</select>
|
||||
</td>
|
||||
<td><input type="number" name="lines[${idx}][einzelpreis]" value="0.00" min="0" step="0.01" style="width:110px" required></td>
|
||||
<td><input type="number" name="lines[${idx}][einzelpreis]" value="0.00" min="0" step="0.01" style="width:100%" required></td>
|
||||
<td class="td-right line-netto text-muted">0,00 €</td>
|
||||
<td class="td-right line-brutto font-bold">0,00 €</td>
|
||||
<td>
|
||||
<button type="button" class="btn btn-danger btn-sm" onclick="removeInvoiceLine(this)">✕</button>
|
||||
</td>`;
|
||||
tbody.appendChild(row);
|
||||
renumberLines();
|
||||
calcInvoiceTotals();
|
||||
const ta = row.querySelector('.line-bez-input');
|
||||
if (ta) { initBezTextarea(ta); ta.focus(); }
|
||||
}
|
||||
|
||||
function addInvoiceHeading() {
|
||||
const tbody = document.getElementById('invoice-lines-body');
|
||||
if (!tbody) return;
|
||||
|
||||
const idx = _nextLineIdx();
|
||||
const row = document.createElement('tr');
|
||||
row.className = 'invoice-heading-row';
|
||||
row.innerHTML = `
|
||||
<td colspan="8">
|
||||
<input type="hidden" name="lines[${idx}][type]" value="heading">
|
||||
<textarea name="lines[${idx}][bezeichnung]" required placeholder="Überschrift" rows="1" class="line-bez-input" style="width:100%;font-weight:bold"></textarea>
|
||||
</td>
|
||||
<td><button type="button" class="btn btn-danger btn-sm" onclick="removeInvoiceLine(this)">✕</button></td>`;
|
||||
tbody.appendChild(row);
|
||||
renumberLines();
|
||||
const ta = row.querySelector('.line-bez-input');
|
||||
if (ta) { initBezTextarea(ta); ta.focus(); }
|
||||
}
|
||||
|
||||
function removeInvoiceLine(btn) {
|
||||
btn.closest('.invoice-line-row').remove();
|
||||
// Renumber positions
|
||||
document.querySelectorAll('.invoice-line-row').forEach((row, i) => {
|
||||
const posEl = row.querySelector('.line-pos');
|
||||
if (posEl) posEl.textContent = i + 1;
|
||||
// Update indices in name attributes
|
||||
btn.closest('tr').remove();
|
||||
renumberLines();
|
||||
calcInvoiceTotals();
|
||||
}
|
||||
|
||||
function renumberLines() {
|
||||
const tbody = document.getElementById('invoice-lines-body');
|
||||
if (!tbody) return;
|
||||
let posNum = 0;
|
||||
tbody.querySelectorAll('tr').forEach((row, i) => {
|
||||
// Alle name-Attribute auf neuen Array-Index setzen
|
||||
row.querySelectorAll('[name]').forEach(el => {
|
||||
el.name = el.name.replace(/lines\[\d+\]/, `lines[${i}]`);
|
||||
});
|
||||
// Positions-Nummer nur bei echten Zeilen hochzählen
|
||||
const posEl = row.querySelector('.line-pos');
|
||||
if (posEl) posEl.textContent = ++posNum;
|
||||
});
|
||||
calcInvoiceTotals();
|
||||
}
|
||||
|
||||
// Init totals on page load
|
||||
|
|
@ -376,6 +437,27 @@ document.addEventListener('keydown', (e) => {
|
|||
}
|
||||
});
|
||||
|
||||
// ── Rechnungsformular: Enter nicht als Submit, Textarea auto-resize ───────────
|
||||
function initBezTextarea(el) {
|
||||
el.style.height = 'auto';
|
||||
el.style.height = el.scrollHeight + 'px';
|
||||
}
|
||||
|
||||
document.addEventListener('input', (e) => {
|
||||
if (e.target.classList.contains('line-bez-input')) initBezTextarea(e.target);
|
||||
});
|
||||
|
||||
document.addEventListener('keydown', (e) => {
|
||||
if (e.key !== 'Enter') return;
|
||||
if (e.target.tagName === 'TEXTAREA') return;
|
||||
const form = e.target.closest('form.invoice-form');
|
||||
if (form) e.preventDefault();
|
||||
}, true);
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
document.querySelectorAll('.line-bez-input').forEach(initBezTextarea);
|
||||
});
|
||||
|
||||
// ── Flash Message Auto-dismiss ────────────────────────────────
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
document.querySelectorAll('.flash').forEach(el => {
|
||||
|
|
|
|||
|
|
@ -65,7 +65,7 @@ class Invoice
|
|||
public static function lines(int $invoiceId): array
|
||||
{
|
||||
return Database::fetchAll(
|
||||
'SELECT * FROM invoice_lines WHERE invoice_id = ? ORDER BY position',
|
||||
'SELECT * FROM invoice_lines WHERE invoice_id = ? ORDER BY id',
|
||||
[$invoiceId]
|
||||
);
|
||||
}
|
||||
|
|
@ -183,32 +183,46 @@ class Invoice
|
|||
|
||||
private static function saveLines(int $invoiceId, array $lines): void
|
||||
{
|
||||
foreach ($lines as $i => $line) {
|
||||
$posCounter = 0;
|
||||
foreach ($lines as $line) {
|
||||
if (empty($line['bezeichnung'])) continue;
|
||||
$menge = round((float)$line['menge'], 3);
|
||||
$preis = round((float)$line['einzelpreis'], 2);
|
||||
$ustSatz = round((float)$line['ust_satz'], 2);
|
||||
$netto = round($menge * $preis, 2);
|
||||
$ust = round($netto * $ustSatz / 100, 2);
|
||||
$brutto = round($netto + $ust, 2);
|
||||
$type = ($line['type'] ?? 'position') === 'heading' ? 'heading' : 'position';
|
||||
|
||||
Database::execute(
|
||||
'INSERT INTO invoice_lines (invoice_id, position, bezeichnung, menge, einheit,
|
||||
einzelpreis, ust_satz, betrag_netto, betrag_ust, betrag_brutto)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)',
|
||||
[
|
||||
$invoiceId,
|
||||
$i + 1,
|
||||
$line['bezeichnung'],
|
||||
$menge,
|
||||
$line['einheit'] ?? 'Stk.',
|
||||
$preis,
|
||||
$ustSatz,
|
||||
$netto,
|
||||
$ust,
|
||||
$brutto,
|
||||
]
|
||||
);
|
||||
if ($type === 'heading') {
|
||||
Database::execute(
|
||||
'INSERT INTO invoice_lines (invoice_id, position, bezeichnung, menge, einheit,
|
||||
einzelpreis, ust_satz, betrag_netto, betrag_ust, betrag_brutto, type)
|
||||
VALUES (?, ?, ?, 0, \'\', 0, 0, 0, 0, 0, ?)',
|
||||
[$invoiceId, 0, $line['bezeichnung'], 'heading']
|
||||
);
|
||||
} else {
|
||||
$posCounter++;
|
||||
$menge = round((float)$line['menge'], 3);
|
||||
$preis = round((float)$line['einzelpreis'], 2);
|
||||
$ustSatz = round((float)$line['ust_satz'], 2);
|
||||
$netto = round($menge * $preis, 2);
|
||||
$ust = round($netto * $ustSatz / 100, 2);
|
||||
$brutto = round($netto + $ust, 2);
|
||||
|
||||
Database::execute(
|
||||
'INSERT INTO invoice_lines (invoice_id, position, bezeichnung, menge, einheit,
|
||||
einzelpreis, ust_satz, betrag_netto, betrag_ust, betrag_brutto, type)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)',
|
||||
[
|
||||
$invoiceId,
|
||||
$posCounter,
|
||||
$line['bezeichnung'],
|
||||
$menge,
|
||||
$line['einheit'] ?? 'Stk.',
|
||||
$preis,
|
||||
$ustSatz,
|
||||
$netto,
|
||||
$ust,
|
||||
$brutto,
|
||||
'position',
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -78,15 +78,21 @@ class PdfInvoiceService
|
|||
// Positionszeilen
|
||||
$posRows = '';
|
||||
foreach ($lines as $line) {
|
||||
$posRows .= '<tr>
|
||||
<td class="pos-nr">' . $h($line['position']) . '</td>
|
||||
<td class="pos-bez">' . $h($line['bezeichnung']) . '</td>
|
||||
<td class="pos-menge">' . $fmtNum($line['menge']) . '</td>
|
||||
<td class="pos-einheit">' . $h($line['einheit']) . '</td>
|
||||
<td class="pos-ust">' . $h(number_format((float)$line['ust_satz'], 0)) . ' %</td>
|
||||
<td class="pos-preis">' . $fmt($line['einzelpreis']) . '</td>
|
||||
<td class="pos-gesamt">' . $fmt($line['betrag_brutto']) . '</td>
|
||||
</tr>';
|
||||
if (($line['type'] ?? 'position') === 'heading') {
|
||||
$posRows .= '<tr>
|
||||
<td colspan="7" class="pos-heading">' . nl2br($h($line['bezeichnung'])) . '</td>
|
||||
</tr>';
|
||||
} else {
|
||||
$posRows .= '<tr>
|
||||
<td class="pos-nr">' . $h($line['position']) . '</td>
|
||||
<td class="pos-bez">' . nl2br($h($line['bezeichnung'])) . '</td>
|
||||
<td class="pos-menge">' . $fmtNum($line['menge']) . '</td>
|
||||
<td class="pos-einheit">' . $h($line['einheit']) . '</td>
|
||||
<td class="pos-ust">' . $h(number_format((float)$line['ust_satz'], 0)) . ' %</td>
|
||||
<td class="pos-preis">' . $fmt($line['einzelpreis']) . '</td>
|
||||
<td class="pos-gesamt">' . $fmt($line['betrag_netto']) . '</td>
|
||||
</tr>';
|
||||
}
|
||||
}
|
||||
|
||||
$payDays = $inv['payment_days'] ?? 14;
|
||||
|
|
@ -112,19 +118,8 @@ class PdfInvoiceService
|
|||
{$h($inv['plz'])} {$h($inv['ort'])}{$land}
|
||||
</div>
|
||||
</td>
|
||||
<!-- Rechte Spalte: Absenderinformationen -->
|
||||
<td class="address-right">
|
||||
<table class="sender-table">
|
||||
<tr><td><strong>{$h($settings['firma'] ?? '')}</strong></td></tr>
|
||||
<tr><td>{$h($settings['inhaber'] ?? '')}</td></tr>
|
||||
<tr><td>{$h($settings['strasse'] ?? '')}</td></tr>
|
||||
<tr><td>{$h($settings['plz'] ?? '')} {$h($settings['ort'] ?? '')}</td></tr>
|
||||
<tr><td> </td></tr>
|
||||
<tr><td>{$h($settings['telefon'] ?? '')}</td></tr>
|
||||
<tr><td>{$h($settings['email'] ?? '')}</td></tr>
|
||||
<tr><td>USt-ID: {$h($settings['ust_id'] ?? '')}</td></tr>
|
||||
</table>
|
||||
</td>
|
||||
<!-- Rechte Spalte: Dokumententitel -->
|
||||
<td class="address-right" style="text-align:right;vertical-align:top">RECHNUNG</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
|
|
@ -169,7 +164,7 @@ class PdfInvoiceService
|
|||
</table>
|
||||
|
||||
<!-- Summen (rechtsbündig) -->
|
||||
<table class="sum-table" align="right">
|
||||
<table class="sum-table" align="right" style="margin-right:1.4mm">
|
||||
<tr><td>Nettobetrag:</td> <td class="sum-val">{$fmt($inv['betrag_netto'])}</td></tr>
|
||||
<tr><td>zzgl. Umsatzsteuer:</td><td class="sum-val">{$fmt($inv['betrag_ust'])}</td></tr>
|
||||
<tr class="sum-total">
|
||||
|
|
@ -221,7 +216,7 @@ HTML;
|
|||
private function buildCss(): string
|
||||
{
|
||||
return <<<CSS
|
||||
body { font-family: DejaVu Sans, sans-serif; font-size: 10pt; color: #000; }
|
||||
body { font-family: DejaVu Sans, sans-serif; font-size: 7.8pt; color: #000; }
|
||||
.invoice-wrap { width: 100%; }
|
||||
|
||||
/* Logo */
|
||||
|
|
@ -233,26 +228,25 @@ body { font-family: DejaVu Sans, sans-serif; font-size: 10pt; color: #000; }
|
|||
.address-right { vertical-align: top; padding: 0 0 0 5mm; }
|
||||
|
||||
.return-address {
|
||||
font-size: 7pt; color: #555;
|
||||
font-size: 5.5pt; color: #555;
|
||||
border-bottom: 0.3pt solid #aaa;
|
||||
padding-bottom: 1mm; margin-bottom: 2mm;
|
||||
}
|
||||
.recipient { font-size: 10pt; line-height: 1.5; min-height: 27.3mm; }
|
||||
|
||||
/* Absendertabelle: auf Inhaltsbreite schrumpfen, rechtsbündig */
|
||||
.sender-table { border-collapse: collapse; font-size: 9pt; margin-left: auto; }
|
||||
.sender-table td { padding: 0.4mm 0; white-space: nowrap; }
|
||||
.recipient { font-size: 7.8pt; line-height: 1.5; min-height: 27.3mm; }
|
||||
|
||||
/* Meta-Felder */
|
||||
.meta-table { width: 100%; table-layout: fixed; border-collapse: collapse; margin-bottom: 8mm; font-size: 9pt; }
|
||||
.meta-table th { background: #f0f0f0; padding: 2mm; text-align: left; font-size: 8pt; color: #555; border: 0.5pt solid #ccc; }
|
||||
.meta-table { width: 100%; table-layout: fixed; border-collapse: collapse; margin-bottom: 8mm; font-size: 7pt; }
|
||||
.meta-table th { background: #f0f0f0; padding: 2mm; text-align: left; font-size: 6pt; color: #555; border: 0.5pt solid #ccc; }
|
||||
.meta-table td { padding: 2mm; border: 0.5pt solid #ccc; }
|
||||
.meta-table th:last-child, .meta-table td:last-child { text-align: right; }
|
||||
|
||||
/* Positionen */
|
||||
.pos-table { width: 100%; table-layout: fixed; border-collapse: collapse; margin-bottom: 5mm; font-size: 9pt; }
|
||||
.pos-table thead th { background: #f0f0f0; color: #000; padding: 2mm; }
|
||||
.pos-table { width: 100%; table-layout: fixed; border-collapse: collapse; margin-bottom: 5mm; font-size: 7pt; }
|
||||
.pos-table thead th { background: #f0f0f0; color: #000; padding: 2mm; text-align: right; }
|
||||
.pos-table thead th.pos-nr, .pos-table thead th.pos-bez, .pos-table thead th.pos-einheit { text-align: left; }
|
||||
.pos-table tbody tr:nth-child(even) { background: #f8f8f8; }
|
||||
.pos-table tbody td { padding: 1.5mm 2mm; border-bottom: 0.3pt solid #e0e0e0; }
|
||||
.pos-table tbody td { padding: 1.5mm 2mm; border-bottom: 0.3pt solid #e0e0e0; vertical-align: top; }
|
||||
.pos-heading { font-weight: bold; }
|
||||
.pos-nr { width: 8%; }
|
||||
.pos-bez { /* nimmt den Rest */ }
|
||||
.pos-menge { width: 11%; text-align: right; }
|
||||
|
|
@ -262,21 +256,20 @@ body { font-family: DejaVu Sans, sans-serif; font-size: 10pt; color: #000; }
|
|||
.pos-gesamt { width: 16%; text-align: right; }
|
||||
|
||||
/* Summen */
|
||||
.sum-table { border-collapse: collapse; margin-bottom: 8mm; font-size: 9pt; }
|
||||
.sum-table td { padding: 1.5mm 2mm; }
|
||||
.sum-table .sum-val { text-align: right; border-top: 0.3pt solid #ccc; }
|
||||
.sum-table .sum-total td { border-top: 1pt solid #000; }
|
||||
.sum-table { border-collapse: collapse; margin-bottom: 8mm; font-size: 7pt; }
|
||||
.sum-table td { padding: 1.5mm 2mm 1.5mm 2mm; }
|
||||
.sum-table .sum-val { text-align: right; padding-right: 0; width: 16%; }
|
||||
|
||||
/* Zahlungshinweis */
|
||||
.payment-note { margin-top: 5mm; font-size: 9pt; padding: 3mm; background: #f8f8f8; border-left: 2pt solid #2563eb; }
|
||||
.payment-note { margin-top: 5mm; font-size: 7pt; padding: 3mm; background: #f8f8f8; border-left: 2pt solid #2563eb; }
|
||||
|
||||
/* Fußzeile */
|
||||
.footer-table { width: 100%; border-collapse: collapse; font-size: 7.5pt; border-top: 0.5pt solid #ccc; }
|
||||
.footer-table { width: 100%; border-collapse: collapse; font-size: 6pt; border-top: 0.5pt solid #ccc; }
|
||||
.footer-table td { padding: 1mm 2mm; vertical-align: top; }
|
||||
.footer-table td:nth-child(1) { width: 35%; }
|
||||
.footer-table td:nth-child(2) { width: 25%; }
|
||||
.footer-table td:nth-child(3) { width: 28%; }
|
||||
.page-nr { width: 12%; text-align: right; }
|
||||
.footer-table td:nth-child(1) { width: 29%; }
|
||||
.footer-table td:nth-child(2) { width: 29%; }
|
||||
.footer-table td:nth-child(3) { width: 29%; }
|
||||
.page-nr { width: 13%; text-align: right; }
|
||||
CSS;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ $preCustomer = (int)($_GET['customer_id'] ?? 0);
|
|||
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<form method="post" action="<?= $h($action) ?>">
|
||||
<form method="post" action="<?= $h($action) ?>" class="invoice-form">
|
||||
|
||||
<div class="form-grid form-grid-2 mb-2">
|
||||
<div class="form-group">
|
||||
|
|
@ -60,7 +60,7 @@ $preCustomer = (int)($_GET['customer_id'] ?? 0);
|
|||
<div class="mb-2">
|
||||
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:.75rem">
|
||||
<strong>Rechnungspositionen</strong>
|
||||
<button type="button" class="btn btn-secondary btn-sm" onclick="addInvoiceLine()">+ Position hinzufügen</button>
|
||||
<button type="button" class="btn btn-primary btn-sm" onclick="showAddLineDialog(this)">+ Position hinzufügen</button>
|
||||
</div>
|
||||
|
||||
<?php if (isset($errors['lines'])): ?>
|
||||
|
|
@ -68,58 +68,86 @@ $preCustomer = (int)($_GET['customer_id'] ?? 0);
|
|||
<?php endif; ?>
|
||||
|
||||
<div class="table-wrap">
|
||||
<table>
|
||||
<table class="invoice-form-table" style="width:calc(100% - 20px);table-layout:fixed">
|
||||
<colgroup>
|
||||
<col style="width:3.5em"><!-- Pos. -->
|
||||
<col><!-- Bezeichnung (flex) -->
|
||||
<col style="width:5.5em"><!-- Menge -->
|
||||
<col style="width:6em"><!-- Einheit -->
|
||||
<col style="width:5em"><!-- USt. -->
|
||||
<col style="width:8em"><!-- Einzelpreis -->
|
||||
<col style="width:7em"><!-- Netto -->
|
||||
<col style="width:7em"><!-- Brutto -->
|
||||
<col style="width:2.5em"><!-- Löschen -->
|
||||
</colgroup>
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width:40px">Pos.</th>
|
||||
<th>Pos.</th>
|
||||
<th>Bezeichnung</th>
|
||||
<th style="width:80px">Menge</th>
|
||||
<th style="width:80px">Einheit</th>
|
||||
<th style="width:80px">USt.</th>
|
||||
<th style="width:110px">Einzelpreis</th>
|
||||
<th style="width:100px" class="td-right">Netto</th>
|
||||
<th style="width:110px" class="td-right">Brutto</th>
|
||||
<th style="width:40px"></th>
|
||||
<th>Menge</th>
|
||||
<th>Einheit</th>
|
||||
<th>USt.</th>
|
||||
<th>Einzelpreis</th>
|
||||
<th class="td-right">Netto</th>
|
||||
<th class="td-right">Brutto</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="invoice-lines-body">
|
||||
<?php foreach ($lines as $i => $line): ?>
|
||||
<?php
|
||||
$posIdx = 0;
|
||||
foreach ($lines as $i => $line):
|
||||
$isHeading = ($line['type'] ?? 'position') === 'heading';
|
||||
?>
|
||||
<?php if ($isHeading): ?>
|
||||
<tr class="invoice-heading-row">
|
||||
<td colspan="8">
|
||||
<input type="hidden" name="lines[<?= $i ?>][type]" value="heading">
|
||||
<textarea name="lines[<?= $i ?>][bezeichnung]" required placeholder="Überschrift" rows="1" class="line-bez-input" style="width:100%;font-weight:bold"><?= $h($line['bezeichnung']) ?></textarea>
|
||||
</td>
|
||||
<td><button type="button" class="btn btn-danger btn-sm" onclick="removeInvoiceLine(this)">✕</button></td>
|
||||
</tr>
|
||||
<?php else: ?>
|
||||
<tr class="invoice-line-row">
|
||||
<td><span class="line-pos text-muted"><?= $i+1 ?></span></td>
|
||||
<td><input type="text" name="lines[<?= $i ?>][bezeichnung]" value="<?= $h($line['bezeichnung']) ?>" required style="min-width:200px"></td>
|
||||
<td><input type="number" name="lines[<?= $i ?>][menge]" value="<?= $h($line['menge']) ?>" min="0.001" step="0.001" style="width:75px" required></td>
|
||||
<td><span class="line-pos text-muted"><?= ++$posIdx ?></span></td>
|
||||
<td>
|
||||
<select name="lines[<?= $i ?>][einheit]" style="width:75px">
|
||||
<input type="hidden" name="lines[<?= $i ?>][type]" value="position">
|
||||
<textarea name="lines[<?= $i ?>][bezeichnung]" required rows="1" class="line-bez-input" style="width:100%"><?= $h($line['bezeichnung']) ?></textarea>
|
||||
</td>
|
||||
<td><input type="number" name="lines[<?= $i ?>][menge]" value="<?= $h($line['menge']) ?>" min="0.001" step="0.001" style="width:100%" required></td>
|
||||
<td>
|
||||
<select name="lines[<?= $i ?>][einheit]" style="width:100%">
|
||||
<?php foreach (['Stk.','h','Tage','Monat','Pauschale','km'] as $eu): ?>
|
||||
<option value="<?= $eu ?>"<?= $line['einheit'] === $eu ? ' selected' : '' ?>><?= $eu ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</td>
|
||||
<td>
|
||||
<select name="lines[<?= $i ?>][ust_satz]" style="width:75px">
|
||||
<select name="lines[<?= $i ?>][ust_satz]" style="width:100%">
|
||||
<?php foreach ([19, 7, 0] as $r): ?>
|
||||
<option value="<?= $r ?>"<?= (float)$line['ust_satz'] === (float)$r ? ' selected' : '' ?>><?= $r ?> %</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</td>
|
||||
<td><input type="number" name="lines[<?= $i ?>][einzelpreis]" value="<?= $h($line['einzelpreis']) ?>" step="0.01" min="0" style="width:105px" required></td>
|
||||
<td><input type="number" name="lines[<?= $i ?>][einzelpreis]" value="<?= $h($line['einzelpreis']) ?>" step="0.01" min="0" style="width:100%" required></td>
|
||||
<td class="td-right line-netto text-muted"><?= View::formatMoney($line['betrag_netto']) ?></td>
|
||||
<td class="td-right line-brutto font-bold"><?= View::formatMoney($line['betrag_brutto']) ?></td>
|
||||
<td><button type="button" class="btn btn-danger btn-sm" onclick="removeInvoiceLine(this)">✕</button></td>
|
||||
</tr>
|
||||
<?php endif; ?>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- Summen -->
|
||||
<div style="display:flex;justify-content:flex-end;margin-top:.75rem">
|
||||
<table style="width:280px;font-size:.9rem">
|
||||
<tr><td>Nettobetrag:</td><td class="td-right" id="total-netto">–</td></tr>
|
||||
<tr><td>zzgl. Umsatzsteuer:</td><td class="td-right" id="total-ust">–</td></tr>
|
||||
<tr style="font-size:1rem;font-weight:700">
|
||||
<td>Rechnungsbetrag:</td>
|
||||
<td class="td-right" id="total-brutto">–</td>
|
||||
<div style="display:flex;justify-content:flex-end;margin-top:.75rem;padding-right:.5rem">
|
||||
<table style="width:auto;font-size:.7rem">
|
||||
<tr><td style="padding:.3rem 0">Nettobetrag:</td><td class="td-right" style="padding:.3rem 0;width:7.5em" id="total-netto">–</td></tr>
|
||||
<tr><td style="padding:.3rem 0">zzgl. Umsatzsteuer:</td><td class="td-right" style="padding:.3rem 0" id="total-ust">–</td></tr>
|
||||
<tr style="font-size:.78rem;font-weight:700">
|
||||
<td style="padding:.5rem 0">Rechnungsbetrag:</td>
|
||||
<td class="td-right" style="padding:.5rem 0" id="total-brutto">–</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ $badgeClass = match($invoice['status']) {
|
|||
<!-- Aktionsleiste -->
|
||||
<div class="toolbar mb-2">
|
||||
<div class="toolbar-left">
|
||||
<span class="badge <?= $badgeClass ?>" style="font-size:.9rem;padding:.3rem .8rem"><?= $h($invoice['status']) ?></span>
|
||||
<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'): ?>
|
||||
|
|
@ -46,7 +46,9 @@ $badgeClass = match($invoice['status']) {
|
|||
</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'])): ?>
|
||||
|
|
@ -73,7 +75,7 @@ $badgeClass = match($invoice['status']) {
|
|||
<!-- Adressbereich (DIN 5008: 50% = 85mm bei vollem A4-Preview) -->
|
||||
<div class="invoice-address" style="margin-bottom:1.5rem">
|
||||
<div>
|
||||
<div style="font-size:8pt;color:#666;border-bottom:1px solid #ccc;padding-bottom:2px;margin-bottom:4px">
|
||||
<div style="font-size:6pt;color:#666;border-bottom:1px solid #ccc;padding-bottom:2px;margin-bottom:4px">
|
||||
<?= $h($settings['firma'] ?? '') ?> · <?= $h($settings['strasse'] ?? '') ?> · <?= $h(($settings['plz'] ?? '') . ' ' . ($settings['ort'] ?? '')) ?>
|
||||
</div>
|
||||
<div style="line-height:1.5">
|
||||
|
|
@ -83,28 +85,18 @@ $badgeClass = match($invoice['status']) {
|
|||
<?php if ($invoice['land'] !== 'DE'): ?><br><?= $h($invoice['land']) ?><?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
<div style="display:flex;justify-content:flex-end;font-size:.85rem;line-height:1.7">
|
||||
<div style="text-align:left">
|
||||
<strong><?= $h($settings['firma'] ?? '') ?></strong><br>
|
||||
<?= $h($settings['inhaber'] ?? '') ?><br>
|
||||
<?= $h($settings['strasse'] ?? '') ?><br>
|
||||
<?= $h(($settings['plz'] ?? '') . ' ' . ($settings['ort'] ?? '')) ?><br>
|
||||
<?= $h($settings['telefon'] ?? '') ?><br>
|
||||
<?= $h($settings['email'] ?? '') ?><br>
|
||||
USt-ID: <?= $h($settings['ust_id'] ?? '') ?>
|
||||
</div>
|
||||
</div>
|
||||
<div style="text-align:right">RECHNUNG</div>
|
||||
</div>
|
||||
|
||||
<!-- Meta-Tabelle -->
|
||||
<table style="width:100%;border-collapse:collapse;margin-bottom:1.5rem;font-size:.9rem">
|
||||
<table style="width:100%;border-collapse:collapse;margin-bottom:1.5rem;font-size:.7rem">
|
||||
<thead>
|
||||
<tr style="background:#f0f0f0">
|
||||
<th style="padding:.4rem .5rem;text-align:left;border:1px solid #ddd;font-weight:normal;white-space:nowrap">Betreff</th>
|
||||
<th style="padding:.4rem .5rem;text-align:left;border:1px solid #ddd;font-weight:normal;white-space:nowrap">Rechnungsnummer</th>
|
||||
<th style="padding:.4rem .5rem;text-align:left;border:1px solid #ddd;font-weight:normal;white-space:nowrap">Kundennummer</th>
|
||||
<th style="padding:.4rem .5rem;text-align:left;border:1px solid #ddd;font-weight:normal;white-space:nowrap">Leistungszeitraum</th>
|
||||
<th style="padding:.4rem .5rem;text-align:left;border:1px solid #ddd;font-weight:normal;white-space:nowrap;width:100%">Datum</th>
|
||||
<th style="padding:.4rem .5rem;text-align:right;border:1px solid #ddd;font-weight:normal;white-space:nowrap;width:100%">Datum</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
|
@ -113,13 +105,13 @@ $badgeClass = match($invoice['status']) {
|
|||
<td style="padding:.4rem .5rem;border:1px solid #ddd;white-space:nowrap;font-weight:700"><?= $h($invoice['rechnungs_nr'] ?? 'Entwurf') ?></td>
|
||||
<td style="padding:.4rem .5rem;border:1px solid #ddd;white-space:nowrap"><?= $h($invoice['kundennummer']) ?></td>
|
||||
<td style="padding:.4rem .5rem;border:1px solid #ddd"><?= $lzHtml ?: '–' ?></td>
|
||||
<td style="padding:.4rem .5rem;border:1px solid #ddd;white-space:nowrap"><?= $h(View::formatDate($invoice['rechnungs_datum'])) ?></td>
|
||||
<td style="padding:.4rem .5rem;border:1px solid #ddd;white-space:nowrap;text-align:right"><?= $h(View::formatDate($invoice['rechnungs_datum'])) ?></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<!-- Positionen -->
|
||||
<table style="width:100%;table-layout:fixed;border-collapse:collapse;margin-bottom:1.5rem;font-size:.9rem">
|
||||
<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>
|
||||
|
|
@ -133,39 +125,48 @@ $badgeClass = match($invoice['status']) {
|
|||
</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"><?= $h($line['bezeichnung']) ?></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_brutto']) ?></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 -->
|
||||
<div style="display:flex;justify-content:flex-end;margin-bottom:1.5rem">
|
||||
<table style="width:260px">
|
||||
<tr><td style="padding:.3rem 0">Nettobetrag:</td><td style="text-align:right"><?= $fmt($invoice['betrag_netto']) ?></td></tr>
|
||||
<tr><td style="padding:.3rem 0">zzgl. Umsatzsteuer:</td><td style="text-align:right"><?= $fmt($invoice['betrag_ust']) ?></td></tr>
|
||||
<tr style="font-weight:700;font-size:1.05rem;border-top:2px solid #000">
|
||||
<td style="padding:.5rem 0">Rechnungsbetrag:</td>
|
||||
<td style="text-align:right"><?= $fmt($invoice['betrag_brutto']) ?></td>
|
||||
</tr>
|
||||
</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>
|
||||
<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>
|
||||
</div>
|
||||
|
||||
<!-- Zahlungshinweis -->
|
||||
<div style="background:#f0f4ff;border-left:3px solid #2563eb;padding:.75rem 1rem;margin-bottom:2rem;font-size:.9rem">
|
||||
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>
|
||||
</div><!-- /.invoice-page-body -->
|
||||
|
||||
<!-- Fußzeile -->
|
||||
<div style="border-top:1px solid #ccc;padding-top:.75rem;display:grid;grid-template-columns:auto auto auto 1fr;gap:1rem;font-size:.75rem;color:#555">
|
||||
<!-- 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>
|
||||
<?= $h($settings['inhaber'] ?? '') ?><br>
|
||||
|
|
@ -173,17 +174,140 @@ $badgeClass = match($invoice['status']) {
|
|||
<?= $h(($settings['plz'] ?? '') . ' ' . ($settings['ort'] ?? '')) ?><br>
|
||||
USt-ID: <?= $h($settings['ust_id'] ?? '') ?>
|
||||
</div>
|
||||
<div style="min-width:0">
|
||||
<div style="white-space:nowrap">
|
||||
Tel: <?= $h($settings['telefon'] ?? '') ?><br>
|
||||
E-Mail: <?= $h($settings['email'] ?? '') ?><br>
|
||||
Web: <?= $h($settings['website'] ?? '') ?>
|
||||
</div>
|
||||
<div style="min-width:0">
|
||||
<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">Seite 1 von 1</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>
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
1-260426
|
||||
1-260427
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue