fix(invoices): remaining_amount can no longer be inserted as 0 on an unpaid invoice (#1655)

* fix(invoices): remaining_amount can no longer be inserted as 0 on an unpaid invoice

remaining_amount is NOT NULL DEFAULT 0 and every payment surface (payment
dialog, bank match, Stripe sync, agent mark-paid) reads it as the customer's
open balance. Four writers omitted it, so their invoices looked settled: the
dialog rejected every payment as an overpayment and the bank match saw
nothing to clear. Prod carried 337 such open invoices on 2026-08-17
(backfilled the same day, snapshot in _backfill_remaining_20260817).

- Migration 20260817191708: BEFORE INSERT trigger invoices_derive_remaining_amount.
  When remaining_amount is NULL/0 on a real invoice (document_type invoice,
  not a credit note) with total > 0 and a status that still owes money, it
  becomes total - paid_amount - deduction_total (>= 0). The ROT/RUT share is a
  1513 receivable on Skatteverket, never the customer's, exactly as
  buildInvoiceWriteData computes it. INSERT only: settlement code owns
  updates and legitimately writes 0 when paid in full.
- pg-real test: derivation, explicit value respected, paid/prior/deduction
  arithmetic, drafts + overdue, paid/cancelled keep 0, credit notes and
  proformas untouched, never negative.
- Writers fixed as well: proforma -> invoice conversion (dashboard route and
  MCP commitConvertInvoice), MCP commitCreateInvoice, sandbox seed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(sandbox): every row in the seed invoice batch carries remaining_amount + paid_amount

PostgREST normalises a bulk insert to the union of keys, so a row that
omits a column the others set arrives as NULL, not as the default. Keep the
draft row on the same contract as the rest of the batch (CodeRabbit).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Jakob Wennberg <311770904+jakobwennberg-oss@users.noreply.github.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Jakob Wennberg
2026-08-17 21:36:16 +02:00
committed by GitHub
co-authored by Claude Fable 5 Jakob Wennberg
parent e030393fe6
commit a447b29210
6 changed files with 188 additions and 0 deletions
+7
View File
@@ -1671,6 +1671,11 @@ async function commitCreateInvoice(
vat_amount_sek: vatAmountSek,
total,
total_sek: totalSek,
// Fresh unpaid receivable: remaining_amount is what every payment
// surface reads as the open balance; leaving the NOT NULL DEFAULT 0
// made every agent-created invoice look settled.
remaining_amount: total,
paid_amount: 0,
vat_treatment: notVatRegistered ? 'exempt' : vatRules.treatment,
vat_rate: isMixedRate ? null : (uniqueRates.values().next().value ?? vatRules.rate),
moms_ruta: notVatRegistered ? null : vatRules.momsRuta,
@@ -4438,6 +4443,8 @@ async function commitConvertInvoice(
vat_amount: proforma.vat_amount,
vat_amount_sek: proforma.vat_amount_sek,
total: proforma.total,
remaining_amount: proforma.total,
paid_amount: 0,
total_sek: proforma.total_sek,
vat_treatment: proforma.vat_treatment,
vat_rate: proforma.vat_rate,