/** How far a purchase order has been invoiced. Pure — client-safe. Shared by Purchases and Suppliers 2. */ /** Invoiced within this many rand of the PO total counts as fully invoiced (rounding). */ export const CLOSE_TOLERANCE = 1; export type InvoiceStatus = "none" | "partial" | "complete" | "over"; /** * red = no invoice; amber = some invoiced but the money does not cover the PO (an invoice with * no amount typed in also lands here, since its value is unknown); teal = invoiced to within R1 * of the PO total, so it can be closed; purple = invoiced MORE than the PO by over R1. That is * never "closed": it is a typo, a duplicate or a changed order, and a person has to look. */ export function invoiceStatusOf(total: number, amounts: (number | null)[]): { status: InvoiceStatus; invoiced: number } { const invoiced = amounts.reduce((s, a) => s + (a ?? 0), 0); if (amounts.length === 0) return { status: "none", invoiced }; if (invoiced - total > CLOSE_TOLERANCE) return { status: "over", invoiced }; if (invoiced > 0 && total - invoiced <= CLOSE_TOLERANCE) return { status: "complete", invoiced }; return { status: "partial", invoiced }; } export const STATUS_STYLE: Record = { none: { color: "#D1495B", label: "No invoice yet" }, partial: { color: "#EDAE49", label: "Partly invoiced" }, complete: { color: "#00798C", label: "Fully invoiced — can be closed" }, over: { color: "#8B5CF6", label: "Invoiced MORE than the PO — check" }, };