fix: preserve invoice payment dates (#1332)

Signed-off-by: Emil <emilmattsson14@gmail.com>
This commit is contained in:
Mattsson
2026-08-02 20:44:59 +02:00
committed by GitHub
parent 18cdba3574
commit 9e54a8e400
28 changed files with 1975 additions and 73 deletions
+74 -4
View File
@@ -204,21 +204,35 @@ describe('match_batch_allocate', () => {
expect(apSum).toBe(6500)
// Verify all 3 supplier invoices flipped to 'paid'.
const inv1 = await client.query<{ status: string; paid_amount: string; remaining_amount: string }>(
`SELECT status, paid_amount, remaining_amount FROM public.supplier_invoices WHERE id = $1`,
const inv1 = await client.query<{
status: string
paid_amount: string
remaining_amount: string
paid_at_matches: boolean
}>(
`SELECT status, paid_amount, remaining_amount,
paid_at = TIMESTAMPTZ '2026-06-05 12:00:00+00' AS paid_at_matches
FROM public.supplier_invoices WHERE id = $1`,
[si1],
)
expect(inv1.rows[0]!.status).toBe('paid')
expect(Number(inv1.rows[0]!.paid_amount)).toBe(2000)
expect(Number(inv1.rows[0]!.remaining_amount)).toBe(0)
expect(inv1.rows[0]!.paid_at_matches).toBe(true)
// Verify 3 supplier_invoice_payments rows all reference the same JE.
const payments = await client.query<{ journal_entry_id: string; supplier_invoice_id: string }>(
`SELECT journal_entry_id, supplier_invoice_id
const payments = await client.query<{
journal_entry_id: string
supplier_invoice_id: string
payment_date_matches: boolean
}>(
`SELECT journal_entry_id, supplier_invoice_id,
payment_date = DATE '2026-06-05' AS payment_date_matches
FROM public.supplier_invoice_payments WHERE transaction_id = $1`,
[txId],
)
expect(payments.rows).toHaveLength(3)
expect(payments.rows.every((payment) => payment.payment_date_matches)).toBe(true)
const jeIds = new Set(payments.rows.map((p) => p.journal_entry_id))
expect(jeIds.size).toBe(1)
expect(jeIds.has(result.journal_entry_id!)).toBe(true)
@@ -248,6 +262,62 @@ describe('match_batch_allocate', () => {
})
})
it('anchors customer paid_at at UTC noon on the bank transaction date', async () => {
const { userId, companyId } = await seedTenant()
const customerId = randomUUID()
await getPool().query(
`INSERT INTO public.customers
(id, user_id, company_id, name, customer_type, country)
VALUES ($1, $2, $3, 'Kund AB', 'swedish_business', 'SE')`,
[customerId, userId, companyId],
)
const invoiceId = randomUUID()
await getPool().query(
`INSERT INTO public.invoices
(id, user_id, company_id, customer_id, invoice_number, invoice_date, due_date,
status, currency, subtotal, vat_amount, total, paid_amount, remaining_amount,
vat_treatment)
VALUES ($1, $2, $3, $4, $5, '2026-06-01', '2026-07-01', 'sent', 'SEK',
1000, 0, 1000, 0, 1000, 'standard_25')`,
[invoiceId, userId, companyId, customerId, `F-${invoiceId.slice(0, 8)}`],
)
const txId = await insertTransaction({
userId,
companyId,
amount: 1000,
date: '2026-06-07',
})
await withUserContext(userId, async (client) => {
const response = await client.query<{ match_batch_allocate: RpcResult }>(
`SELECT match_batch_allocate($1, $2::jsonb, $3)`,
[
txId,
JSON.stringify([{ kind: 'customer_invoice', invoice_id: invoiceId, amount: 1000 }]),
companyId,
],
)
expect(response.rows[0]!.match_batch_allocate.ok).toBe(true)
const invoice = await client.query<{ paid_at_matches: boolean }>(
`SELECT paid_at = TIMESTAMPTZ '2026-06-07 12:00:00+00' AS paid_at_matches
FROM public.invoices WHERE id = $1`,
[invoiceId],
)
expect(invoice.rows[0]!.paid_at_matches).toBe(true)
const payment = await client.query<{ payment_date_matches: boolean }>(
`SELECT payment_date = DATE '2026-06-07' AS payment_date_matches
FROM public.invoice_payments
WHERE invoice_id = $1 AND transaction_id = $2`,
[invoiceId, txId],
)
expect(payment.rows).toHaveLength(1)
expect(payment.rows[0]!.payment_date_matches).toBe(true)
})
})
it('rejects with BATCH_OVERSHOOT when allocation exceeds invoice remaining', async () => {
const { userId, companyId } = await seedTenant()
const supplier = await insertSupplier({ userId, companyId })