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 <noreply@anthropic.com>
feat/client-detail-redesign
Thomas Joise 5 days ago
parent bc140fc54c
commit 36fcf795a8

@ -3,8 +3,9 @@ import { describe, it, expect } from 'vitest'
import { openDb } from '../src/db' import { openDb } from '../src/db'
import { seedIfEmpty } from '../src/seed' import { seedIfEmpty } from '../src/seed'
import { createClient } from '../src/repos-clients' import { createClient } from '../src/repos-clients'
import { createModule, setPrice } from '../src/repos-modules' import { assignModule, createModule, setPrice } from '../src/repos-modules'
import { convertDocument, createDraft, getDocument, issueDocument } from '../src/repos-documents' 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 { getReminder, listReminders, setSetting, upsertReminder } from '../src/repos-reminders'
import { reminderContext } from '../src/send-reminder' import { reminderContext } from '../src/send-reminder'
import { reminderEmail } from '../src/reminder-templates' import { reminderEmail } from '../src/reminder-templates'
@ -16,6 +17,12 @@ const deps: ScanDeps = {
now: () => '2026-07-17T00:00:00Z', 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() { function world() {
const db = openDb(':memory:'); seedIfEmpty(db) // seeds billing.payment_terms_days=15 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' }] }) 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 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', () => { it('bad dueDate format is rejected at draft', () => {
const { db, c, m } = world() const { db, c, m } = world()
expect(() => createDraft(db, 'u1', { expect(() => createDraft(db, 'u1', {

Loading…
Cancel
Save