import { describe, expect, it } from 'vitest' import { buildStatement } from '../src/statement' import type { Doc, Payment } from '../src/api' /** Minimal Doc factory — only the fields buildStatement reads matter. */ const doc = (over: Partial): Doc => ({ id: 'd', docType: 'INVOICE', docNo: 'INV/1', fy: '2026-27', clientId: 'c', docDate: '2026-04-01', status: 'sent', refDocId: null, taxablePaise: 0, cgstPaise: 0, sgstPaise: 0, igstPaise: 0, roundOffPaise: 0, payablePaise: 0, payload: { lines: [] }, source: 'manual', createdBy: 'u', createdAt: '', ...over, }) const pay = (over: Partial): Payment => ({ id: 'p', clientId: 'c', receivedOn: '2026-04-10', mode: 'bank', reference: '', amountPaise: 0, tdsPaise: 0, createdBy: 'u', createdAt: '', ...over, }) describe('buildStatement', () => { it('runs a balance across invoice → payment → credit note', () => { const s = buildStatement( [ doc({ id: 'a', docType: 'INVOICE', docNo: 'INV/1', docDate: '2026-04-01', payablePaise: 100_00 }), doc({ id: 'b', docType: 'CREDIT_NOTE', docNo: 'CN/1', docDate: '2026-04-20', payablePaise: 30_00 }), ], [pay({ id: 'x', receivedOn: '2026-04-10', amountPaise: 40_00 })], ) expect(s.rows.map((r) => r.balancePaise)).toEqual([100_00, 60_00, 30_00]) expect(s.closingPaise).toBe(30_00) // client still owes ₹30 expect(s.totalDebitPaise).toBe(100_00) expect(s.totalCreditPaise).toBe(70_00) }) it('excludes quotations, proformas, receipts, drafts and cancelled docs', () => { const s = buildStatement( [ doc({ id: 'q', docType: 'QUOTATION', docNo: 'QT/1', payablePaise: 500_00 }), doc({ id: 'pf', docType: 'PROFORMA', docNo: 'PI/1', payablePaise: 500_00 }), doc({ id: 'rc', docType: 'RECEIPT', docNo: 'RC/1', payablePaise: 500_00 }), doc({ id: 'dr', docType: 'INVOICE', docNo: null, payablePaise: 500_00 }), // unissued draft doc({ id: 'cx', docType: 'INVOICE', docNo: 'INV/9', status: 'cancelled', payablePaise: 500_00 }), ], [], ) expect(s.rows).toHaveLength(0) expect(s.closingPaise).toBe(0) }) it('orders by date, debits before credits on the same day', () => { const s = buildStatement( [doc({ id: 'a', docType: 'INVOICE', docNo: 'INV/1', docDate: '2026-05-05', payablePaise: 10_00 })], [pay({ id: 'x', receivedOn: '2026-05-05', amountPaise: 4_00 })], ) expect(s.rows.map((r) => r.kind)).toEqual(['invoice', 'payment']) }) it('counts TDS as part of the payment credit and can close in credit (advance)', () => { const s = buildStatement( [doc({ id: 'a', docType: 'INVOICE', docNo: 'INV/1', docDate: '2026-06-01', payablePaise: 100_00 })], [pay({ id: 'x', receivedOn: '2026-06-02', amountPaise: 90_00, tdsPaise: 20_00 })], ) expect(s.rows[1]!.creditPaise).toBe(110_00) // 90 + 20 TDS expect(s.closingPaise).toBe(-10_00) // overpaid → client in credit }) })