feat: create validated ZUGFeRD invoices in one console command
This commit is contained in:
1 parent
e0c15fc7f2
commit
a643cf5227
30 files changed
+1509
-207
No files matched your search
+40
-21
@@ -4,18 +4,32 @@
|
||||
<meta charset="utf-8">
|
||||
<title>Rechnung {{ meta.invoice_number }}</title>
|
||||
<style>
|
||||
body { font-family: sans-serif; font-size: 12px; }
|
||||
.row { display: flex; justify-content: space-between; gap: 12px; }
|
||||
.muted { color: #555; }
|
||||
h1 { font-size: 22px; margin: 0 0 8px 0; }
|
||||
.box { border: 1px solid #ddd; padding: 10px; border-radius: 6px; }
|
||||
table { width: 100%; border-collapse: collapse; margin-top: 14px; }
|
||||
th, td { border-bottom: 1px solid #eee; padding: 6px 6px; text-align: left; }
|
||||
th { background: #fafafa; }
|
||||
@page {
|
||||
size: A4; margin: 18mm 17mm 21mm;
|
||||
@bottom-left { content: "{{ seller.name | replace('"', '') }}"; font-family: sans-serif; font-size: 8pt; color: #64748b; }
|
||||
@bottom-right { content: "Seite " counter(page) " von " counter(pages); font-family: sans-serif; font-size: 8pt; color: #64748b; }
|
||||
}
|
||||
* { box-sizing: border-box; }
|
||||
body { font-family: "DejaVu Sans", sans-serif; font-size: 9pt; color: #1e293b; line-height: 1.55; }
|
||||
.row { display: flex; justify-content: space-between; gap: 16px; }
|
||||
.muted { color: #64748b; }
|
||||
h1 { color: #123655; font-size: 30pt; font-weight: 500; letter-spacing: -1px; margin: 0 0 12px; }
|
||||
.box { border: 1px solid #e2e8f0; border-radius: 6px; padding: 14px; }
|
||||
.row:first-of-type { margin-top: 20px; }
|
||||
.box .muted:first-child { font-size: 8pt; text-transform: uppercase; letter-spacing: 1px; margin-bottom: 7px; }
|
||||
table { width: 100%; border-collapse: collapse; margin-top: 24px; }
|
||||
thead { display: table-header-group; }
|
||||
tr { break-inside: avoid; }
|
||||
th, td { padding: 10px 6px; text-align: left; vertical-align: top; border-bottom: 1px solid #e2e8f0; }
|
||||
th { color: white; background: #123655; font-size: 7pt; font-weight: 500; }
|
||||
tbody tr:nth-child(even) { background: #f8fafc; }
|
||||
.right { text-align: right; }
|
||||
.totals { margin-top: 12px; width: 44%; margin-left: auto; }
|
||||
.totals td { border: none; padding: 4px 6px; }
|
||||
.footer { margin-top: 18px; font-size: 11px; }
|
||||
td.right { white-space: nowrap; }
|
||||
.totals { margin-top: 16px; width: 58%; margin-left: auto; break-inside: avoid; }
|
||||
.totals td { border: none; padding: 5px 7px; font-size: 8.5pt; }
|
||||
.totals tr:last-child td { background: #edf4fa; color: #123655; border-top: 2px solid #123655; padding-top: 12px; padding-bottom: 12px; font-size: 11pt; }
|
||||
.footer { margin-top: 28px; font-size: 8.5pt; border-top: 1px solid #e2e8f0; padding-top: 12px; }
|
||||
.footer strong { color: #123655; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
@@ -27,8 +41,12 @@
|
||||
<div style="flex: 1;">
|
||||
<h1>Rechnung</h1>
|
||||
<div class="muted">Rechnungsnummer: <strong>{{ meta.invoice_number }}</strong></div>
|
||||
<div class="muted">Rechnungsdatum: {{ meta.invoice_date }}</div>
|
||||
<div class="muted">Leistungsdatum: {{ meta.service_date }}</div>
|
||||
<div class="muted">Rechnungsdatum: {{ meta.invoice_date | de_date }}</div>
|
||||
{% if meta.service_period_start %}
|
||||
<div class="muted">Leistungszeitraum: {{ meta.service_period_start | de_date }} – {{ meta.service_period_end | de_date }}</div>
|
||||
{% else %}<div class="muted">Leistungsdatum: {{ meta.service_date | de_date }}</div>{% endif %}
|
||||
{% if meta.buyer_reference %}<div>Kundenreferenz: {{ meta.buyer_reference }}</div>{% endif %}
|
||||
{% if meta.order_reference %}<div>Bestellung: {{ meta.order_reference }}</div>{% endif %}
|
||||
</div>
|
||||
<div class="box" style="min-width: 260px;">
|
||||
<div><strong>{{ seller.name }}</strong></div>
|
||||
@@ -53,7 +71,7 @@
|
||||
|
||||
<div class="box" style="flex: 1;">
|
||||
<div class="muted">Zahlungsinformationen</div>
|
||||
<div>Fällig am: {{ payment.due_date }}</div>
|
||||
<div>Fällig am: {{ payment.due_date | de_date }}</div>
|
||||
<div>Verwendungszweck: {{ payment.reference }}</div>
|
||||
{% if payment.iban %}<div>IBAN: {{ payment.iban }}</div>{% endif %}
|
||||
{% if payment.bic %}<div>BIC: {{ payment.bic }}</div>{% endif %}
|
||||
@@ -81,10 +99,10 @@
|
||||
<strong>{{ it.name }}</strong><br>
|
||||
{% if it.description %}<span class="muted">{{ it.description }}</span>{% endif %}
|
||||
</td>
|
||||
<td class="right">{{ "%.2f"|format(it.quantity) }}</td>
|
||||
<td>{{ it.unit }}</td>
|
||||
<td class="right">{{ money(it.unit_price_net) }}</td>
|
||||
<td class="right">{{ "%.2f"|format(it.tax_rate) }}%</td>
|
||||
<td class="right">{{ number(it.quantity) }}</td>
|
||||
<td>{{ unit_label(it.unit) }}</td>
|
||||
<td class="right">{{ price(it.unit_price_net) }}</td>
|
||||
<td class="right">{{ number(it.tax_rate) }}%</td>
|
||||
<td class="right">{{ money(it.line_total_net) }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
@@ -93,6 +111,9 @@
|
||||
|
||||
<table class="totals">
|
||||
<tr><td class="right">Zwischensumme (netto):</td><td class="right"><strong>{{ money(totals.net_total) }}</strong></td></tr>
|
||||
{% for rate, tax in totals.tax_by_rate.items() %}
|
||||
<tr><td class="right">USt {{ rate }} % auf {{ money(totals.basis_by_rate[rate]) }}:</td><td class="right">{{ money(tax) }}</td></tr>
|
||||
{% endfor %}
|
||||
<tr><td class="right">USt gesamt:</td><td class="right"><strong>{{ money(totals.tax_total) }}</strong></td></tr>
|
||||
<tr><td class="right">Gesamt (brutto):</td><td class="right"><strong>{{ money(totals.gross_total) }}</strong></td></tr>
|
||||
</table>
|
||||
@@ -104,8 +125,6 @@
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="footer muted">
|
||||
Hinweis: Bei Nutzung von --combine wird ein Factur-X/ZUGFeRD XML-Datensatz in das PDF eingebettet.
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
Reference in new issue
Block a user