* 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>