// apps/hq/test/mis.test.ts — D27: owner MIS cockpit composes existing aggregates. import { describe, it, expect } from 'vitest' import { openDb } from '../src/db' import { seedIfEmpty } from '../src/seed' import { createClient } from '../src/repos-clients' import { createModule, setPrice } from '../src/repos-modules' import { createDraft, issueDocument } from '../src/repos-documents' import { recordPayment } from '../src/repos-payments' import { misCockpit } from '../src/repos-mis' describe('misCockpit (D27)', () => { it('sums this-month billing, collections, outstanding and pipeline', async () => { const db = openDb(':memory:'); await seedIfEmpty(db) const c = await createClient(db, 'u1', { name: 'Bank', stateCode: '32' }) const m = await createModule(db, 'u1', { code: 'CORE', name: 'Core', allowedKinds: ['yearly'] }) await setPrice(db, 'u1', { moduleId: m.id, kind: 'yearly', pricePaise: 10_000_00, effectiveFrom: '2026-01-01' }) const today = new Date().toISOString().slice(0, 10) const month = today.slice(0, 7) // An issued invoice dated this month → billed + outstanding. const inv = await issueDocument(db, 'u1', (await createDraft(db, 'u1', { docType: 'INVOICE', clientId: c.id, lines: [{ moduleId: m.id, qty: 1, kind: 'yearly' }], })).id) await db.run(`UPDATE document SET doc_date=? WHERE id=?`, `${month}-05`, inv.id) // An open quote → pipeline. await createDraft(db, 'u1', { docType: 'QUOTATION', clientId: c.id, lines: [{ moduleId: m.id, qty: 1, kind: 'yearly' }] }) // A part payment this month → collections + reduces outstanding. await recordPayment(db, 'u1', { clientId: c.id, receivedOn: `${month}-10`, mode: 'bank', amountPaise: 5_000_00, allocations: [{ documentId: inv.id, amountPaise: 5_000_00 }], }) const mis = await misCockpit(db, today) expect(mis.invoicedThisPaise).toBe(11_800_00) // one invoice incl. GST expect(mis.collectedThisPaise).toBe(5_000_00) // the part payment expect(mis.outstandingPaise).toBe(6_800_00) // 11,800 − 5,000 expect(mis.outstandingClients).toBe(1) expect(mis.pipelineCount).toBe(1) // the open quote expect(mis.pipelineValuePaise).toBe(11_800_00) expect(mis.tickets).toMatchObject({ open: 0 }) expect(mis.reminders).toMatchObject({ queued: expect.any(Number), failed: expect.any(Number) }) }) })