/** * Single source of truth for PO money math (issue #133). * * A PO's value is built up as: * * taxable = Σ qty · unitPrice (ex-GST) * gst = Σ qty · unitPrice · gstRate * inclGst = taxable + gst (the line-items total) * netPayable = inclGst + tcs − discount (PO-level charges, below GST) * * `netPayable` is what is stored in `PurchaseOrder.totalAmount`, so payments, * reports, and the advance-payment slider all operate on the true amount due. * * TCS and Discount are **absolute** rupee amounts (the UI's % control is only a * convenience that writes back the rupee value). Discount is applied **post-GST**. * The percentage shown for either is taken against `inclGst` (the GST-inclusive * line-items total) — see `amountToPercent` / `percentToAmount`. */ export interface MoneyItem { quantity: number; unitPrice: number; gstRate?: number | null; } export const DEFAULT_GST_RATE = 0.18; export interface PoMoney { taxable: number; gst: number; inclGst: number; tcs: number; discount: number; netPayable: number; } export function computePoMoney( items: MoneyItem[], tcs = 0, discount = 0 ): PoMoney { const taxable = items.reduce((s, i) => s + i.quantity * i.unitPrice, 0); const gst = items.reduce( (s, i) => s + i.quantity * i.unitPrice * (i.gstRate ?? DEFAULT_GST_RATE), 0 ); const inclGst = taxable + gst; const t = tcs || 0; const d = discount || 0; return { taxable, gst, inclGst, tcs: t, discount: d, netPayable: inclGst + t - d }; } /** Net payable (PO totalAmount) for a set of line items plus PO-level charges. */ export function poNetPayable(items: MoneyItem[], tcs = 0, discount = 0): number { return computePoMoney(items, tcs, discount).netPayable; } /** Convert an absolute charge to its percentage of a base (0 when base is 0). */ export function amountToPercent(amount: number, base: number): number { if (!base) return 0; return (amount / base) * 100; } /** Convert a percentage of a base back to an absolute charge. */ export function percentToAmount(percent: number, base: number): number { return (percent / 100) * base; }