fix(v1): thread resolved settlement account through FX and cash-method supplier-payment branches (#1033)

* fix(v1): thread resolved settlement account through FX and cash-method supplier-payment branches

The v1 (MCP-facing) match-supplier-invoice route resolved paymentAccount
via resolveSettlementAccount but only passed it to
createSupplierInvoicePaymentEntry for pure-SEK matches (gated on
isPureSek) and never to createSupplierInvoiceCashEntry at all. A
foreign-currency match, or a kontantmetoden match, settling from a
bank/cash account other than the primary 1930 (e.g. a EUR account on
1940) was still misbooked to 1930: the same class of bug PR #985/#986
fixed for the pure-SEK accrual path.

Pass the resolved account through both branches unconditionally (the
generators' internal 1930 default remains the documented no-link
fallback, reached via resolveSettlementAccount's own fallback for
transactions without a cash_account_id), and widen the
findUnresolvableAccounts chart pre-validation from the pure-SEK accrual
path to every non-customLines branch, since all of them now consume the
resolved account.

The dashboard route needed no code change: its FX/cash-method branches
were already threaded inside PR #985 itself. Added branch-level
regression tests on both routes (linked non-1930 account books to that
account; no cash_account_id falls back to 1930; deactivated resolved
account rejects with ACCOUNTS_NOT_IN_CHART before booking).

Fixes #1000

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(v1): validate settlement account against the chart before the conflicting-JE storno

The widened findUnresolvableAccounts pre-validation ran after the
conflicting-categorization storno, so a request rejected with
ACCOUNTS_NOT_IN_CHART could first reverse the transaction's posted
categorization entry: an irreversible side effect on a failed request.
Move the paymentAccount resolution and the chart validation ahead of
the storno block (same !customLines guard, same error shape) and add a
regression test asserting reverseEntry is never called when the chart
validation fails. The dashboard route has no storno block and no chart
pre-validation on this path, so it is unaffected.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Jakob Wennberg
2026-07-16 18:13:09 +02:00
committed by GitHub
co-authored by Claude Fable 5
parent 1dc85736d8
commit f0907da7e9
4 changed files with 315 additions and 50 deletions
+1
View File
@@ -164,6 +164,7 @@ One line per decision: `[YYYY-MM-DD] <decision>: <why>`. Appended by agents and
[2026-07-15] Superseded the hard deletion part of the 2026-07-14 credit note draft decision: numbered credit note drafts are now retained as cancelled rows and reopened on retry so the KR series remains complete.
[2026-07-15] Customer personnummer uses application field encryption with masked API and UI output rather than a database-only cipher: the existing key custody and AES-256-GCM implementation can protect values before they reach Postgres, while ordinary reads never expose the full identifier.
[2026-07-15] Credit note creation uses a completion marker plus unique company guards instead of a large creation RPC: incomplete parents are never returned, concurrent requests converge, and all journal writes remain in the bookkeeping engine.
[2026-07-16] Closed #1000 (v1 match-supplier-invoice FX + cash-method settlement account): threaded the already-resolved paymentAccount into createSupplierInvoicePaymentEntry (was gated on isPureSek) and createSupplierInvoiceCashEntry (was passed undefined), and widened the findUnresolvableAccounts chart pre-validation from the pure-SEK accrual path to every !customLines branch, since every non-customLines branch now consumes the resolved account. The dashboard route needed no code change (its FX/cash branches were already threaded inside PR #985 itself); added branch-level regression tests on both routes instead. The entry generators keep their internal 1930 default: it is the documented no-link fallback, reached via resolveSettlementAccount returning 1930 for transactions without a cash_account_id.
[2026-07-16] Two bugs from one customer report (an AB). Bug 1 (acct 2893 showed 2393's "langfristig del" memo after an andringsverifikation): root cause = CorrectionEntryDialog never re-derived line_description on account change (JournalEntryForm does). Fixed forward via a pure helper (correction-line-description.ts) that refreshes the memo only when it is empty or still equals the prev account's name (preserves hand-typed memos). Chose NO prod data repair: the wrong memo sits on a POSTED verifikat (immutable per migration-017 trigger); it is cosmetic (account number + amounts correct, all reports key off the number); ~26 posted lines across 11 cos share this stale-echo pattern, all fix-forward only. Deferred the twin entry-level header fix (#1031). Bug 2 (auto tax-deadlines never appeared): root cause = generation only fired on a settings save where a TAX field CHANGED value (didTaxFieldsChange); settings are filled once at onboarding so re-saving generated nothing -> only 5/776 real cos had system deadlines. Chose count-based self-heal (regenerate when the company has 0 system deadlines) over always-regenerate, because generateTaxDeadlinesForUser deletes+reinserts and would reset is_completed/status on every unrelated save. Also wired the /deadlines empty-state to the existing (dead) /api/tax-deadlines/generate route, and fixed a 1000-row PostgREST cap in the annual cron. Backfilled 771 real cos with zero system deadlines via scripts/backfill-tax-deadlines.ts. Deferred moms_period=yearly config (#1030, 295 filers, largest VAT cohort): helarsmoms deadline (SFL 26 kap. 33-33b) depends on EU-trade status (no flag in CompanySettingsForDeadlines) and, for AB, the income-tax-return date.
[2026-07-15] Repaired the single legacy paid credit note blocking invoices_credit_note_not_paid validation by normalizing its invoice metadata to sent, clearing payment fields, setting zero payable remainder, and linking its existing balanced posted V44 reversal: the immutable voucher already exactly reversed V42 and was not edited or duplicated.