feat(hq): extract pure prepareDraft from createDraft (permissive preview path)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>feat/client-detail-redesign
parent
7afd769a14
commit
90df8b0e9f
@ -0,0 +1,71 @@
|
||||
// apps/hq/test/prepare-draft.test.ts
|
||||
import { describe, it, expect } from 'vitest'
|
||||
import { openDb } from '../src/db'
|
||||
import { createClient } from '../src/repos-clients'
|
||||
import { createModule, setPrice } from '../src/repos-modules'
|
||||
import { prepareDraft, createDraft, type DraftInput } from '../src/repos-documents'
|
||||
|
||||
function setup() {
|
||||
const db = openDb(':memory:')
|
||||
db.prepare(`INSERT INTO setting (key, value) VALUES ('company.state_code','32')`).run()
|
||||
db.prepare(`INSERT INTO tax_class (class_code, rate_pct_bp, effective_from) VALUES ('GST18', 1800, '2017-07-01')`).run()
|
||||
const c = createClient(db, 'u1', { name: 'Acme', stateCode: '32' })
|
||||
const kar = createClient(db, 'u1', { name: 'BLR Co', stateCode: '29' })
|
||||
const m = createModule(db, 'u1', { code: 'POS', name: 'POS Billing', quoteContent: ['Cloud POS'] })
|
||||
setPrice(db, 'u1', { moduleId: m.id, kind: 'yearly', pricePaise: 10_000_00, effectiveFrom: '2026-04-01' })
|
||||
const noPrice = createModule(db, 'u1', { code: 'ANALYTICS', name: 'Analytics' }) // no price row
|
||||
return { db, c, kar, m, noPrice }
|
||||
}
|
||||
|
||||
describe('prepareDraft ↔ createDraft parity', () => {
|
||||
it('createDraft persists exactly what prepareDraft computes (totals + payload deep-equal)', () => {
|
||||
const { db, c, m } = setup()
|
||||
const input: DraftInput = { docType: 'INVOICE', clientId: c.id, terms: 'Net 15',
|
||||
lines: [{ moduleId: m.id, qty: 2, kind: 'yearly' }] }
|
||||
const prepared = prepareDraft(db, input)
|
||||
const saved = createDraft(db, 'u1', input)
|
||||
expect(saved.payload).toEqual(prepared.payload)
|
||||
expect({
|
||||
taxable: saved.taxablePaise, cgst: saved.cgstPaise, sgst: saved.sgstPaise,
|
||||
igst: saved.igstPaise, roundOff: saved.roundOffPaise, payable: saved.payablePaise,
|
||||
}).toEqual({
|
||||
taxable: prepared.totals.taxablePaise, cgst: prepared.totals.cgstPaise, sgst: prepared.totals.sgstPaise,
|
||||
igst: prepared.totals.igstPaise, roundOff: prepared.totals.roundOffPaise, payable: prepared.totals.payablePaise,
|
||||
})
|
||||
expect(prepared.warnings).toEqual([])
|
||||
})
|
||||
it('resolves IGST for an out-of-state client, exactly as createDraft would', () => {
|
||||
const { db, kar, m } = setup()
|
||||
const input: DraftInput = { docType: 'INVOICE', clientId: kar.id, lines: [{ moduleId: m.id, qty: 1, kind: 'yearly' }] }
|
||||
expect(prepareDraft(db, input).totals.igstPaise).toBe(1_800_00)
|
||||
})
|
||||
})
|
||||
|
||||
describe('prepareDraft strict vs permissive', () => {
|
||||
it('STRICT: a line with no price throws (save must refuse the same document)', () => {
|
||||
const { db, c, noPrice } = setup()
|
||||
expect(() => prepareDraft(db, { docType: 'QUOTATION', clientId: c.id,
|
||||
lines: [{ moduleId: noPrice.id, qty: 1, kind: 'yearly' }] }))
|
||||
.toThrow(/No price for module ANALYTICS/)
|
||||
})
|
||||
it('PERMISSIVE: the same line computes at ₹0 and is reported in warnings', () => {
|
||||
const { db, c, noPrice } = setup()
|
||||
const out = prepareDraft(db, { docType: 'QUOTATION', clientId: c.id,
|
||||
lines: [{ moduleId: noPrice.id, qty: 1, kind: 'yearly' }] }, { permissive: true })
|
||||
expect(out.totals.payablePaise).toBe(0)
|
||||
expect(out.warnings.some((w) => /ANALYTICS/.test(w))).toBe(true)
|
||||
})
|
||||
it('PERMISSIVE: zero lines → empty payload, ₹0, no computeBill throw', () => {
|
||||
const { db, c } = setup()
|
||||
const out = prepareDraft(db, { docType: 'QUOTATION', clientId: c.id, lines: [] }, { permissive: true })
|
||||
expect(out.payload.lines).toEqual([])
|
||||
expect(out.totals.payablePaise).toBe(0)
|
||||
})
|
||||
it('PERMISSIVE: unknown client does not throw; split defaults intra-state with a pending warning', () => {
|
||||
const { db, m } = setup()
|
||||
const out = prepareDraft(db, { docType: 'QUOTATION', clientId: '', lines: [{ moduleId: m.id, qty: 1, kind: 'yearly' }] }, { permissive: true })
|
||||
expect(out.totals.cgstPaise).toBe(900_00) // intra-state default (our state 32)
|
||||
expect(out.totals.igstPaise).toBe(0)
|
||||
expect(out.warnings.some((w) => /client/i.test(w))).toBe(true)
|
||||
})
|
||||
})
|
||||
Loading…
Reference in New Issue