since3-portal/modules/shared/lib/invoice-math.ts
2026-09-29 13:46:50 +02:00

50 lines
1.8 KiB
TypeScript

import Decimal from "decimal.js";
type Item = { quantity: number; unitPrice: number; taxRate: number };
// Dezimal statt Float, damit Summen exakt der View invoices_overview entsprechen
// (Float: 0.01 * 14.50 = 0.14499… → 0.14, Postgres numeric: 0.15).
const decimal = (value: number) => new Decimal(value.toString());
const round2 = (value: Decimal) => value.toDecimalPlaces(2, Decimal.ROUND_HALF_UP);
const lineNetDecimal = (item: Item) => round2(decimal(item.quantity).times(decimal(item.unitPrice)));
const lineTaxDecimal = (item: Item) =>
round2(decimal(item.quantity).times(decimal(item.unitPrice)).times(decimal(item.taxRate)).div(100));
export function lineNet(item: Item) {
return lineNetDecimal(item).toNumber();
}
export function invoiceTotals(items: Item[]) {
const net = items.reduce((sum, item) => sum.plus(lineNetDecimal(item)), new Decimal(0));
const tax = items.reduce((sum, item) => sum.plus(lineTaxDecimal(item)), new Decimal(0));
const rates = new Set(items.map((item) => item.taxRate));
return {
net: net.toNumber(),
tax: tax.toNumber(),
gross: net.plus(tax).toNumber(),
uniformTaxRate: rates.size === 1 ? [...rates][0] : null,
};
}
const numberFormat = new Intl.NumberFormat("de-DE", { minimumFractionDigits: 2, maximumFractionDigits: 2 });
export function formatAmount(value: number) {
return numberFormat.format(value);
}
export function formatCurrency(value: number) {
return `${numberFormat.format(value)} €`;
}
export function formatDate(isoDate: string) {
const [year, month, day] = isoDate.slice(0, 10).split("-");
return `${day}.${month}.${year}`;
}
export function toIsoDate(date: Date) {
const month = String(date.getMonth() + 1).padStart(2, "0");
const day = String(date.getDate()).padStart(2, "0");
return `${date.getFullYear()}-${month}-${day}`;
}