/** * Shared payment/settlement math for invoices. Both the global * `BillingService.recordPayment` and the warehouse fee invoice flow apply a * payment the same way — accumulate `paidAmount`, derive the outstanding * `balanceAmount`, and decide whether the invoice is now fully settled. Keeping * it here means the two flows can never drift on rounding or the * partial-vs-full threshold. */ /** Round to 2 decimals, avoiding binary float drift. */ export const round2 = (n: number): number => Math.round(n * 100) / 100; export interface SettlementResult { /** New cumulative amount paid. */ paidAmount: number; /** Remaining balance (0 once fully paid). */ balanceAmount: number; /** True once the balance reaches zero. */ fullyPaid: boolean; } /** * Apply a single payment of `amount` to an invoice with `totalAmount` already * carrying `currentPaid`. Caller is responsible for validating `amount > 0` and * the invoice being in a payable state. */ export function applySettlement( totalAmount: number, currentPaid: number, amount: number, ): SettlementResult { const total = Number(totalAmount); const paidAmount = round2(Number(currentPaid) + Number(amount)); const balanceAmount = Math.max(0, round2(total - paidAmount)); return { paidAmount, balanceAmount, fullyPaid: paidAmount >= total }; }