From 36fcf795a8d9cea35854b9b06b8f0ff7f8fe7d52 Mon Sep 17 00:00:00 2001 From: Thomas Joise Date: Fri, 17 Jul 2026 12:35:21 +0530 Subject: [PATCH] test(documents): assert due-date stamp on the recurring-generation issue path Closes the WS-A coverage gap: due-dates.test.ts proved the manual-issue and convert paths but relied on code inspection for recurring generation. A manual recurring plan now runs through runDailyScan and the generated invoice's due_date is asserted as doc_date + seeded 15-day terms. Suite 310/310. Co-Authored-By: Claude Fable 5 --- apps/hq/test/due-dates.test.ts | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/apps/hq/test/due-dates.test.ts b/apps/hq/test/due-dates.test.ts index c105506..c3c2592 100644 --- a/apps/hq/test/due-dates.test.ts +++ b/apps/hq/test/due-dates.test.ts @@ -3,8 +3,9 @@ import { describe, it, expect } from 'vitest' import { openDb } from '../src/db' import { seedIfEmpty } from '../src/seed' import { createClient } from '../src/repos-clients' -import { createModule, setPrice } from '../src/repos-modules' -import { convertDocument, createDraft, getDocument, issueDocument } from '../src/repos-documents' +import { assignModule, createModule, setPrice } from '../src/repos-modules' +import { createRecurringPlan } from '../src/repos-recurring' +import { convertDocument, createDraft, getDocument, issueDocument, listDocuments } from '../src/repos-documents' import { getReminder, listReminders, setSetting, upsertReminder } from '../src/repos-reminders' import { reminderContext } from '../src/send-reminder' import { reminderEmail } from '../src/reminder-templates' @@ -16,6 +17,12 @@ const deps: ScanDeps = { now: () => '2026-07-17T00:00:00Z', } +function addDaysIso(iso: string, days: number): string { + const d = new Date(`${iso}T00:00:00Z`) + d.setUTCDate(d.getUTCDate() + days) + return d.toISOString().slice(0, 10) +} + function world() { const db = openDb(':memory:'); seedIfEmpty(db) // seeds billing.payment_terms_days=15 const c = createClient(db, 'u1', { name: 'Acme', stateCode: '32', contacts: [{ name: 'R', email: 'r@acme.in' }] }) @@ -77,6 +84,17 @@ describe('due dates (D18)', () => { expect(inv.dueDate).not.toBeNull() // stamped from terms on the invoice's own doc_date }) + it('recurring generation stamps the due date on the invoice it issues (third issue path)', async () => { + const { db, c, m } = world() + setPrice(db, 'u1', { moduleId: m.id, kind: 'monthly', pricePaise: 2_000_00, effectiveFrom: '2026-01-01' }) + const cm = assignModule(db, 'u1', { clientId: c.id, moduleId: m.id, kind: 'monthly' }) + createRecurringPlan(db, 'u1', { clientId: c.id, clientModuleId: cm.id, cadence: 'monthly', nextRun: '2026-07-17', policy: 'manual' }) + await runDailyScan(db, deps, '2026-07-17') + const [inv] = listDocuments(db, { clientId: c.id, type: 'INVOICE' }) + expect(inv).toBeDefined() + expect(inv!.dueDate).toBe(addDaysIso(inv!.docDate, 15)) // doc_date + seeded 15-day terms + }) + it('bad dueDate format is rejected at draft', () => { const { db, c, m } = world() expect(() => createDraft(db, 'u1', {