fix(reports): count reversed entries in bank/supplier/AR reconciliation (#734)

A reversed (storno-corrected) journal entry must be summed together with
its storno + correction, exactly as the trial balance and balance sheet
already do (.in('status', ['posted','reversed'])). The reconciliation
paths used posted-only and manufactured phantom differences.

- bank-reconciliation getReconciliationStatus: gl_1930_balance now counts
  posted+reversed (so it equals the balansräkning for the account). Dropped
  the correction_adjustment subtraction and the reversed-linked-tx drop — a
  corrected/amount-corrected bank receipt now reconciles. The prior model
  broke once correctEntry began re-pointing the bank transaction to the live
  correction (the two changes were mutually inconsistent and produced a
  difference equal to the corrected amount).
- supplier-reconciliation (2440) and ar-reconciliation (1510/1513): same
  posted -> posted+reversed fix. Removes the false "Ej avstämd" gap a fully
  paid, fully corrected company shows — the books net to 0 over posted+reversed
  while a posted-only query double-counted the payment legs.

Also in this change:
- MCP gnubok_get_reconciliation_status: add account_number param (was hardcoded
  to 1930; the lib already supported per-account reconciliation).
- counterparty-templates normalizeCounterpartyName: strip trailing month /
  personal-initials tokens so "ngrok JW" / "Ngrok Mars" learn as one merchant.
- pending-operation reject 409 (route + MCP tool): clarify a resolved op was
  approved explicitly (no auto-commit path exists) instead of a bare
  "already committed".
- create_voucher: normalize description with String().trim() for consistency
  with line_description.
- migration 20260625120000: backfill stripped diacritics on ~830 companies'
  seeded chart-of-accounts names (Foretagskonto -> Företagskonto, etc.); the
  seed function was fixed for new companies in 20260516130000 but never
  backfilled. UPDATE-only and idempotent. Already applied to production.

Tests: 899 passing across the touched suites; lint + typecheck clean.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Jakob Wennberg
2026-06-15 20:46:41 +02:00
committed by GitHub
co-authored by Claude Opus 4.8
parent fa4daf6f98
commit e5f1d7a916
12 changed files with 445 additions and 109 deletions
@@ -0,0 +1,56 @@
-- Backfill: restore Swedish diacritics (å/ä/ö) on seeded chart-of-accounts names.
--
-- The seed_chart_of_accounts() helper shipped between 2026-03-30
-- (20260330130000_multi_tenant_company_refactor.sql) and 2026-05-16 with its
-- account-name string literals stripped of å/ä/ö — e.g. 'Foretagskonto /
-- checkkonto', 'Leverantorsskulder', 'Arets resultat', 'Loner'. The function was
-- repaired for NEW companies by 20260516130000_seed_chart_of_accounts_restore_
-- swedish_chars.sql, but the ~830 companies seeded in that window were never
-- backfilled, so their books still render bank/expense/VAT accounts without
-- diacritics (the reported "Foretagskonto / checkkonto" / "Ovriga bankkonton").
--
-- This restores the diacritics on the AFFECTED rows only. It maps each corrupted
-- name back to the diacritic form OF THAT SAME ACCOUNT (same account_number and
-- same wording) — it deliberately does NOT adopt the later seed's restructured
-- VAT accounts (2610/2611/2612 stay as-is with their names fixed; they are not
-- renumbered to 2611/2621/2631 — that is a structural change, out of scope for a
-- charset repair).
--
-- Safe + idempotent: the join matches on account_number AND the exact corrupted
-- account_name, so a company that renamed an account, or a row already carrying
-- the correct name, is left untouched. Re-running is a no-op.
UPDATE public.chart_of_accounts AS coa
SET account_name = fix.correct_name
FROM (
VALUES
('1930', 'Foretagskonto / checkkonto', 'Företagskonto / checkkonto'),
('1940', 'Ovriga bankkonton', 'Övriga bankkonton'),
('2013', 'Ovriga egna uttag', 'Övriga egna uttag'),
('2018', 'Ovriga egna insattningar', 'Övriga egna insättningar'),
('2099', 'Arets resultat', 'Årets resultat'),
('2440', 'Leverantorsskulder', 'Leverantörsskulder'),
('2610', 'Utgaende moms 25%', 'Utgående moms 25%'),
('2611', 'Utgaende moms 12%', 'Utgående moms 12%'),
('2612', 'Utgaende moms 6%', 'Utgående moms 6%'),
('2641', 'Debiterad ingaende moms', 'Debiterad ingående moms'),
('2650', 'Redovisningskonto for moms', 'Redovisningskonto för moms'),
('2731', 'Avrakning socialavgifter', 'Avräkning socialavgifter'),
('2893', 'Skuld till aktieagare', 'Skuld till aktieägare'),
('3001', 'Forsaljning tjanster 25%', 'Försäljning tjänster 25%'),
('3002', 'Forsaljning varor 25%', 'Försäljning varor 25%'),
('3100', 'Momsfri forsaljning', 'Momsfri försäljning'),
('3900', 'Ovriga rorelseintakter', 'Övriga rörelseintäkter'),
('4000', 'Varuinkop', 'Varuinköp'),
('5410', 'Forbrukningsinventarier', 'Förbrukningsinventarier'),
('5460', 'Forbrukningsmaterial', 'Förbrukningsmaterial'),
('6530', 'Redovisningstjanster', 'Redovisningstjänster'),
('6991', 'Ovriga avdragsgilla kostnader', 'Övriga avdragsgilla kostnader'),
('7010', 'Loner', 'Löner'),
('7210', 'Semesterloner', 'Semesterlöner'),
('7960', 'Valutakursforluster', 'Valutakursförluster'),
('8310', 'Ranteintakter', 'Ränteintäkter'),
('8410', 'Rantekostnader', 'Räntekostnader')
) AS fix(account_number, corrupted_name, correct_name)
WHERE coa.account_number = fix.account_number
AND coa.account_name = fix.corrupted_name;