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