import type { Invoice, PaymentReconciliation } from 'fhir/r4'; import { invoiceTotalCents } from './invoiceTransform'; /** A payment ledger that could not be read in full: its payments are a subset, so nothing is worked out from them. */ export class IncompleteLedgerError extends Error { constructor() { super('The payments on this invoice could not be read in full.'); this.name = 'IncompleteLedgerError'; } } export const isIncompleteLedger = (error: unknown): boolean => error instanceof IncompleteLedgerError || (error as { name?: unknown } | null)?.name === 'IncompleteLedgerError'; // Pure, React/HTTP-free model deriving paid/remaining/partial-payment state from an invoice's // PaymentReconciliation ledger (server: GET /R4/PaymentReconciliation?invoice=). Kept dependency-free // (no services import) so it unit-tests in isolation and is reusable by any invoice surface (list, detail, // pay UI) without dragging in HTTP. /** Sum of every payment's `paymentAmount.value`, in integer cents. A missing paymentAmount counts as 0. */ export const paidCents = (recs: PaymentReconciliation[]): number => Math.round(recs.reduce((total, rec) => total + (rec.paymentAmount?.value ?? 0), 0) * 100); /** Frozen invoice total minus paid-to-date, in integer cents. Never negative (an overpay clamps to 0). */ export const remainingCents = (invoice: Invoice, recs: PaymentReconciliation[]): number => Math.max(0, invoiceTotalCents(invoice) - paidCents(recs)); // Partial-paid is only meaningful for an ISSUED invoice with money down but not fully covered - a draft // or balanced (fully paid/settled some other way) invoice is never "partially paid", regardless of what // its ledger shows. export const isPartiallyPaid = (invoice: Invoice, recs: PaymentReconciliation[]): boolean => invoice.status === 'issued' && paidCents(recs) > 0 && remainingCents(invoice, recs) > 0; export interface PaymentHistoryEntry { dateISO: string; amountCents: number; } /** Maps the ledger to a display-ready history, most-recent-first. Prefers `created`, falls back to `paymentDate`. */ export const paymentHistory = (recs: PaymentReconciliation[]): PaymentHistoryEntry[] => recs .map((rec) => ({ dateISO: rec.created ?? rec.paymentDate ?? '', amountCents: Math.round((rec.paymentAmount?.value ?? 0) * 100), })) .sort((a, b) => (a.dateISO < b.dateISO ? 1 : a.dateISO > b.dateISO ? -1 : 0));