// apps/hq/test/bulk-sms-purchase.test.ts — Task 7: ad-hoc bulk-SMS top-up documents, // tagged distinctly from subscription renewals via document.source. import { describe, it, expect } from 'vitest' import { openDb } from '../src/db' import { seedIfEmpty } from '../src/seed' import { createClient } from '../src/repos-clients' import { createModule, assignModule, setPrice, updateClientModule } from '../src/repos-modules' import { setUsageRateCard } from '../src/repos-rate-card' import { generateModuleRenewalQuote, generateBulkSmsPurchaseQuote } from '../src/repos-renewals' import { createDraft, getDocument } from '../src/repos-documents' // The founder's real card: ₹0.40 / 0.37 / 0.34 / 0.30 per SMS = 40/37/34/30 paise. const BANDS = [ { minQty: 50_000, ratePaise: 40 }, { minQty: 100_000, ratePaise: 37 }, { minQty: 200_000, ratePaise: 34 }, { minQty: 300_000, ratePaise: 30 }, ] async function world() { const db = openDb(':memory:') await seedIfEmpty(db) // company state 32, GST18 const c = await createClient(db, 'u1', { name: 'Karapuzha SCB', stateCode: '32' }) const sms = await createModule(db, 'u1', { code: 'SMS', name: 'Bulk SMS', allowedKinds: ['usage'] }) await setUsageRateCard(db, 'u1', sms.id, '2026-04-01', BANDS) const cm = await assignModule(db, 'u1', { clientId: c.id, moduleId: sms.id, kind: 'usage' }) return { db, c, sms, cm } } describe('generateBulkSmsPurchaseQuote', () => { it('raises a proforma tagged bulk_sms_topup for the given SMS quantity', async () => { const { db, cm } = await world() const doc = await generateBulkSmsPurchaseQuote(db, 'u1', cm.id, 100_000) expect(doc.docType).toBe('PROFORMA') expect(doc.source).toBe('bulk_sms_topup') expect(doc.payload.lines[0]!.qty).toBe(100_000) expect(doc.payablePaise).toBeGreaterThan(0) // Persisted correctly — a re-fetch from the DB carries the same tag. const full = (await getDocument(db, doc.id))! expect(full.source).toBe('bulk_sms_topup') }) it('rejects a non-positive or fractional quantity', async () => { const { db, cm } = await world() await expect(generateBulkSmsPurchaseQuote(db, 'u1', cm.id, 0)).rejects.toThrow() await expect(generateBulkSmsPurchaseQuote(db, 'u1', cm.id, -5)).rejects.toThrow() await expect(generateBulkSmsPurchaseQuote(db, 'u1', cm.id, 1.5)).rejects.toThrow() }) it('rejects an unknown client module', async () => { const { db } = await world() await expect(generateBulkSmsPurchaseQuote(db, 'u1', 'nope', 100_000)).rejects.toThrow(/not found/) }) }) describe('document.source tagging', () => { it('tags a renewal proforma module_renewal, and a manual draft keeps the default', async () => { const { db, c } = await world() // A separate yearly-priced module for the renewal path (usage modules enforce a // minimum-order qty that a qty:1 renewal line would fail). const yearly = await createModule(db, 'u1', { code: 'CORE', name: 'Core App', allowedKinds: ['yearly'] }) await setPrice(db, 'u1', { moduleId: yearly.id, kind: 'yearly', pricePaise: 10_000_00, effectiveFrom: '2026-01-01' }) const cm2 = await assignModule(db, 'u1', { clientId: c.id, moduleId: yearly.id, kind: 'yearly' }) await updateClientModule(db, 'u1', cm2.id, { nextRenewal: '2026-08-15' }) const renewal = await generateModuleRenewalQuote(db, 'u1', cm2.id) expect(renewal.source).toBe('module_renewal') const manual = await createDraft(db, 'u1', { docType: 'PROFORMA', clientId: c.id, lines: [{ moduleId: yearly.id, qty: 1, kind: 'yearly' }], }) expect(manual.source).toBe('hq') // default preserved when source is omitted }) })