* refactor(transactions): extract shared settlement-account resolution helper
Dedupe the identical cash_account_id -> ledger_account lookup across
match-supplier-invoice (POST + preview) and categorize into
resolveSettlementAccount, per CodeRabbit's nitpick on PR #985. Pure
extraction, no behavior change.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Signed-off-by: Jonas Flodén <jonas@floden.nu>
* fix(v1): resolve supplier-payment/categorize settlement account from cash_account_id
Closes the v1/MCP-facing half of the settlement-account gap left open
by PR #985 (which only fixed the dashboard routes):
- match-supplier-invoice: the pure-SEK accrual path always called
createSupplierInvoicePaymentEntry with no paymentAccount at all
(hardcoded internal default 1930), never reading the transaction's
cash_account_id. Now resolves it via resolveSettlementAccount, same
as the dashboard route post-#985.
- categorize: never called applySettlementAccount after building the
mapping result, so every categorization booked the bank leg to 1930
regardless of which cash account the transaction was linked to.
Left the FX/foreign-currency branch (createSupplierInvoicePaymentEntry)
and the cash-method branch (createSupplierInvoiceCashEntry) on their
pre-existing internal 1930 default, matching #985's own scope decision
on the equivalent dashboard route.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Signed-off-by: Jonas Flodén <jonas@floden.nu>
* fix(bookkeeping): abort instead of silently defaulting to 1930 when settlement-account lookup errors
Same shared-helper fix as PR #985: resolveSettlementAccount now throws
BookkeepingDatabaseError on a genuine cash_accounts query error instead
of warning and falling back to 1930. An explicit cash_account_id almost
certainly resolves to a non-1930 account, so a transient failure masking
it risked the same class of misbooking this whole PR series exists to
fix, just via infra flakiness instead of a stale setting.
No route changes needed: both v1 call sites (match-supplier-invoice,
categorize) already run under withApiV1, whose existing catch-all
converts any isBookkeepingError() throw into the correct structured 500.
Added regression tests confirming the abort for both.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Signed-off-by: Jonas Flodén <jonas@floden.nu>
* fix(v1): guard resolved settlement account against chart of accounts
CodeRabbit and jakobwennberg's triage on #986 both flagged that
resolveSettlementAccount() returns cash_accounts.ledger_account
unvalidated, so an inactive/removed account surfaced as the generic
MATCH_SI_RECORD_PAYMENT_FAILED instead of an actionable error. Add the
same findUnresolvableAccounts pre-check and AccountsNotInChartError
race-guard the categorize routes already use.
Signed-off-by: Jonas Flodén <jonas@floden.nu>
* test(bookkeeping): align settlement-account error assertion with #985
Use .rejects.toBeInstanceOf(BookkeepingDatabaseError) instead of
toMatchObject({ constructor: ... }), matching #985's edef79d follow-up
(the assertion was correct either way, but this is the more idiomatic
check and now makes the shared helper's test file byte-identical
across #985/#986/#987, removing the add/add merge conflict between
them noted in the merge-order validation.
Signed-off-by: Jonas Flodén
Signed-off-by: Jonas Flodén <jonas@floden.nu>
---------
Signed-off-by: Jonas Flodén <jonas@floden.nu>
Co-authored-by: Jakob Wennberg <jakob.wennberg@gmail.com>