fix(scoping): Skatteverket per företag + nåbara startkort + företags-scopade val (#1610)

* fix(scoping): skatteverket per company + true pristine gates + scoped dismissals

Skatteverket connections become per (user, company): the token table
carried BOTH UNIQUE(user_id) and UNIQUE(company_id) (two stacked half
migrations), so one connection leaked "connected" onto every company the
user belongs to, sync ran the token against the wrong orgnr (behorighet
403), and reconnecting from another company silently moved the row and
went dark on the first company's crons. Token reads/writes are now scoped
by company through the whole chain (token-store, api-client refresh
coalescing, skvRequest and its 21 call sites, resolve-auth, crons, MCP),
/skattekonto/saldo answers 401 NOT_CONNECTED for companies without their
own row (which is what the page's startkort keys on), and the dashboard
connect-nudge counts only the active company's row.

Bookkeeping's pristine start card now keys on all-years emptiness via a
count probe instead of "no active filters": the default fiscal-year
selection counted as a filter, which made the card unreachable on
brand-new companies (it showed "inga traffar" instead).

Two browser-global localStorage keys become company-scoped with legacy
fallbacks: the inbox onboarding dismissal (dismissing on one company hid
the card everywhere) and the periodisering auto-detect toggle.

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

* fix(scoping): dedupe cron work per (user, company) + guard the ledger probe

CodeRabbit findings on #1610: the skattekonto sync cron still deduped
token rows by user_id alone, which would drop every company but one for
multi-company operators (the exact scenario the PR fixes); and the
all-years ledger probe could leave a stale false behind on a failed
refetch, letting the pristine card render unconfirmed. The probe now
resets to unknown in flight and carries the fetch generation guard.

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

---------

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-14 08:21:57 +02:00
committed by GitHub
co-authored by Claude Fable 5 Jakob Wennberg
parent 4e14182a00
commit 18c20e68e6
28 changed files with 317 additions and 145 deletions
+2
View File
@@ -976,3 +976,5 @@ One line per decision: `[YYYY-MM-DD] <decision>: <why>`. Appended by agents and
[2026-08-13] Renaming a migration after the Supabase preview applied it orphans the PREVIEW tracker, not prod: the preview branch for PR 1605 had recorded 20260813180000, the rename to 20260813213000 removed that file, and the next Migrations task aborted with "Remote migration versions not found in local migrations directory". Repair = delete the orphaned version row from supabase_migrations.schema_migrations on the preview branch project only (prod never applied it, so merge-time apply is unaffected), then re-run tasks with a fresh commit. The migration itself is drop-if-exists idempotent so the replay under the new version is safe.
[2026-08-14] PR #1608 round-2 compliance finding (VMB class-3/4 VAT hole): account_override now books GROSS with no auto-VAT line unless the caller stated explicit VAT intent (vat_treatment or vat_amount). Deliberate divergence from v1 REST, which keeps the category-default standard_25 on overrides: MCP callers are agents, and a forgotten flag must under-deduct (lawful), never fabricate an ingående-moms deduction on a margin-scheme account. The review's class-appropriateness suggestion (block e.g. expense onto 27xx) was noted but not implemented: the approval gate is the guard, and a class matrix would block legitimate balance-sheet bookings v1 REST supports today.
[2026-08-14] Skatteverket connections are per (user, company): the stacked UNIQUE(user_id) + UNIQUE(company_id) pair is replaced by UNIQUE(user_id, company_id), every token read/write is company-scoped, and /skattekonto/saldo returns 401 NOT_CONNECTED when the active company has no row. A multi-company operator connects each company separately; legacy NULL-company rows are ignored by scoped reads and just require a reconnect.
[2026-08-14] Pristine empty-state gates key on all-years emptiness (a count probe), never on the scoped list result: a default fiscal-year selection is a scope, not a filter, and it made the bookkeeping start card unreachable on brand-new companies.