fix(periodisering): stop overselling automatic periodization to enskild firma (#1730)

* fix(bokslut): honest periodisering for enskild firma (K1)

Stop mis-selling automatic periodisering to sole traders and give the
auto-detect a materiality floor:

- Remove the inert PeriodiseringAutoDetectToggle (write-only localStorage,
  no reader anywhere); the settings row is now a plain link to the
  periodisering wizard, with new i18n keys in sv+en.
- Auto-detect tags suggestions under 5 000 kr as low confidence with the
  reason 'Under 5 000 kr: behöver normalt inte periodiseras', citing K1
  (BFNAR 2006:1) for enskild firma and K2 for aktiebolag; the wizard only
  pre-ticks high-confidence rows, so under-floor posts land unticked.
  Personnel-cost lines (7xxx) are exempt: they must always be accrued.
- The accruals GET route resolves companies.entity_type and threads it to
  the detector.
- Per-line accrual hint in the invoice editors is entity-aware: new
  accruals.k1_hint (K1, förenklat årsbokslut) for EF, k2_hint stays for AB.
- Periodisering wizard and year-end AccrualsStep relabel Revisionsarvode
  to Bokslutsarvode for EF, default the liability account to 2991 instead
  of 2992, and show a muted K1-floor intro line.

All copy stays advisory (behöver normalt inte, never får inte):
entity_type is a proxy since no förenklat-vs-full-årsbokslut flag exists.

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

* fix(bokslut): SEK-correct materiality floor, entity-type via settings, narrower personnel exemption

Review fixes on the K1 periodisering branch:

- The 5 000 kr floor now compares a SEK amount: queries select currency
  and subtotal_sek, the floor uses the periodisation share of
  subtotal_sek for foreign-currency invoices, and is skipped entirely
  when no SEK amount is resolvable (accrual-k2-hint precedent,
  DECISIONS.md 2026-07-26).
- The accruals route resolves entity type via getCompanyEntityType
  (company_settings-primary, companies fallback) instead of reading
  companies.entity_type directly.
- The personnel-cost exemption from the floor is narrowed from
  startsWith('7') to /^7[0-6]/: 78xx/79xx are not personnel costs.

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-20 10:06:01 +02:00
committed by GitHub
co-authored by Claude Fable 5 Jakob Wennberg
parent c402421908
commit 64fc7c783d
16 changed files with 622 additions and 179 deletions
+2
View File
@@ -1087,3 +1087,5 @@ One line per decision: `[YYYY-MM-DD] <decision>: <why>`. Appended by agents and
[2026-08-19] Import mapping step gets a bulk "Bekräfta alla föreslagna" for the VAT-treatment review gate, batching the per-row confirm semantics unchanged (defaults kept, rows marked reviewed): a Fortnox chart routinely puts 70+ class 3/4 accounts behind the gate and the one-click-per-row flow across 50-row pages was an observed live migration dead end (Boltonshield 2026-08-18, stuck at "50 kvar"). Rejected: auto-skipping review for accounts unused by the imported vouchers, because the chart rows are still created with the suggested treatment and a silently wrong default on a soon-used account is exactly what the review gate exists to catch.
[2026-08-19] Dialog overflow hardening: Dialog/Sheet titles and descriptions get break-words at the primitive; AccountCombobox's non-flat dropdown is portaled to document.body with viewport-clamped geometry (same rationale as info-tooltip's TooltipContent portal, since DialogContent's overflow-y-auto otherwise grows a horizontal scrollbar around the 34rem panel); the four rattelse-family dialog explainers are unified behind one RattelseExplainer HelpPopover (convention 7, MatchVoucherDialog precedent) instead of four near-duplicate inline paragraphs; a dialog-overflow-risk ratchet in no-new-antipatterns.mjs keeps bare-1fr tracks, dialog whitespace-nowrap and unportaled wide overlays from coming back.
[2026-08-19] Trial-expired visibility: the 2026-07-11 "no trial-expired nag" call is narrowed at the founder's direction (user-reported discoverability failure 2026-08-18): one on-entry dialog with persisted acknowledgement (user_preferences.ui_state.trial_expired_ack, per user AND company, never localStorage) plus a non-dismissable quiet chrome link (SubscriptionTouchpoint replaces the vanishing countdown pill, stays visible when the rail is collapsed, and adds the first mobile touchpoint). Free tier stays a legitimate resting state: no recurring nag, muted chrome tone, no ochre. getCompanyEntitlements also reads company_subscriptions.status inside its existing Promise.all (zero extra round-trips) so churned payers get "abonnemang" copy instead of "provperiod". Hem AttnLine variant skipped: the page already carries the otherAccountHint AttnLine and design.md allows max one per page.
[2026-08-19] Removed PeriodiseringAutoDetectToggle (settings > Automatik) instead of wiring it: the localStorage key it wrote (periodisering_autodetect_enabled) had no reader anywhere, so it advertised "automatisk periodiseringsdetektering" while changing nothing; auto-detect is already best-effort and review-gated in the wizard, so the row is now a plain link to the periodisering wizard. Deleting the key is safe: it was write-only.
[2026-08-19] Periodisering auto-detect materiality floor uses entity_type as a K1 proxy: no stored flag distinguishes förenklat årsbokslut (K1, BFNAR 2006:1) from full årsbokslut (BFNAR 2017:3) for enskild firma, so every EF gets K1 wording and every AB gets K2, always advisory ("behöver normalt inte"), never prohibitive. Suggestions under 5 000 kr are tagged low-confidence (unticked) rather than dropped because the relief is a MAY, not a MUST; personnel-cost lines (7xxx) are exempt from the floor since K1/K2 require personnel costs to always be accrued.