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>