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/test/bulk-sms-purchase.test.ts

75 lines
3.6 KiB
TypeScript

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