import type { Doc, Payment } from './api' /** * One line of a client account statement: an invoice (debit), a credit note (credit) * or a payment (credit), with the running balance AFTER the line is applied. Kept as a * pure data shape so the running-balance maths can be unit-tested without the DOM. */ export interface StatementRow { date: string kind: 'invoice' | 'credit_note' | 'payment' particulars: string ref: string /** Charge that raises what the client owes (0 when this row is a credit). */ debitPaise: number /** Credit that lowers what the client owes (0 when this row is a debit). */ creditPaise: number /** Cumulative balance after this row — positive = owed by client, negative = in credit. */ balancePaise: number } export interface Statement { rows: StatementRow[] totalDebitPaise: number totalCreditPaise: number /** Final running balance: > 0 the client owes us, < 0 they are in credit (advance). */ closingPaise: number } /** * Build a statement of account from the client's ledger. Only posted receivables move * the balance: issued (numbered), non-cancelled INVOICEs are debits and CREDIT_NOTEs are * credits; every payment is a credit (amount + TDS, matching how the ledger settles). * Quotations, proformas, receipts and drafts are not account postings and are excluded. * Rows are ordered by date, debits before credits on the same day (conventional). */ export function buildStatement(documents: Doc[], payments: Payment[]): Statement { type Entry = { date: string; order: number; row: Omit } const entries: Entry[] = [] for (const d of documents) { if (d.docNo === null || d.status === 'cancelled') continue if (d.docType === 'INVOICE') { entries.push({ date: d.docDate, order: 0, row: { date: d.docDate, kind: 'invoice', particulars: 'Invoice', ref: d.docNo, debitPaise: d.payablePaise, creditPaise: 0 }, }) } else if (d.docType === 'CREDIT_NOTE') { entries.push({ date: d.docDate, order: 0, row: { date: d.docDate, kind: 'credit_note', particulars: 'Credit note', ref: d.docNo, debitPaise: 0, creditPaise: d.payablePaise }, }) } } for (const p of payments) { entries.push({ date: p.receivedOn, order: 1, row: { date: p.receivedOn, kind: 'payment', particulars: `Payment (${p.mode})`, ref: p.reference, debitPaise: 0, creditPaise: p.amountPaise + p.tdsPaise, }, }) } entries.sort((a, b) => (a.date < b.date ? -1 : a.date > b.date ? 1 : a.order - b.order)) let balance = 0 let totalDebitPaise = 0 let totalCreditPaise = 0 const rows: StatementRow[] = entries.map((e) => { balance += e.row.debitPaise - e.row.creditPaise totalDebitPaise += e.row.debitPaise totalCreditPaise += e.row.creditPaise return { ...e.row, balancePaise: balance } }) return { rows, totalDebitPaise, totalCreditPaise, closingPaise: balance } }