// Previously duplicated as groupByTaxRate() independently inside // invoicePdf.tsx and correctionInvoicePdf.tsx (and, on the Payload backend, // their own copies) — pulled out so the storefront's own MwSt. breakdowns // (checkout summary, order confirmation page/email, account order pages) // can share the exact same math instead of a fourth hand-rolled version // drifting out of sync with what the actual invoices say. export type TaxBreakdownLine = { quantity: number; unitPrice: number; taxRatePercent: number }; export type TaxBreakdownGroup = { rate: number; net: number; tax: number; gross: number }; // Groups line items by their effective VAT rate, then scales each group's // gross total by however much shipping/discount moved the grand total away // from the raw item subtotal — proportional to that group's own share of // the subtotal, not a flat split. Passing discountAmount=0 and // shippingCost=0 (e.g. a Gutschrift, which excludes both) collapses `scale` // to 1, i.e. no adjustment at all. export function computeTaxBreakdown( items: TaxBreakdownLine[], subtotal: number, discountAmount: number, shippingCost: number, ): TaxBreakdownGroup[] { const groups = new Map(); for (const item of items) { const lineGross = item.quantity * item.unitPrice; groups.set(item.taxRatePercent, (groups.get(item.taxRatePercent) ?? 0) + lineGross); } const scale = subtotal > 0 ? (subtotal - discountAmount + shippingCost) / subtotal : 1; return Array.from(groups.entries()) .map(([rate, lineGross]) => { const gross = lineGross * scale; const net = gross / (1 + rate / 100); return { rate, net, tax: gross - net, gross }; }) .sort((a, b) => b.rate - a.rate); }