15a1c96292
commitMatchTransactionInvoice fed the raw bank amount into planInvoicePayment with no currency conversion and no absorbOreRounding, both of which the dashboard and v1 routes do. Consequences: an exact whole-krona settlement of an ore-carrying invoice was rejected as MATCH_AMOUNT_EXCEEDS_REMAINING, and a cross-currency match would have recorded SEK figures in invoice-currency columns. Parity changes, mirroring the v1 route block: - FX resolution: refuse foreign rows with no SEK value (MATCH_INVOICE_TX_FX_RATE_MISSING), convert via the Riksbanken spot rate on the payment date, refuse when no rate exists (MATCH_INVOICE_FX_RATE_UNAVAILABLE). - planInvoicePayment gets absorbOreRounding on pure-SEK settlements. - The accrual clearing entry is now built by the shared buildInvoicePaymentClearingLines helper (same as dashboard/v1), so the 3740 oresavrundning line and 3960/7960 FX-diff lines exist and 1510 is credited at the invoice's booking rate. Dimension re-propagation included. Failure semantics preserved (no fiscal period still soft-fails like the old builder). - invoice_payments.exchange_rate records the payment-date rate on cross-currency matches. Reported via gnubok_feedback 2026-07-24 (codex/hermes: 14 875 SEK against exactly 14 875 outstanding rejected as exceeding remaining). Co-authored-by: Jakob Wennberg <311770904+jakobwennberg-oss@users.noreply.github.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com>