Commit Graph
15 Commits
Author SHA1 Message Date
9ce1ebc65f feat(reports): bokslutsbilagor, the pärm per räkenskapsår (Reko bilagor, PR 4) (#1874)
* feat(reports): bokslutsbilagor, the pärm per räkenskapsår (Reko bilagor, PR 4)

One bilaga per balance account as of the balansdag: IB, movement and UB
from the trial balance, what it was reconciled against, the difference,
the sign-off with who, when and note, and every attached file with its
SHA-256; the closing checklist as the first page. JSON and PDF through
/api/reports/bokslutsbilagor, in the reports library and on the
Avstämning page, and written into every period folder of the full
archive. Built from the attested rows, never by recomputing live status.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RvFveUpbdPBXdm7f5FEYoz

* fix(reports): load the pärm renderer on demand in the full archive so PDF stubs elsewhere keep working

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RvFveUpbdPBXdm7f5FEYoz

* fix(reconciliation): neutral rail dot for a manual account that is merely not attested yet

An unsigned manual account without a system specification has nothing to
compare against, so an amber dot read as a problem on every balance account
of a freshly migrated company. Neutral until it is signed or a
specification differs.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RvFveUpbdPBXdm7f5FEYoz

---------

Co-authored-by: Jakob Wennberg <311770904+jakobwennberg-oss@users.noreply.github.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-08-25 09:35:02 +02:00
9ebb2e518f feat(reconciliation): absorb the last bank-view tools and retire /reports/bank-reconciliation (#1871)
The old Bankavstämning report page was the only place a user could still tag
a bank row as ingående balans or move it to another bank account, so the new
/reconciliation page kept linking out to it and the reconciliation lived in
two places. Both row tools now live on the account overview (hover-revealed,
same endpoints), the ?autorun=1 deep link from the transactions inbox runs the
matcher on the new page, and the report slug redirects: old links, ⌘K, the
bokslut readiness wizard and the ignore toast all land on /reconciliation.

BankReconciliationView and its FocusedReport branches (own range preset,
help popover, autoRun plumbing) are removed; the source-grepping parity test
for its quick-book path goes with it.

Co-authored-by: Jakob Wennberg <311770904+jakobwennberg-oss@users.noreply.github.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-08-25 09:03:17 +02:00
4be51aae67 feat(reports): behandlingshistorik report (BFL 5 kap. 11 §, BFNAR 2013:2 p. 9.16) (#1787)
* feat(reports): behandlingshistorik report (BFL 5 kap. 11 §, BFNAR 2013:2 p. 9.16)

Adds the per-räkenskapsår processing history as a first-class report in
Rapporter (Export & arkiv), with CSV/XLSX export. Until now the
behandlingshistorik only existed as raw audit_log JSON inside the
Säkerhetsbackup ZIP; revisorer ask for a readable per-year document.

- lib/reports/behandlingshistorik.ts: read model over journal_entries
  (committed_at = registreringsdatum, the complete source of bokföringsposter),
  the trigger-written audit_log (storno, deletions, diffs, kontoplan, settings,
  period lock/unlock/close, API keys, dimensions, accruals), the rättelse log,
  company_migration_resets, sie_imports and bank_file_imports. Field-level
  diffs with Swedish labels; company_settings restricted to processing-relevant
  keys (p. 9.16 second paragraph); kontoplan seeding and bulk underlag
  deletions collapse into one summary row; actor labels for users, API keys,
  MCP, agent, cron and system; fiscal-year mode unions audit rows touching the
  year's entries regardless of timestamp (bokslut/storno land after period_end),
  date-range mode narrows by registration time.
- GET /api/reports/behandlingshistorik?period_id&from_date&to_date&category&format
  (json|csv|xlsx), withRouteContext + Zod, e-mail labels via service-role
  profiles lookup scoped to the ids in the result, app version stamped.
- Report catalog row + focused view (category filter, export menu), sv/en.
- Tests: 30 read-model tests, 10 route tests; smoke-tested read-only on prod.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Kw2CFCEt8MxzbJiXMAgMVi

* fix(reports): keep behandlingshistorik queries statically resolvable for the schema guard

tests/schema/no-phantom-columns.test.ts counts `.or()` calls with non-literal
arguments as unresolvable and holds a ceiling (379); the report added two.
The audit_log table/action filter is now a string literal in the call (pinned
to AUDITED_TABLES / GLOBAL_ACTIONS by a unit test), and the migration-reset
lookup is two plain `.eq()` queries instead of an interpolated `.or()`.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Kw2CFCEt8MxzbJiXMAgMVi

---------

Co-authored-by: Jakob Wennberg <311770904+jakobwennberg-oss@users.noreply.github.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-08-21 16:45:39 +02:00
5a8dd21931 feat(reconciliation): page-owned window, automatic matching, and a way out for unbookable rows (#1742)
Second half of the reconciliation redesign, on top of the bridge in #1737.

**Toolbar.** The view hosted its own "Datum från / Datum till" inputs behind a
Filtrera button: a second period control competing with the header's
räkenskapsår picker (convention 8), and the source of a "typed but not applied"
state that needed its own attention line to explain. The window is now owned by
the page, narrowed through the shared ReportDateRange like every other report,
and applied on change. The view holds no date state at all, which also removes
the ref-synchronisation dance and the off-by-one it existed to prevent (a year
switch fetching the previous year's window because the refs updated a commit
late).

Reconciliation opens on the FULL year, not the family default of YTD, and keeps
its own preset memory: a reconciliation runs over a whole räkenskapsår, and
inheriting a "Denna månad" last used on Resultatrapport would show an alarming
difference for a window nobody chose here. ReportDateRange gained defaultPreset
and storageKeyPrefix for that; every existing caller keeps its behaviour.

**Automatic matching.** "Förhandsgranska" told the user nothing about what it
did, and the ochre line above it existed only to point at it: people matched a
whole migration row by row next to a button they never found. The matcher now
runs by itself, once per window+account, whenever there is unmatched work. It is
a dry run, so nothing is written and Tillämpa still requires an explicit click.
The button stays as a re-run and is renamed to what it does. ?autorun=1 keeps a
distinct meaning (run even on a clean window) so the transactions-inbox deep
link still produces a result rather than silence.

**A way out for rows that cannot be paired.** An unmatched bank row that no
voucher on the account could settle is not reconciliation work, it is an unbooked
affärshändelse, and the match picker held nothing for it. Those rows now offer
"Bokför" into /transactions?highlight=<id>, with a bulk link in the section
header. The rule (direction-compatible and equal to the öre) is extracted to
lib/reconciliation/voucher-candidate.ts so it is testable and so the component
never imports the server-only reconciliation module. Deliberately strict: a false
negative offers booking on a row that could also have been paired, which is a
legitimate outcome, while a false positive sends the user into an empty picker.

11 new tests for the candidate rule, covering direction, öre equality, float
noise, PostgREST numeric strings and the foreign-account case where the
candidate RPC projects no FX amount and no match may be claimed.

Co-authored-by: Jakob Wennberg <311770904+jakobwennberg-oss@users.noreply.github.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-20 13:14:31 +02:00
1b829883ae feat(reconciliation): promote bulk matching and bridge it from the inbox (#1571)
* feat(reconciliation): accept confidence_threshold on the bank run route

Mirror the v1 route: RunReconciliationSchema gains an optional
confidence_threshold (0..1) that passes through to runReconciliation as
the server-side floor on the apply path. The UI sends 0.85 with a
strong-only apply so a pair the fresh re-run scores lower is skipped
instead of committed; omitting it keeps the legacy behavior where every
selected pair applies.

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

* feat(reconciliation): promote the bulk match flow and bridge it from the inbox

The dry-run preview with pre-ticked strong matches existed but was never
found: users matched whole migrations row by row. Three discoverability
changes, no engine changes:

- Bankavstamning: an attention line above the toolbar while unmatched
  transactions exist and no preview has run, with Forhandsgranska
  promoted to the filled variant. When every ticked preview pair is a
  strong match (>= 0.85) the apply button relabels to 'Matcha X starka
  traffar' and the apply sends confidence_threshold 0.85; mixed
  selections keep the plain label and omit the floor so manually ticked
  weaker pairs still apply.
- Autorun bridge: ?autorun=1 on /reports/bank-reconciliation runs the
  preview once, only after appliedDates is set and not while datesDirty,
  so it can never cover a different window than the on-screen lists.
- Transactions inbox: with >= 5 unbooked bank rows visible, an attention
  line links to the reconciliation with autorun (static text + count, no
  probe; the preview is the honest source of how many actually match).

The review step stays: autorun lands on the preview table, one click
from apply, and the server intersection guard is untouched.

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>
2026-08-13 15:21:27 +02:00
02a5d10538 refactor(ui): migrate remaining inline pages to the concept design language (#1470)
* refactor(ui): migrate remaining inline pages to the concept design language

Catch-up pass for surfaces the 2026-07 UI migration missed:

- Bankavstämning: de-boxed toolbar, dry-table sections (preview, omatchade
  verifikationer, ignorerade, matchade), instructional copy moved behind the
  page "?" (HelpPopover via FocusedReport, sv+en), AttnLine for the dirty-
  dates hint, EmptyState for the blank page, space-y-8 rhythm.
- Report detail views (trial balance, income statement, balance sheet,
  resultat-/balansrapport, reskontror, huvudbok, grundbok, dimension-P&L):
  shared Skeleton/Error/EmptyState shells, border-2 totals bands flattened
  to hairline cards with font-display tabular-nums headline numbers,
  ReportSectionTable rebuilt on the group-band idiom, font-mono money ->
  tabular-nums, house tablist for Förenklad/Detaljerad, GL filter de-boxed
  onto Input primitives, verdicts follow chips-mark-exceptions.
- Extensions browse: PageHeader, locked section headers, rounded-lg
  secondary icon tiles, flat hover shift on cards, p-6 content.
- Återkommande fakturor: page-level list moved off ui/table onto dry-table
  with hover-revealed quiet row actions; Skeleton loading.
- Help: EmptyState for no search hits, flat hover shift on resource links.
- Chart of accounts: spinner loading blocks -> Skeleton rows.
- Kunskap graph + salary calendar popovers: rounded-lg, Input/Textarea
  primitives instead of hand-rolled shadow-sm controls.

No logic, endpoint, or data changes. Verified via sandbox screenshots;
lint 0 errors, 13214 tests green.

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

* fix(ui): review triage: skip empty industry sectors, keyboard path to schedule edit

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>
2026-08-08 16:40:19 +02:00
Jakob WennbergandClaude Fable 5 4bc2093e51 polish(vat): title-row Exportera, fused period chip, chip classification, calm SKV status (#1181)
Founder feedback on the live momsdeklaration (2026-07-25):

- The black Exportera now sits on the title row like every other page:
  standalone report pages render their own PageHeader (FocusedReport
  passes the title and skips its own), and the period chips get their
  own row below.
- Year + quarter/month fuse into ONE chip ("Kvartal 3 2026") listing
  five years reverse-chronologically with month-span annotations;
  cadence stays behind the Period chip. Yearly keeps FyPicker, which
  is already a fused rakenskapsar chip.
- The RC-basis worklist's Leverantorstyp/Typ av inkop selects (old
  boxy style with labels) become ContextPicker chips, in the toolbar
  and in each expanded row.
- SkatteverketPanel connection status per the locked conventions: the
  contradictory "Ansluten" + "Session utgangen" badge cluster becomes
  muted "Ansluten" text for the normal state and one attn sentence
  with an embedded "Fornya med BankID" action for the expired session.

Verified via sandbox screenshots (Playwright against dev).

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-25 11:59:06 +02:00
Jakob WennbergandClaude Fable 5 98e1a48c2b feat(bokslut): the bokslut trio in the flat concept language (scenes 34-36) (#1161)
* feat(bokslut): Arsbokslut hub as Stegen with de-boxed linear steps (trio 1/3)

Scene 34: the six-step wizard gets the house horizontal stepper (done
checks behind the current step, click-back navigation, forward stays
with each step's continue action), the period select moves into the
header as the context control, and the progress-bar and picker cards
die. Preflight, Preview, Execute and Result are de-boxed: sans eyebrow
sections with hairlines, quiet action links, attn lines instead of
boxed alerts, muted text for normal states. Step content is centered
at reading width. Accruals/Dispositions keep their internals for now
(interactive panels; follow-up pass). All wizard logic untouched.

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

* feat(bokslut): INK2 and NE-bilaga views in the flat house language (trio 2/3)

Scene 36 direction: the declaration views lose every card. Statutory
sections (Tillgangar, Eget kapital och skulder, Resultatrakning, INK2S,
Intakter, Kostnader) become sans eyebrow sections with hairlines, the
header card becomes a flat block with the SRU download beside it, the
filing instructions lose their info box, warnings render as attn lines,
and the NE R11 result becomes the emphasized document-foot row. All
ruta tables, SRU downloads and warnings logic untouched.

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

* feat(bokslut): Arsredovisning page, studio and digital filing in the flat house language (trio 3/3)

De-boxes the whole scene-35 surface: the page shell (period picker,
K3 note, narrativ, flerarsoversikt, underskrifter, PDF section), the
AnnualReportStudio (workflow strip, scope form, completeness checks,
versions) and DigitalInlamning (iXBRL review, submission form,
status history) all move from Card shells to sans-eyebrow sections
with hairlines. Inner info boxes flatten to bordered text blocks;
the digital-checks warning becomes an attn line. The iframe border
and the Kommer snart overlay sign stay: one frames an external
document, the other floats above blurred content.

Statutory Swedish-only surface: no new i18n keys.

* polish(bokslut): align trio controls with the house language

Founder feedback: buttons and selects still read old-style next to the
bookkeeping page. Sweeps all trio surfaces:

- Drop every min-h-11 override on Buttons and Inputs (44px chunky
  controls) so the compact house pill and h-10 input apply.
- Replace all native <select> elements (BooleanQuestion, currency,
  group size, signing method, signer role, versions, AGM outcome,
  roll) with the shadcn Select primitive used system-wide.
- Step navigation matches the merged Moms Stegen: white outline
  size-sm pills labelled 'Nästa: <steg> →' and '← Tillbaka'. Booking
  commits (Verkställ, Bokför valda dispositioner) stay primary.
- Year-end period picker becomes the ContextPicker chip (the house
  context-picker idiom), and the stepper row widens to max-w-4xl so
  step 6 'Klart' no longer clips.
- font-sans on two uppercase h3 eyebrows that rendered serif via the
  global h1-h3 display rule.

* fix(vat): one toolbar row and the FyPicker chip on every fiscal-year surface

The prod momsdeklaration (helårsmoms) broke into two sparse rows:
Exportera alone, then a labelled 280px FiscalYearSelector below it,
plus a serif uppercase worklist heading and internal check codes in
the UI.

- The VAT toolbar is one flat row: periodicity picker, fiscal-year
  chip, black Exportera, all h-9 aligned.
- FyPicker (the rounded ContextPicker chip from UI-migration PR 3)
  replaces FiscalYearSelector on every page-level surface it had
  left: the VAT toolbar, FocusedReport's header (all report detail
  pages) and Kassaflödesanalys. Dialog/settings forms keep the
  labelled select, which is a form field, not a context picker.
- 'Verifikationer som saknar basbelopp' becomes a sans eyebrow with
  hairline; internal codes (RC_BASIS_MISSING et al) no longer render
  in check rows, the Swedish message already cites the SKV felkod.
- font-sans on the SkatteverketPanel validation eyebrow.

* polish(vat): periodicity, year and quarter/month pickers as ContextPicker chips

Founder feedback: the Arsvis dropdown should also be the rounded
style. The cadence choice now lives behind a settings-style 'Period'
chip (Manadsvis/Kvartalsvis/Arsvis with a check on the active one),
and the year and quarter/month selects become chips too, so the
whole momsdeklaration toolbar is chip-shaped: Period, 2026,
Kvartal 3 (jul-sep) or Rakenskapsar 2026, then the black Exportera
pill. The concrete period chip already implies the cadence, so the
Period chip can stay label-only.

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-24 20:20:00 +02:00
Mattsson e11f70b347 Bug/gh issues fiz (#1103)
* refactor: optimize page loading and data fetching

* fix: resolve recurring production runtime errors

* feat: add MCP company and customer updates

* fix: handle year-end tax adjustments

* feat: harden annual report compliance

* fix: expand invoice logo and font support

* fix: sanitize API route error responses

* fix: sanitize user-facing error messages

* feat: persist onboarding and tax assessment notices

* fix: reduce cloud backup audit churn

* feat: refine invoice editor layout

* fix: show saved tax adjustments in INK2

* fix: complete annual report API mappings

* docs: record operational safeguards and decisions

* fix: harden annual report review findings

* fix: adjust column span for description based on VAT registration

* New css class name
2026-07-21 23:00:15 +02:00
MattssonandClaude Fable 5 7d7f604e00 Add/stripe invoice link (#998)
* feat(supplier-invoices): show registered invoices under "Att betala" with inline approve

Registered supplier invoices are already booked as debt (2440) but were
hidden from the "Att betala" tab until approved, which confused users.
The tab now shows registered invoices too, marked "Ej godkand" with a
compact inline approve button. Approval remains the gate for payment,
not visibility; status model and approve API untouched.

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

* feat(reports): add date range filter to huvudbok (kontoanalys)

Mounts the existing ReportDateRange control on /reports/huvudbok so the
ledger can be narrowed to any date range within the fiscal year, matching
Fortnox kontoanalys. Lines before the range roll into each account's
opening balance so running balances stay correct at the range start;
lines after the range are dropped. Applies to the XLSX export too.

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

* feat(invoices): add optional payment link on invoices (paste-link MVP)

The user pastes a payment link created in their PSP dashboard (e.g. a
Stripe Payment Link) onto an invoice. The recipient gets a "Betala
online" button in the invoice email and a QR code + clickable link in
the PDF payment box. No PSP integration server-side: this is the
demand probe; a future Stripe Connect integration would auto-fill the
same column.

- invoices.payment_link_url (migration 20260709090000), https-only +
  2048-char cap enforced in CreateInvoiceSchema; empty string
  normalises to undefined and build-invoice-write always writes a
  concrete value so clearing the field on a draft edit NULLs the column
- editor field (real invoices only) with one-link-per-invoice hint;
  strings in sv+en (messages landed via e0e11066)
- email button (customer.language, hidden for credit notes/proforma/
  delivery notes, URL escaped for the href attribute) + URL in the
  plain-text part
- PDF QR + link row following the Swish QR pattern; wired into send,
  download and preview routes
- derived documents (credit note, proforma convert, recurring) do NOT
  copy the link: it encodes one amount for one specific invoice
- MCP gnubok_create_invoice accepts payment_link_url (validated at
  staging and re-checked in the commit executor); v1 API exposes the
  column; tools/list token ceiling bumped 45K -> 45.5K (ledger entry
  in payload-size.bench.test.ts, headroom was <10 tokens)

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

* fix(invoices): show oresavrundning on editor/form totals, supplier list and invoice email

The rounding logic (getDisplayTotal) was correct but only applied on the
PDF, invoice list/detail and review dialog. The invoice editor summary,
the supplier invoice form totals and the supplier invoice list showed the
raw ore total right next to the toggle, and the invoice email said
"Att betala" with the unrounded invoice.total while the attached PDF
showed the rounded amount (and the email also ignored the ROT/RUT
deduction).

Extract the PDF's Att betala block into getAmountToPay
(lib/invoices/rounding.ts) and point PDF + email at it so they cannot
drift; behavior-identical refactor for the PDF. Booked amounts stay
ore-exact; display-only as designed.

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

* test(reports): adapt huvudbok date-range tests to the two-step entry-lines fetch

The date-range tests (0969168f) mocked the old single-query shape with the
parent entry embedded on each line; main's refactor (fetchEntryLines)
queries journal_entries first and reattaches. Queue entry rows like the
other tests so the merge of the two features is actually exercised.

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

* fix(invoices): fetch full invoice projection in v1 send so ROT/RUT deduction and payment link reach the PDF and email

The v1 send route's hand-rolled column list omitted deduction_total,
deduction_personnummer_last4, payment_link_url and the item-level
ROT/RUT fields, so invoices sent via the public API overstated
'Att betala' and dropped the deduction box. Reuse the shared
INVOICE_FULL_COLUMNS/INVOICE_ITEM_FULL_COLUMNS so the send row can
never drift from the GET shape again.

Also harden the supplier-invoice inline approve: a thrown fetch left
the button stuck spinning; failures now refetch the true server state.

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

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-12 00:56:16 +02:00
Jakob WennbergandClaude Sonnet 5 ec27228a8e style: remove em/en dashes repo-wide, add CLAUDE.md rule against them (#890)
Em dashes (—) and en dashes (–) had spread across comments, docs, tests,
and a few UI strings, reading as AI-generated boilerplate rather than
house style. Replaced each with punctuation matching its context: colon
for explanatory clauses, comma for asides, plain hyphen for numeric/legal
ranges (e.g. "21-23§"), "to"/"till" for date ranges, parentheses for
paired-dash asides. messages/en.json and messages/sv.json were fixed by
hand together to keep sv/en in sync.

Left untouched where the dash is the functional subject rather than
decorative punctuation: date-range-parser.ts's separator regex,
charset-repair.ts's CP1252 byte-mapping table (and its test), the SIE
encoding mojibake docs, generic-csv.ts's minus-sign normalizer, the
agent system-prompt files that already instruct against em dashes, and
a golden iXBRL test fixture compared byte-for-byte.

Also fixes two bugs surfaced along the way: an off-by-one in
ApiKeysPanel's scope-label split (a leftover from an earlier partial
pass), and a charset-repair test that had lost the literal en-dash it
exists to verify.

Regenerated the agent atom seed migration (skills:generate) since 27
SKILL.md files changed. Added a CLAUDE.md rule against em/en dashes,
with an explicit carve-out for the functional-dash cases above.

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-04 15:58:06 +02:00
Jakob WennbergandClaude Fable 5 9f7c842a33 feat(skatt): setup gates + auto-loading momsdeklaration across Skatt & bokslut tabs (#885)
* feat(skatt): setup gates + auto-loading momsdeklaration across Skatt & bokslut tabs

Every tab in the Skatt & bokslut nav group now tells an unconfigured user
what is missing and where to fix it, instead of dead-ending or rendering
zeros:

- Momsdeklaration: gates on vat_registered with a settings CTA; auto-fetches
  the configured period on load and on every period change (no more "Hämta"
  button); one period control (räkenskapsår picked inline for helårsmoms,
  shell selector + back link dropped via new ReportDescriptor.standalone);
  raw <select>s replaced with the Select primitive; banner when moms_period
  is missing; BankID connect returns to the page instead of the report
  library.
- Deadlines: callout (sv+en) when no system-generated tax deadlines exist —
  they are derived from tax settings, so point at /settings/tax rather than
  presenting an empty manual todo list.
- Årsbokslut: "no räkenskapsår yet" (CTA to bookkeeping settings) is now
  distinguished from "nothing to close yet".
- Skattekonto: non-auth fetch failures render an error card with retry
  instead of the misleading "inget saldo hämtat ännu" empty state.

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

* fix(skatt): address bot review — res.ok guard, blocking moms_period gate, panel hidden while räkenskapsår unresolved

- Auto-fetch treats non-2xx or unparsable responses as errors (with retry)
  instead of rendering undefined data.
- momsPeriodMissing now blocks the declaration (EmptyState + settings CTA)
  rather than fetching a guessed quarterly period behind a banner — a
  declaration submittable for the wrong period type is a hazard, not a
  convenience.
- SkatteverketPanel is not rendered while yearly mode awaits a fiscal
  period, so its actions can never target an unconfirmed period.

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

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 16:12:51 +02:00
Jakob WennbergandClaude Fable 5 01dbef4015 feat(dimensions): PR4 reports — dimension-filtered P&L + Resultat per projekt/kostnadsställe (#862)
* feat(dimensions): PR4 reports — dimension-filtered P&L everywhere + Resultat per projekt/kostnadsställe

The Project P&L milestone of the dimensions plan (dev_docs §7 PR4).

One choke point lights up everything: generateTrialBalance gains
options.dimensions (SIE dim → code map) pushed down as jsonb containment
(dimensions @>, served by idx_jel_dimensions_gin) on both line queries, with
company-wide opening balances dropped when filtered (they cannot be
dimension-scoped; P&L-safe by whitelist). Resultatrapport, resultaträkning,
huvudbok, monthly-breakdown and the TB drill-down inherit the filter; the
KPI route filters only its P&L-side inputs (income statement, months,
expense composition) — never cash/VAT.

New report lib/reports/dimension-pnl.ts — "Resultat per projekt/
kostnadsställe" (Fortnox Resultatrapport projekt): value-as-column matrix
over one dimension with an explicit "(Utan dimension)" bucket computed as
the residual against the same trial-balance pass resultatrapport uses, so
every row and the Totalt column reconcile with the unfiltered
resultatrapport by construction. Registered in REPORT_CATALOG (visible only
when dimensions_enabled), slug-routed view + xlsx export.

UI: DimensionFilter (dimension + value picker, persistent "Filtrerad — ej
fullständig rapport" chip) mounts in FocusedReport for catalog entries
flagged dimensions: true; huvudbok rows show line dim codes.

Statutory exclusion pinned by TEST, not convention:
lib/reports/__tests__/dimension-statutory-guard.test.ts fails if the filter
parser leaks into balance sheet, balansrapport, kassaflöde, VAT, SIE or
full-archive routes/generators, or if the catalog whitelist widens.

MCP: new gnubok_get_dimension_pnl (reports:read); dimensions filter arg on
get_trial_balance/get_income_statement/get_general_ledger with
resolve-don't-select (names → registry codes, resolution echoes);
query_journal totals fixed to aggregate the FULL match set (was silently
slice-scoped while claiming otherwise) with an honest totals_scope field,
plus group_by / group_by_dimension aggregation.

Also: voucher-detail dim-6 badge now uses the registry name instead of the
non-standard "PR" abbreviation (#859 review follow-up).

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

* fix(dimensions): address #862 review — export disclosure, prior-column suppression, period-label honesty, route hardening

- Filtered XLSX/PDF exports now carry the partial-view disclosure past the
  file boundary (BFNAR 2013:2): filename suffix (-dim6-p001), a
  "Filtrerad … — ej fullständig rapport" row on every sheet, and a header
  note/title line in the PDFs.
- Resultatrapport drops the prior-year column when a dimension filter is
  active — project codes are time-limited under K2/K3, so "this code last
  year" may be a different project (same rule as narrowed date ranges).
- dimension-pnl no longer accepts fromDate: the matrix is cumulative from
  period_start by design (closing-balance semantics), and the period label
  now states exactly that instead of echoing a lower bound that was never
  applied. Routes/MCP tool updated to toDate-only.
- dimension-pnl routes 404 on an unknown/foreign period id and cap dim_no
  to 4 digits (matching the MCP tool's PostgREST-path guard, which the
  generator now also enforces itself).
- Statutory-guard test's generateTrialBalance call-site scan is paren-aware
  instead of a 300-char window; added fully-untagged and injection-guard
  test cases.

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

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-02 15:20:47 +02:00
Jakob WennbergandClaude Opus 4.8 739f18fd1c fix(reconciliation): drive bank avstämning period from the report header (#771) (#774)
The bank reconciliation view hosted its OWN FiscalYearSelector inside the
action bar — but that bar renders below the loading-skeleton early-return, so
the selector never mounted, its onReady/onChange never fired, the periodReady
gate never flipped, and the page hung on a permanent skeleton (#771).

Make the report period-scoped like the ledgers: lib/reports/catalog.ts marks
bank-reconciliation `params: 'fiscal'`, so the report page's räkenskapsår
selector owns the period. FocusedReport passes periodId + periodBounds down,
and BankReconciliationView takes them as props instead of self-selecting. The
window seeds from periodBounds and a periodId-keyed effect re-seeds (and writes
dateFromRef/dateToRef synchronously) on a year switch, preserving the #751
period-scoped IB-floor behaviour without the deadlock. Manual date edits still
apply on demand via "Filtrera".

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 13:42:49 +02:00
Jakob WennbergandClaude Opus 4.8 ff01640f60 feat(reports,settings): report library + focused report routes, settings modal (#629)
* feat(reports,settings): report library + focused report routes, settings modal

Reports
- Replace the monolithic /reports tab-switcher with a calm, grouped report
  library landing (ReportLibrary + RecentReportsShelf) driven by a new
  lib/reports/catalog.ts.
- Each report opens a focused /reports/[slug] route (FocusedReport) with a
  shared fiscal-year selector, optional date-range, and URL-based account
  drill-down into the general ledger.
- Extract every report view into components/reports/views, add a reusable
  ReportExportMenu, and remove the old ReportsNav.

Settings
- Add an intercepting @settingsModal parallel route so in-app navigation to
  /settings opens as a modal over the current page; hard loads still resolve
  to the full page.
- Share one SettingsShell (rail + content) between page and modal, extract each
  section into components/settings/sections/*Content, add a settings hotkey and
  command-palette entry, and remove the old SettingsSidebar.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(reports,settings): remove dead salary-journal entry, fix border token

Addresses PR review feedback (#629):

- Remove the unreachable `salary-journal` report from the catalog. It had
  needsEmployees + no route + no FocusedView handler and `hasEmployees` was
  never plumbed through, so it never appeared in the library and a direct
  /reports/salary-journal URL rendered a blank frame. The report was never on
  the old page and has no view component; the API + generator stay in place for
  a proper follow-up. Drops its two now-unused i18n keys.

- Replace opacity-suffixed `border-border/8` section dividers with full-opacity
  `border-border` across the extracted settings section components, per the
  design system (no opacity-suffixed border tokens on surfaces).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* ci: re-trigger checks (pg-real hit a Docker Hub registry timeout)

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-02 14:50:50 +02:00