be0478c219
* fix(bokslut): kontantmetod cut-off treats the ROT/RUT share as settled on 1513 Root cause: collectKontantmetodCutoff compared invoice_payments against the gross invoice total and never read deduction_total. Under fakturamodellen the customer owes total minus the skattereduktion; the deduction is a fordran on Skatteverket carried on 1513 by the payment voucher (Dr 1930 customer share / Dr 1513 deduction / Cr 30xx / Cr 26xx in full), and every settlement path records the customer share as the payment row amount. A fully paid ROT/RUT invoice therefore showed exactly deduction_total as outstanding, and the year-end cut-off booked a phantom Dr 1510 / Cr 30xx / Cr 2618 on top of a sale whose revenue and moms were already fully recognised: revenue overstated and vilande moms invented. Fix: the customer's outstanding is total - deduction_total - paid (the deduction follows the sign of the total, since credit notes store it as a positive magnitude), floored at zero on the invoice's own side for over-collection noise, mirroring the invoices_remaining_amount_guard formula. The moms carried into the cut-off is scaled by the customer share, not the gross total, so an unpaid ROT/RUT invoice reports its whole moms in the final period and a part-paid one the matching fraction. Invoices without a deduction take the unchanged gross path. The readiness gate, the pending-operation executor and the MCP tool all consume this collector, so they inherit the fix. The Skatteverket share itself (an unpaid ROT/RUT invoice's 1513 fordran at year end) is not part of the cut-off and stays a separate change, as is the 1513 point already deferred on the currency revaluation. Fixes #2248 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015qgLgdt4mLmha1ZLFMwq1u * refactor(invoices): one definition of the customer share of an invoice The customer share of an invoice (total minus the ROT/RUT deduction, and what remains of it after payments) was re-derived by hand at three TypeScript sites plus the SQL guard invoices_remaining_amount_guard, and the kontantmetod cut-off's copy had drifted to the gross total (#2248). Fixing the cut-off's arithmetic alone would leave the next copy free to drift the same way. lib/invoices/customer-share.ts now holds the definition: invoiceCustomerShare(invoice) returns total minus sign(total) times deduction_total (the deduction is stored as a positive magnitude, also on credit notes), and invoiceCustomerOutstanding(invoice, paid) returns the signed residual after payments. The module comment names the SQL twin (migration 20260817191708) so the two stay in lockstep; the SQL is untouched. Call sites moved onto the helper with byte-identical behaviour: kontantmetod-cutoff.ts (keeps its as-of payment sum, the sign-aware zero floor for ROT/RUT rows and the moms scaling), rot-rut-file.ts (the customer-share-paid test) and payment-sync.ts (the storno path, which still floors with Math.max(0, ...) because it persists the column). Plain invoices get their total back exactly as stored, so their paths do not change by a bit. New unit tests cover plain, ROT/RUT, credit-note sign, null deduction and the lockstep with the guard's formula. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015qgLgdt4mLmha1ZLFMwq1u --------- Co-authored-by: Jakob Wennberg <311770904+jakobwennberg-oss@users.noreply.github.com> Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>