One bank row can now settle several vouchers: journal_entry_id stays NULL and one transaction_voucher_links row per voucher carries a signed allocated_amount slice (sum must equal the row within the link tolerance, each slice bounded by the voucher's net line on the account). linkTransactionToVouchers does the locked transaction UPDATE first and rolls back on a failed junction insert; unlink and the re-booking guards understand junction-only rows; a storno of one of the N vouchers releases the row when the remaining slices no longer sum to its amount. The worksheet's right pane becomes multi-select when exactly one bank row is picked (Koppla only at difference 0); the v1/dashboard pair schemas accept allocations; the MCP reconcile resolver and executor carry 1:N pairs; skattekonto keeps single-pointer semantics. Closes#1553
* fix(transactions): allow re-linking a bank tx stranded on a reversed verifikat (#988)
A transaction whose journal_entry_id points at a reversed/cancelled entry reads
as "utan koppling" in the UI (the transactions page enriches only status='posted'
links), yet the re-booking guards treated ANY non-null pointer as "already
linked". So a storno'd/corrected transaction could never be linked to another
verifikat or re-categorized: the exact symptom in issue #988.
Add a shared hasLiveJournalEntryLink() predicate used by every re-booking guard
(linkTransactionToJournalEntry, manualLink, categorize-core, and the MCP link
stage-check): a pointer at a non-posted entry is treated as re-linkable, and the
two optimistic-locked writes now lock on the exact previous pointer (null OR the
stale id) instead of always .is(null), so the overwrite goes through race-safely.
hasLiveJournalEntryLink fails closed on a read error so a transient blip can't
detach a genuinely live link.
The source was fixed in #726 (reverseEntry/correctEntry now detach/re-point the
tx); this makes the guards self-heal for the pre-#726 backlog and any future
best-effort miss.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(transactions): detect 0-row CAS before invoice effects; fix categorize commit test
Addresses PR review (CodeRabbit Critical + CI):
- link-journal-entry.ts: the tx UPDATE now .select('id') and treats a 0-row
result as LINK_TX_TX_ALREADY_LINKED, failing BEFORE any invoice settlement /
invoice_payments insert. Without this, a concurrent re-link that lost the CAS
would still mark the invoice paid against a transaction we didn't link (same
optimistic-lock contract manualLink already enforces).
- pending-operations commit route test: the categorize_transaction "already
categorized" case now enqueues the hasLiveJournalEntryLink status read (posted
= live) so it still returns 409. This was the core-only CI failure: the new
liveness read in categorize-core consumed a queued response.
- Updated the link happy-path / invoice-race test enqueues to return a row for
the now-selecting tx UPDATE.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>