fix(vat): keep the momsrapport intact after a manual nollställning (#990)

* fix(vat): keep the momsrapport intact after a manual nollställning (#984)

The momsrapport already excludes settlement verifikat tagged with
source_type 'vat_settlement' (#983), but settlements booked any other
way still zeroed every ruta the moment they were posted: manual
momsomföringar booked before the tagged flow existed (the report in
issue #984), SIE-imported settlements, and storno reversals of a
settlement, which inverted the sign instead and silently doubled the
rutor after an annullera.

Exclude settlement entries by SHAPE as well: an entry with at least one
line on a declaration account (ACCOUNT_RUTA) and at least one on a
settlement net account (2650/1650) is bookkeeping about the declaration,
not VAT-bearing activity, in both the web projection
(fetchVatAccountTotals) and the MCP twin (computeVatReport).
Opening-balance entries are exempt: carried-in 26xx balances are
unsettled VAT that belongs in the next declaration.

Shape-detected POSTED settlements now also gate the "Skapa verifikat"
button through existing_entries, since the proposal re-clears the full
period and booking it on top of a manual settlement would corrupt the
26xx balances. Stornos never gate, so annullera still re-enables
booking.

Fixes #984

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

* fix(mcp): stable id order for the paginated VAT-report line fetch (CodeRabbit)

fetchAllRows pages with .range(); without a unique .order() rows can
shift across page boundaries once a period exceeds 1000 lines, skipping
or double-counting journal lines in the rutor. Same discipline as the
web projection (fetch-all.ts). Pre-existing, but the query was already
being touched for #984.

Also documents the shape-rule triage from the compliance-bot review in
DECISIONS.md: compound business-VAT-plus-2650 verifikat stay a known
accepted residual (a direction guard would break the storno exclusion),
and the opening-balance concern is false for app flows (SIE import and
set_opening_balances both tag source_type 'opening_balance').

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-11 21:37:30 +02:00
committed by GitHub
co-authored by Claude Fable 5
parent 2774e01258
commit 9d91ee0168
7 changed files with 404 additions and 29 deletions
+2
View File
@@ -60,3 +60,5 @@ One line per decision: `[YYYY-MM-DD] <decision>: <why>`. Appended by agents and
[2026-07-09] SalaryCalendar absence-type rainbow palette (red/amber/emerald/blue/indigo pills) left as-is in the UI consistency pass: those colors encode absence categories (data), not status chrome, and swapping them for the 3 semantic tokens would collapse 5 distinguishable categories; needs a proper categorical-palette decision instead of a mechanical fix.
[2026-07-10] Momsverifikat from momsrapport (#980): the proposal clears each 26xx account at exact öre but books the 2650/1650 net at the FILED whole-krona amount (buildFiledAmounts, öretal faller bort) with the gap on 3740, so redovisningskontot always matches the skattekonto movement; and vat_settlement entries are excluded from the VAT report projection (web calculateVatDeclaration + MCP computeVatReport) because a pure-projection report would otherwise read zero (and Skatteverket submission would file zeros) the moment the settlement is booked.
[2026-07-10] VatBookingCard hard-disables "Skapa verifikat" while a POSTED vat_settlement exists in the period (CodeRabbit finding, accepted over the initial warn-but-allow): the proposal is not delta-aware (it re-clears the FULL period), so booking twice corrupts 26xx balances; the sanctioned redo path is annullera (storno restores the balances and re-enables the button). Already-booked detection is by source_type + entry_date within the period, so redating the entry outside the period escapes the gate: accepted v1 limitation. Card copy is hardcoded Swedish per the file's existing momsdeklaration convention (i18n.md).
[2026-07-11] Momsrapport after settlement (#984): extended the VAT-report exclusion from tag-only to shape-based. Any entry touching both a declaration account (ACCOUNT_RUTA) and a settlement net account (2650/1650) is treated as a momsredovisning and excluded from the projection (web calculateVatDeclaration + MCP computeVatReport), covering manual momsomforingar booked before #980 shipped, SIE-imported settlements, and stornos of a settlement (which would otherwise double the rutor after annullera, a latent bug in the #983 tag-only filter). Opening-balance entries are exempt from the shape rule: carried-in 26xx balances are unsettled VAT that belongs in the next declaration. Shaped POSTED entries also gate the "Skapa verifikat" button via existing_entries (the proposal re-clears the full period, so booking over a manual settlement would corrupt 26xx); stornos never gate, or annullera could not re-enable booking. Rejected the frozen-snapshot alternative the issue suggested: pure projection heals historical periods retroactively (a snapshot would not exist for them) and needs no migration.
[2026-07-11] #984 shape-rule residuals triaged and ACCEPTED (compliance-bot review): a compound verifikat mixing business VAT lines with a 2650/1650 payment/correction line in ONE entry is excluded from the rutor by the shape rule (under-reports). Kept anyway: such compound entries are rare bad practice, and the suggested direction guard (only exclude when 2650 is credited / 1650 debited) would break the storno exclusion, whose reversal carries exactly the flipped sides. Opening-balance concern verified false for app flows: SIE import and set_opening_balances both tag source_type 'opening_balance' (sie-import.ts); only a hand-booked IB verifikat shares the compound-entry residual.