|
|
// apps/hq/test/functional-d26.test.ts — D26: GST summary, document search, bulk ops.
|
|
|
import { describe, it, expect } from 'vitest'
|
|
|
import { openDb } from '../src/db'
|
|
|
import { seedIfEmpty } from '../src/seed'
|
|
|
import { createClient, bulkUpdateClients, getClient } from '../src/repos-clients'
|
|
|
import { createModule, setPrice, assignModule } from '../src/repos-modules'
|
|
|
import { createDraft, issueDocument, createCreditNote, listDocuments, getDocument } from '../src/repos-documents'
|
|
|
import { gstSummary } from '../src/repos-reports'
|
|
|
import { listProjectMilestones, bulkSetMilestone } from '../src/repos-milestones'
|
|
|
|
|
|
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 m = await createModule(db, 'u1', { code: 'CORE', name: 'Core', allowedKinds: ['yearly'] })
|
|
|
await setPrice(db, 'u1', { moduleId: m.id, kind: 'yearly', pricePaise: 10_000_00, effectiveFrom: '2026-01-01' })
|
|
|
return { db, c, m }
|
|
|
}
|
|
|
const mkInvoice = async (db: any, c: any, m: any, date: string) => {
|
|
|
const inv = await issueDocument(db, 'u1', (await createDraft(db, 'u1', {
|
|
|
docType: 'INVOICE', clientId: c.id, lines: [{ moduleId: m.id, qty: 1, kind: 'yearly' }],
|
|
|
})).id)
|
|
|
await db.run(`UPDATE document SET doc_date=? WHERE id=?`, date, inv.id)
|
|
|
return inv
|
|
|
}
|
|
|
|
|
|
describe('GST summary (D26, money locked)', () => {
|
|
|
it('sums invoices per month; credit notes subtract; CGST+SGST split exact', async () => {
|
|
|
const { db, c, m } = await world()
|
|
|
await mkInvoice(db, c, m, '2026-07-05') // ₹10,000 + 18% = 11,800; CGST 900 SGST 900
|
|
|
const inv2 = await mkInvoice(db, c, m, '2026-07-20')
|
|
|
await mkInvoice(db, c, m, '2026-08-01')
|
|
|
// A full, ISSUED credit note against inv2 (same month) reverses one invoice's tax.
|
|
|
const cnDraft = await createCreditNote(db, 'u1', inv2.id)
|
|
|
await issueDocument(db, 'u1', cnDraft.id) // a CN counts for GST only once numbered
|
|
|
await db.run(`UPDATE document SET doc_date='2026-07-25' WHERE id=?`, cnDraft.id)
|
|
|
void getDocument
|
|
|
|
|
|
const { rows, total } = await gstSummary(db, { from: '2026-07-01', to: '2026-08-31' })
|
|
|
const jul = rows.find((r) => r.period === '2026-07')!
|
|
|
expect(jul).toMatchObject({ invoiceCount: 2, creditNoteCount: 1 })
|
|
|
// 2 invoices − 1 credit note = net 1 invoice's worth in July.
|
|
|
expect(jul.taxablePaise).toBe(10_000_00)
|
|
|
expect(jul.cgstPaise).toBe(900_00)
|
|
|
expect(jul.sgstPaise).toBe(900_00)
|
|
|
expect(jul.taxPaise).toBe(1_800_00)
|
|
|
expect(jul.totalPaise).toBe(11_800_00)
|
|
|
const aug = rows.find((r) => r.period === '2026-08')!
|
|
|
expect(aug.totalPaise).toBe(11_800_00)
|
|
|
expect(total.totalPaise).toBe(23_600_00) // net July (1) + Aug (1)
|
|
|
expect(total.taxPaise).toBe(3_600_00)
|
|
|
})
|
|
|
})
|
|
|
|
|
|
describe('document search (D26)', () => {
|
|
|
it('finds documents by number and by client name, case-insensitive', async () => {
|
|
|
const { db, c, m } = await world()
|
|
|
const inv = await mkInvoice(db, c, m, '2026-07-05')
|
|
|
expect((await listDocuments(db, { q: inv.docNo!.toLowerCase() })).total).toBe(1)
|
|
|
expect((await listDocuments(db, { q: 'KARAPUZHA' })).total).toBe(1)
|
|
|
expect((await listDocuments(db, { q: 'nomatch' })).total).toBe(0)
|
|
|
expect((await listDocuments(db, {})).total).toBe(1) // no q = all
|
|
|
})
|
|
|
})
|
|
|
|
|
|
describe('bulk operations (D26)', () => {
|
|
|
it('bulk-updates client fields in one call, each audited', async () => {
|
|
|
const { db } = await world()
|
|
|
const a = await createClient(db, 'u1', { name: 'A', stateCode: '32' })
|
|
|
const b = await createClient(db, 'u1', { name: 'B', stateCode: '32' })
|
|
|
const out = await bulkUpdateClients(db, 'u1', [a.id, b.id], { district: 'Ernakulam' })
|
|
|
expect(out.updated).toBe(2)
|
|
|
expect((await getClient(db, a.id))!.district).toBe('Ernakulam')
|
|
|
expect((await getClient(db, b.id))!.district).toBe('Ernakulam')
|
|
|
})
|
|
|
|
|
|
it('bulk-ticks a milestone across projects', async () => {
|
|
|
const { db, c, m } = await world()
|
|
|
const p = await assignModule(db, 'u1', { clientId: c.id, moduleId: m.id, kind: 'yearly' })
|
|
|
const out = await bulkSetMilestone(db, 'u1', [p.id], 'installed', true, '2026-07-10')
|
|
|
expect(out.updated).toBe(1)
|
|
|
const ms = await listProjectMilestones(db, p.id)
|
|
|
expect(ms.find((x) => x.key === 'installed')).toMatchObject({ done: true, doneOn: '2026-07-10' })
|
|
|
})
|
|
|
})
|