export interface InvoiceItemGroup { /** 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( items: readonly T[] ): InvoiceItemGroup[] { const byRate = new Map(); 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), })); }