@ -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' , { module Id : m . id , kind : 'monthly' , pricePaise : 2_000_00 , effectiveFrom : '2026-01-01' } )
const cm = assignModule ( db , 'u1' , { clientId : c.id , module Id : 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' , {