import { describe, it, expect } from 'vitest'
import { documentHtml, rupeesInWords } from '../src/templates'
const doc = {
id: 'd1', docType: 'INVOICE', docNo: 'INV/26-27-0001', fy: '2026-27', clientId: 'c1',
docDate: '2026-07-10', status: 'draft', refDocId: null, taxablePaise: 10_000_00,
cgstPaise: 900_00, sgstPaise: 900_00, igstPaise: 0, roundOffPaise: 0, payablePaise: 11_800_00,
payload: { lines: [{ itemId: 'm1', name: 'POS Billing — yearly', hsn: '998313', qty: 1,
unitCode: 'NOS', unitPricePaise: 10_000_00, grossPaise: 10_000_00, discountPaise: 0,
taxablePaise: 10_000_00, taxRateBp: 1800, cgstPaise: 900_00, sgstPaise: 900_00,
igstPaise: 0, cessPaise: 0, lineTotalPaise: 11_800_00 }], totals: {} },
} as never
const client = { id: 'c1', code: 'CL0001', name: 'Acme', stateCode: '32', address: 'Kochi',
contacts: [], status: 'active', notes: '' } as never
const company = { 'company.name': 'Tecnostac', 'company.gstin': '', 'company.address': '',
'company.phone': '', 'company.email': '', 'company.bank': 'HDFC ****1234' }
describe('document html', () => {
it('renders number, SAC, Indian-grouped totals and TAX INVOICE title', () => {
const html = documentHtml(doc, client, company)
expect(html).toContain('INV/26-27-0001')
expect(html).toContain('TAX INVOICE')
expect(html).toContain('998313') // SAC column
expect(html).toContain('₹11,800.00') // formatINR grouping
expect(html).toContain('HDFC ****1234') // bank block on invoices
})
it('titles a quotation QUOTATION and omits the bank block', () => {
const html = documentHtml({ ...(doc as object), docType: 'QUOTATION', docNo: 'QT/26-27-0001' } as never, client, company)
expect(html).toContain('QUOTATION')
expect(html).not.toContain('HDFC')
})
it('renders per-line content bullets when present and nothing when absent', () => {
const withContent = {
...(doc as object),
payload: { lines: (doc as never as { payload: { lines: unknown[] } }).payload.lines,
lineContents: [['Bulk SMS gateway', 'DLT template registration']], totals: {} },
} as never
const html = documentHtml(withContent, client, company)
expect(html).toContain('
')
expect(html).toContain('Bulk SMS gateway')
expect(html).toContain('DLT template registration')
// The base fixture has no lineContents, so no stray content list is emitted.
expect(documentHtml(doc, client, company)).not.toContain('')
})
})
describe('rupees in words', () => {
it('spells the payable in the Indian system', () => {
expect(rupeesInWords(11_800_00)).toBe('Rupees Eleven Thousand Eight Hundred Only')
expect(rupeesInWords(1_23_45_678_00)).toBe(
'Rupees One Crore Twenty Three Lakh Forty Five Thousand Six Hundred Seventy Eight Only')
expect(rupeesInWords(50)).toBe('Fifty Paise Only')
})
})
describe('quotation sections (D18 WS-H)', () => {
const line = (n: number) => ({
itemId: `m${n}`, name: `Module ${n}`, hsn: '998313', qty: 1, unitCode: 'NOS',
unitPricePaise: 1_000_00, grossPaise: 1_000_00, discountPaise: 0, taxablePaise: 1_000_00,
taxRateBp: 1800, cgstPaise: 90_00, sgstPaise: 90_00, igstPaise: 0, cessPaise: 0,
lineTotalPaise: 1_180_00,
})
const fiveModuleQuote = {
...(doc as object), docType: 'QUOTATION', docNo: 'QT/26-27-0002',
payload: {
lines: [1, 2, 3, 4, 5].map(line),
totals: {},
lineContents: [['Core ledgers', 'Day-end'], ['RTGS/NEFT'], [], ['App for members'], ['SMS 50k pack']],
},
} as never
it('a 5-module quotation renders 5 titled sections, the summary line, and NO billing grid', () => {
const html = documentHtml(fiveModuleQuote, client, company)
expect(html.match(/class="qsection/g)).toHaveLength(5)
expect(html.match(/class="qsection qbreak/g)).toHaveLength(4) // fresh page from the 2nd on (print)
expect(html).toContain('This proposal covers:')
expect(html).toContain('Module 1')
expect(html).toContain('Core ledgers') // per-module contents land in their section
expect(html).toContain('SMS 50k pack')
expect(html).not.toContain('') // proposals are not billing grids
expect(html).toContain('Grand Total') // totals block still follows
})
it('a module without content prints a tight section (no empty included-block)', () => {
const html = documentHtml(fiveModuleQuote, client, company)
expect(html.match(/What's included/g)).toHaveLength(4) // section 3 has no contents
})
it('INVOICE keeps the compact billing grid — layout untouched by WS-H', () => {
const html = documentHtml(doc, client, company)
expect(html).toContain('')
expect(html).not.toContain('class="qsection')
})
})