feat(reconciliation): residual booking + junction-aware bridge (#1862)

When the worksheet selection (N bank rows vs one verifikat) misses by a
few kronor, 'Bokför mellanskillnaden som Bankavgift / Räntekostnad /
Ränteintäkt / Öresavrundning och koppla' books the remainder on
6570 / 8410 / 8310 / 3740 against the bank account, links the rows to the
main verifikat and anchors the residual verifikat through
transaction_voucher_links. Bank accounts only (Skatteverket posts ränta
and avgifter as rows of their own), capped at 5 000 kr, direction-checked
against the kind; links are made first and undone if the booking is
refused. Dashboard + v1 doors (transactions:write, Idempotency-Key,
dry run), API skill regenerated.

The bridge now treats transaction_voucher_links as links on both sides:
migration 20260824190000 re-creates get_unlinked_gl_lines and
get_account_gl_lines_for_matching to count junction-linked verifikat as
matched (pg-real test), and the TS engine + items do the same for the
transactions. This also stops bulk-booked samlingsverifikat from
polluting the open buckets. 'Koppla bort' drops the junction rows too.

Co-authored-by: Jakob Wennberg <311770904+jakobwennberg-oss@users.noreply.github.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Jakob Wennberg
2026-08-25 08:30:54 +02:00
committed by GitHub
co-authored by Jakob Wennberg Claude Fable 5
parent 0a3cb1a31a
commit d88df74b85
21 changed files with 1496 additions and 15 deletions
@@ -1,6 +1,6 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`v1 spec snapshot > matches the recorded endpoint count > endpoint-count 1`] = `134`;
exports[`v1 spec snapshot > matches the recorded endpoint count > endpoint-count 1`] = `135`;
exports[`v1 spec snapshot > matches the recorded endpoint key set > endpoint-keys 1`] = `
[
@@ -106,6 +106,7 @@ exports[`v1 spec snapshot > matches the recorded endpoint key set > endpoint-key
"POST /api/v1/companies/:companyId/journal-entries/batch-create",
"POST /api/v1/companies/:companyId/reconciliation/accounts/:accountKey/items/:itemId/ignore",
"POST /api/v1/companies/:companyId/reconciliation/accounts/:accountKey/links",
"POST /api/v1/companies/:companyId/reconciliation/accounts/:accountKey/residual",
"POST /api/v1/companies/:companyId/reconciliation/accounts/:accountKey/signoff",
"POST /api/v1/companies/:companyId/reconciliation/accounts/:accountKey/signoff/:signoffId/reopen",
"POST /api/v1/companies/:companyId/reconciliation/bank/run",
+1
View File
@@ -162,6 +162,7 @@ import '@/app/api/v1/companies/[companyId]/reconciliation/accounts/[accountKey]/
import '@/app/api/v1/companies/[companyId]/reconciliation/accounts/[accountKey]/items/[itemId]/ignore/route'
import '@/app/api/v1/companies/[companyId]/reconciliation/accounts/[accountKey]/signoff/route'
import '@/app/api/v1/companies/[companyId]/reconciliation/accounts/[accountKey]/signoff/[signoffId]/reopen/route'
import '@/app/api/v1/companies/[companyId]/reconciliation/accounts/[accountKey]/residual/route'
// Dimensions PR2: registry list + value creation (kostnadsställe/projekt).
import '@/app/api/v1/companies/[companyId]/dimensions/route'