You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
sims-hq/apps/hq-web/test/statement.test.ts

64 lines
2.9 KiB
TypeScript

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>): 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>): 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
})
})