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.
51 lines
2.6 KiB
TypeScript
51 lines
2.6 KiB
TypeScript
// apps/hq/test/renewals.test.ts — D27: renewals command center + one-click module quote.
|
|
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, assignModule, updateClientModule } from '../src/repos-modules'
|
|
import { createAmc } from '../src/repos-amc'
|
|
import { renewalsDue, generateModuleRenewalQuote } from '../src/repos-renewals'
|
|
import { getDocument } from '../src/repos-documents'
|
|
|
|
async function world() {
|
|
const db = openDb(':memory:'); await seedIfEmpty(db)
|
|
const c = await createClient(db, 'u1', { name: 'Karapuzha SCB', stateCode: '32' })
|
|
const m = await createModule(db, 'u1', { code: 'SMS', name: 'Bulk SMS', allowedKinds: ['yearly'] })
|
|
await setPrice(db, 'u1', { moduleId: m.id, kind: 'yearly', pricePaise: 10_000_00, effectiveFrom: '2026-01-01' })
|
|
const cm = await assignModule(db, 'u1', { clientId: c.id, moduleId: m.id, kind: 'yearly' })
|
|
await updateClientModule(db, 'u1', cm.id, { nextRenewal: '2026-08-15' })
|
|
return { db, c, m, cm }
|
|
}
|
|
|
|
describe('renewalsDue (D27)', () => {
|
|
it('lists module + AMC renewals in a window with amount and days-until, earliest first', async () => {
|
|
const { db, c, cm } = await world()
|
|
await createAmc(db, 'u1', {
|
|
clientId: c.id, coverage: 'Support', periodFrom: '2025-09-01', periodTo: '2026-08-31', amountPaise: 15_000_00,
|
|
})
|
|
const rows = await renewalsDue(db, '2026-07-01', '2026-09-30', '2026-08-01')
|
|
expect(rows).toHaveLength(2)
|
|
const mod = rows.find((r) => r.kind === 'module')!
|
|
expect(mod).toMatchObject({ id: cm.id, label: 'SMS', dueOn: '2026-08-15', amountPaise: 10_000_00 })
|
|
expect(mod.daysUntil).toBe(14)
|
|
const amc = rows.find((r) => r.kind === 'amc')!
|
|
expect(amc).toMatchObject({ dueOn: '2026-08-31', amountPaise: 15_000_00 })
|
|
expect(rows[0]!.dueOn <= rows[1]!.dueOn).toBe(true) // sorted earliest-first
|
|
// A renewal outside the window is excluded.
|
|
expect(await renewalsDue(db, '2026-07-01', '2026-08-10', '2026-08-01')).toHaveLength(0)
|
|
})
|
|
})
|
|
|
|
describe('generateModuleRenewalQuote (D27)', () => {
|
|
it('creates a proforma draft carrying the module line at the price-book rate', async () => {
|
|
const { db, cm } = await world()
|
|
const draft = await generateModuleRenewalQuote(db, 'u1', cm.id)
|
|
expect(draft.docType).toBe('PROFORMA')
|
|
expect(draft.status).toBe('draft')
|
|
const full = (await getDocument(db, draft.id))!
|
|
expect(full.taxablePaise).toBe(10_000_00) // one SMS-yearly line at the dated price
|
|
expect(full.payablePaise).toBe(11_800_00) // + 18% GST
|
|
})
|
|
})
|