feat(rechnung): branded invoice with VAT ID and per-rate grouping
The invoice PDF and the invoice page now share one branded layout: logo (uploaded brand logo, otherwise the drawn honeycomb mark) and wordmark in the header, honey accents, a forest-green table header, a meta box with invoice number, date, order number and VAT ID, and a footer with address, contact, VAT ID and page numbers on every page. A mixed cart (e.g. honey at 7 % and a beeswax candle at 19 %) lists its items in one block per VAT rate with a subtotal each. Next to the totals, a VAT table shows net, VAT and gross per rate plus a sum row. Long invoices continue on further pages with the table header repeated. The VAT identification number is a new company field (company.vatId), editable in Admin > Inhalte, defaulting to the number already stated in the imprint. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
This commit is contained in:
1 parent
1daef79026
commit
77bdd9958c
12 files changed
+965
-246
No files matched your search
@@ -0,0 +1,36 @@
|
||||
export interface InvoiceItemGroup<T> {
|
||||
/** Steuersatz der Gruppe; null bei Positionen aus der Zeit vor der Steuerausweisung. */
|
||||
ratePercent: number | null;
|
||||
items: T[];
|
||||
/** Summe der Positionen dieser Gruppe (brutto, wie an den Positionen gespeichert). */
|
||||
totalCents: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Teilt die Positionen einer Rechnung nach Steuersatz auf, damit ein
|
||||
* gemischter Korb (Honig 7 %, Bienenwachskerze 19 %) auf der Rechnung in
|
||||
* getrennten Bloecken steht — jede Position laesst sich so auf einen Blick
|
||||
* ihrem Satz zuordnen, und die Steueraufstellung darunter (lib/vat.ts,
|
||||
* invoiceVatBreakdown) hat ein sichtbares Gegenstueck.
|
||||
*
|
||||
* Gruppen aufsteigend nach Satz, Positionen innerhalb einer Gruppe in ihrer
|
||||
* urspruenglichen Reihenfolge. Positionen ohne Satz bilden eine eigene
|
||||
* Gruppe am Ende, statt einem Satz zugeschlagen zu werden, den sie nie hatten.
|
||||
* Rechnungsansicht und PDF nutzen dieselbe Funktion.
|
||||
*/
|
||||
export function groupInvoiceItemsByVatRate<T extends { totalCents: number; vatRatePercent: number | null }>(
|
||||
items: readonly T[]
|
||||
): InvoiceItemGroup<T>[] {
|
||||
const byRate = new Map<number | null, T[]>();
|
||||
for (const item of items) {
|
||||
byRate.set(item.vatRatePercent, [...(byRate.get(item.vatRatePercent) ?? []), item]);
|
||||
}
|
||||
|
||||
return [...byRate.entries()]
|
||||
.sort(([a], [b]) => (a ?? Number.POSITIVE_INFINITY) - (b ?? Number.POSITIVE_INFINITY))
|
||||
.map(([ratePercent, groupItems]) => ({
|
||||
ratePercent,
|
||||
items: groupItems,
|
||||
totalCents: groupItems.reduce((sum, item) => sum + item.totalCents, 0),
|
||||
}));
|
||||
}
|
||||
Reference in new issue
Block a user