// What the billing overview may say about money still owed. A figure is only stated when every // sent invoice, and every payment ledger that could reduce one, was actually read. import type { Invoice, PaymentReconciliation } from 'fhir/r4'; import { readInvoiceAllowPartialPay, readInvoiceTotalCents } from '../../../utils/invoiceTransform'; import { remainingCents } from '../../../utils/paymentModel'; import { sumCents } from '../../../utils/money'; import { pastDue, pastDueWithRemaining } from './invoiceDisplay'; export type Ledgers = Map; export interface OutstandingSummary { // null when the issued set itself is incomplete: a truncated fetch makes a count that LOOKS whole // but is not, which is the same defect as a wrong dollar figure that looks read. sent: number | null; pastDue: number | null; // null when some part could not be read: an unknown balance is never shown as a number. outstandingCents: number | null; } /** Only an invoice that allows part payment can carry payments while still sent. */ export const needsLedger = (invoice: Invoice): boolean => invoice.status === 'issued' && Boolean(invoice.id) && readInvoiceAllowPartialPay(invoice); export function summarizeOutstanding( issued: Invoice[], ledgers: Ledgers, complete: boolean, today?: string, ): OutstandingSummary { let unknownAmount = !complete; let unknownPastDue = false; const remaining = issued.map((invoice) => { const amount = readInvoiceTotalCents(invoice); if (amount === null) { unknownAmount = true; if (pastDue(invoice, today)) unknownPastDue = true; return 0; } if (!needsLedger(invoice)) return amount; const ledger = ledgers.get(invoice.id as string); if (!ledger || ledger === 'unreadable') { unknownAmount = true; return amount; } return remainingCents(invoice, ledger); }); return { sent: complete ? issued.length : null, pastDue: !complete || unknownPastDue ? null : issued.filter((invoice, index) => pastDueWithRemaining(invoice, remaining[index], today)).length, outstandingCents: unknownAmount ? null : sumCents(remaining), }; }