18 Commits
Author SHA1 Message Date
MattssonandClaude Fable 5.1 0c854ac54f feat(settings): let a company name each verifikationsserie letter (#2336)
* feat(settings): let a company name each verifikationsserie letter

The series pickers show a fixed preset label next to every letter (A
Redovisning ... M Momsrapport, Fortnox's layout). A byrå that lays its
series out differently sees a wrong or missing name in every dropdown: a
partner running löner on L saw "Kontantfaktura" in the verifikat form and
asked for the series name.

- company_settings.voucher_series_labels JSONB ({"L": "Lön"}), keys A-Z,
  values 1 to 40 chars, CHECK on the JSON shape. Display only; the engine
  never reads it.
- UpdateSettingsSchema validates the map, trims names and strips empty
  values so a cleared field removes the name.
- voucherSeriesLabel(letter, labels) is the one place that decides what a
  letter is called: company name, then preset, then empty.
  buildVoucherSeriesOptions replaces the three near-identical option
  builders in the verifikat form and the two settings pickers.
- The Verifikationsserier list in settings edits the names: rows are the
  union of used, configured and named letters, one save button.
- The SIE import review's two series pickers show the name too.

Migration applied to staging (metjnjrhvujscngnpzdv) as 20260906131300.

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

* fix(settings): keep imported series in the list and unsaved names through a refetch

Skeptic pass on the series-name editor refuted two things:

- The rewritten list filtered voucher_sequences to single letters, dropping
  multi-character series (FT, LB, SKV, ...) that 54 production companies
  carry over from Fortnox and Bokio imports; the old list showed them with
  their highest number. Rows are now every used series plus the configured
  and named letters; only single-letter series get a name input, since
  those are what the pickers offer and the schema accepts.
- The draft re-seeded on the identity of settings.voucher_series_labels,
  and the settings hook revalidates on window focus with a fresh object, so
  unsaved typing was wiped after any earlier save on the page. The re-seed
  is now keyed on the serialized content of the saved names.

Also folds the "new series are created on first use" footnote back into
the group help, which the rewrite had dropped.

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

* fix(settings): name the default-series options and enforce the label shape in the database

Review pass on #2336:

- CodeRabbit: the Standardserie selector under Bokföring still rendered
  bare letters; it now shows the same name the other pickers do, through
  voucherSeriesLabel.
- Compliance swarm (SOC 2 PI1.1, low): the key and length rules for
  voucher_series_labels lived only in UpdateSettingsSchema. Migration
  20260906134700 adds voucher_series_labels_valid(jsonb) and swaps the
  object-only CHECK for one that mirrors the Zod rules (keys A-Z, values
  non-blank strings of at most 40 characters), so a write that bypasses
  /api/settings cannot store a map the pickers cannot handle. Applied to
  staging with its schema_migrations row; verified against good, empty,
  lowercase, blank, over-long, numeric and array inputs.

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

* test(pg): cover the voucher_series_labels CHECK against real Postgres

The coverage gate refuses a migration that adds a function without a
*.pg.test.ts. voucher_series_labels_valid(jsonb) and the constraint that
wraps it now have one: accepts the empty map and single-letter keys with
names of 1 to 40 characters, rejects lowercase and multi-letter keys,
blank, over-long, numeric and null values, arrays and scalars, and leaves
the row untouched after a refused write.

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

---------

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-06 16:20:42 +02:00
MattssonandClaude Fable 5.1 f33628f005 feat(import): say in the SIE wizard that the chart and fiscal year come along (#2307)
* feat(import): say in the SIE wizard that the chart and fiscal year come along

The preview scored the file's accounts against the BAS reference and said
"matchas mot din kontoplan", so a consultant with a 41-account seeded company
read "150 mappade" as "the file's chart replaces mine". A fiscal-year overlap
with a non-empty period was only refused after the mapping step.

- Parse route adds preview.chart (accounts new to THIS company vs already
  present, with a sample) via planChartChanges, and preview.fiscalYear from
  precheckFiscalPeriod: the containment/overlap verdict extracted out of
  ensureFiscalPeriod, which now consumes it, so preview and import cannot drift.
- Preview card renamed to Kontoplan with the counts and the fiscal-year
  verdict (match / create / conflict with the import's own refusal text).
- Review step lists the chart among "Vad händer när du importerar?".
- executeSIEImport reports accountsCreated from the account sync; the result
  grid gets a Konton skapade card.

No import logic changed; no migration.

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

* fix(import): preview refuses what the import refuses, and the chart card survives "Skapa saknade konton"

Skeptic pass on #2307 refuted the first cut twice:

- "Skapas vid import" was shown for a #RAR the import then refuses under
  BFL 3 kap. (19 months, non-month-end finish, mid-month start after an
  earlier year). The shape rules move into precheckFiscalPeriod as a fourth
  verdict 'invalid' with the same refusal text; ensureFiscalPeriod stays a
  consumer of one verdict, same query order.
- The Kontoplan card counted unmapped sources under "Läggs till" and kept
  listing them after the create button, while the result said 0 created.
  planChartChanges (now client-safe in lib/import/chart-plan.ts) counts
  mapped targets only; the create button moves those accounts from
  "Ej mappade" to "Finns redan" in place.
- The review line claimed existing accounts keep their name unless you opt
  in; the switch defaults to on. Reworded to match.
- Sample names follow the file for identity mappings, as the sync does.

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

---------

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-05 13:56:54 +02:00
227a6317f1 fix(import): label the SIE preview IB total as "summa debet", not "IB Summa" (#2142)
The fourth stat card in the SIE preview showed the debit-side total of the
opening-balance voucher under the label "IB Summa". A user read it as the
net ingående balans and could not reconcile it against any single figure.

- Relabel the card "IB, summa debet" and add a one-line helper saying it is
  the sum of all debit balances in IB, not a single account balance. The
  number equals "Total debet" in the Balansräkning (IB) card right below.
- Review step: "Skapar IB-verifikation, summa debet X" instead of
  "Skapar verifikation för IB på X".
- Comment the field in generateImportPreview so the meaning is explicit.

No data or logic change: openingBalanceTotal keeps its semantics.


Claude-Session: https://claude.ai/code/session_01AvaV9n4GswzF2Mq932PXTJ

Co-authored-by: Jakob Wennberg <311770904+jakobwennberg-oss@users.noreply.github.com>
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-04 09:42:41 +02:00
3ee3565d6d perf(reference-data): sweep the remaining raw reads onto the session cache, ratchet to 0 (#1941)
Final consumer migration of the responsiveness plan: the 35 files still
fetching fiscal periods, settings, accounts, cash accounts, dimensions or
templates on their own now read lib/reference-data, and every client
write site invalidates the shared cache instead of refetching locally.

Settings and registries: FiscalYearsManager, FiscalPeriodEditor (period
snapshotted once per company so a revalidation cannot reset dates being
edited), BookingTemplatesPanel, ChartOfAccounts, ChartOfAccountsManager,
EditAccountDialog, CorrectionEntryDialog, StrikeLinesDialog,
InvoicePaymentAccountsSettings; the dimensions registry (DimensionsManager,
DimensionCombobox, LineDimensionFields, DimensionFilter, bookkeeping/[id])
reads useDimensions and the ad-hoc fetchDimensions/fetchDimensionsCached
helpers are deleted.

Pages and pickers: CashAccountSelector (FyPicker-shaped restore, once per
company load), use-account-names, FiscalYearGapNotice,
OpeningBalancePeriodStep, BankFileConfirmStep, ImportReviewStep, the import
page (invalidates accounts + periods after a SIE execute), customers list,
invoices list + detail, pending, salary employee, asset dispose, year-end
and periodisering pages (invalidate periods after closing), reports
DimensionPnlView (its pivot picker read the wrong payload key and was
always empty; it now populates), SkatteverketPanel, TemplatePicker,
ArticleForm (vat_registered).

Invoice dialogs and extensions: SendInvoiceDialog, PaymentBookingDialog
(init reduced to the credit-note lookup + catalogue, proposal and voucher
preview fire on open when cached; a local getSession replaces the network
getUser for the fallback CC), InvoiceInboxWorkspace, TicWorkspace,
ArcimMigrationWorkspace (invalidates after each SIE import step),
enable-banking AccountPickerDialog.

raw-reference-fetch ratchet: 35 -> 0 files.

Co-authored-by: Jakob Wennberg <311770904+jakobwennberg-oss@users.noreply.github.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-08-26 14:56:37 +02:00
MattssonandClaude Fable 5 c6f2bebab9 fix(sie): selectable IB voucher series, smarter IB toggle on re-import, orphan-IB guard (#1896)
* fix(sie): selectable IB voucher series that never collides with the file's numbering

The Ingående balanser voucher was hardcoded to series A and created before
the file's vouchers, so it consumed the A series' next number and shifted
every imported A voucher one number higher than in the source system
(issue #1882).

- IB voucher series is now selectable in the import wizard; the default is
  the first of M,O,P,Q,R,S,T,V,W,X,Y,Z not used by the file's #VER records
  (M matches the existing migration-adjustment series).
- Plumbed end to end: wizard -> /api/import/sie/execute -> executeSIEImport,
  v1 REST options.openingBalanceSeries, MCP gnubok_import_sie
  opening_balance_series -> commitImportSie.
- The wizard's 'Importera ingående balanser' toggle now defaults OFF when a
  posted IB voucher already exists inside the file's fiscal year, with a
  hint saying why.
- Orphan-IB guard in executeSIEImport: replace_sie_import deletes only
  source_type='import' entries and clears the period's OB pointer, so a
  prior import's IB voucher survived every replace cycle and each re-import
  created another one (field report: five accumulated). The import now
  skips IB creation with a warning when a posted opening_balance entry
  already exists in the period.
- MCP import_opening_balances default (false) vs web (true) documented as
  deliberate in the tool schema and DECISIONS.md.

Fixes #1882

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

* fix(sie): harden IB series fix after skeptic review (relink orphan, exclude fallback series, type-check option)

Skeptic findings on PR #1896, all four blocking items:

- Orphan-IB guard now relinks a single surviving opening-balance voucher
  as the period's OB entry (permitted by the immutability trigger while
  the pointer is NULL): without it, reports showed IB 0, year-end's
  duplicate-IB blocker never armed, and the manual IB flow could
  double-book. It also diffs the survivor's lines against the file's IB
  and calls out stale amounts in the warning instead of keeping them
  silently; reverseEntry clears the pointer again for the
  storno-then-reimport path.
- Series-less #VER records resolve to the transaction fallback series at
  import time, so the IB default picker now treats that series as used by
  the file (the same #1882 shift pattern through the fallback). The
  wizard recomputes its IB default with the effective transaction series
  once loaded.
- openingBalanceSeries is type-checked on the web execute route, the MCP
  stage, and the staged-operation commit: a non-string falls back to the
  default instead of crashing mid-import after side effects.
- The wizard's IB series select flags series used by the file and shows
  an attention line when the chosen series collides; the engine warns
  when an explicitly chosen series collides with the file's series (the
  choice is honored).

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

* fix(sie): uppercase caller-chosen IB series before persisting

Swedish accounting review on PR #1896: a lowercase series from v1 or
MCP was persisted as-is, booking a case-distinct parallel series next
to its uppercase sibling (BFL 5 kap requires one systematic series)
and slipping past the file-collision warning. Normalize centrally in
executeSIEImport, the single funnel for web, v1, and MCP.

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

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-08-25 15:14:59 +02:00
e51a2c8102 refactor(design): no amber boxes, attention is one ochre sentence (#1562)
The founder wants the yellow boxes gone everywhere. The design system
already agreed: status colors are data, not chrome (convention 12) and
attention is a single ochre sentence, never a banner (convention 6).
This enforces it:

- ConfirmationDialog: the amber warning panel is now an AttnLine, and
  the hardcoded Swedish default warningText is gone: it injected an
  immutability warning into dialogs whose authors never asked for one
  (every current caller passes the prop explicitly, so no behavior
  change at any call site).
- Badge warning variant: amber fill replaced with a hairline chip and
  ochre text.
- DestructiveConfirmDialog warning variant: neutral icon disc, default
  primary confirm button (only --destructive survives as chrome).
- BankSyncStatusChip stale state: same neutral shape as the healthy
  chip, ochre text carries the signal.
- SandboxBanner: solid amber bar becomes secondary-on-border chrome.
- BankIdAuth, BankIdCompanyPicker, SessionTimeoutModal: the last three
  raw-amber (bg-amber-*) holdouts moved onto tokens, the company-picker
  banner becoming a plain AttnLine.
- Mechanical sweep of the ~58 hand-rolled bg-warning/border-warning
  boxes across 45 files: fills to bg-muted/30 (icon discs bg-muted),
  borders to border-border, text-warning-foreground to text-attn. The
  account-class dots in account-number.tsx keep bg-warning: they are
  data indicators, not chrome.

Co-authored-by: Jakob Wennberg <311770904+jakobwennberg-oss@users.noreply.github.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-08-13 11:20:16 +02:00
d4ef4f8bc4 feat(import): the import theater during SIE execute (#1471)
* feat(onboarding): branch question on the journey done screen

Second slice of the approved activation concept: the moment the company
exists, the done screen asks "Var fanns bokföringen innan?" with
provider chips (real logos), SIE file, and new-business options, plus a
quiet look-around escape. Choices persist initial_setup_path
(fire-and-forget) and deep-link into the existing flows: providers jump
straight to the migration wizard's connect step (sieViaApi providers
only; Visma/Bokio land on the provider list where the SIE-first gate
lives), the SIE chip opens the upload step, new business lands on Hem
with step one checked off.

mode='add' keeps the plain "Öppna Accounted" button: the concept's own
guard, and it avoids writing the path onto the previous company if
setActiveCompany silently failed. Routing lives in a pure helper with
tests; anonymous onboarding_branch_chosen funnel event follows the
guarded capture pattern.

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

* fix(onboarding): review triage: single-choice latch, preselect reset

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

* fix: restore package-lock.json to main (worktree npm install mutated it)

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

* feat(import): the import theater: a knowledge graph draws itself during SIE execute

Third slice of the activation concept. While the SIE import commits
server-side (one opaque call, up to ~5 min), the client parses the same
file locally (the parser is browser-clean) and a canvas constellation
builds itself: company hub, fiscal years as tree rings, account-class
anchors, top accounts and recognized counterparties, with paced
narration lines alongside. The final line holds with the elapsed counter
until the server answers, so the theater never outruns the truth.

- lib/import/theater-model.ts: pure aggregation of ParsedSIEFile into a
  capped display model (14 accounts, 12 counterparties, >=2 sightings,
  internal accounting texts skipped, counterparty attached to its
  counter account rather than the bank leg). Tested with fixture-string
  SIE per the sie-parser test pattern.
- components/import/ImportTheater.tsx: ink-on-paper canvas + narration,
  tokens read per frame (theme/palette reactive, JourneyOrb idiom),
  reduced motion renders the settled graph and all lines instantly.
- Wizard: client parse kicks off at execute start via dynamic import,
  capped at 8 MB; any failure silently leaves the existing spinner
  takeover, which also remains for oversized files.

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-09 19:58:33 +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 Opus 4.8 2da9c71eb3 UI badge cleanup + /chart-of-accounts route + loop skills (#850)
Bundles three separable concerns:

- style(ui): badge audit + cleanup across 45 files — real-status chips use Badge variants (raw Tailwind colors dropped), non-status count/type/label chips demoted to muted text, clustered badges consolidated; 20 unused imports removed.
- feat(bookkeeping): Kontoplan moved to a dedicated /chart-of-accounts route (nav + command palette wired); /bookkeeping shows the journal list only.
- chore(skills): loop-* automation skills + design-scan workflow under .claude/.

fix(reports): restored the destructive count badge on blocking errors in the periodisk sammanställning (EC Sales List) — a genuine status cue the audit had wrongly flattened; flagged by the PR reviewer and Swedish compliance bot, now clean.

All CI green; compliance bots report no findings.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 14:29:23 +02:00
Jakob WennbergandClaude Opus 4.8 b800dcd403 style(ui): system-wide UX/UI polish pass — design-system conformance + copy cleanup (#835)
* style(ui): system-wide UX/UI polish pass — design-system conformance + copy cleanup

Multi-agent scan of all 404 UI files against the locked design system, then
141 verified surgical fixes across 109 files (net -32 lines):

- Remove forbidden elevation/motion: shadow-* and rounded-xl on cards, active:scale
  bounce, hover:shadow on list items, transition-all -> transition-colors.
- Drop font-medium from single-weight Hedvig display headings/numerals.
- Replace raw rainbow Tailwind status colors with Badge variants / brand tokens /
  neutral surfaces (achromatic chrome, semantic colors stay data-only).
- Route raw dates through formatDate(), hand-rolled currency through formatCurrency(),
  add tabular-nums to financial figures; text-gray-* -> text-foreground tokens.
- Swap hand-rolled skeletons for the Skeleton primitive; off-scale spacing -> token scale.
- Fix copy: mislabeled "Leverantörsfakturor" -> "Utgifter" on bank-import outflow total,
  collapse no-op identical-branch ternaries, broken Swedish diacritics (mojibake),
  correct mismatch-password toast, correct supplier currency-field label.
- Remove PII-leaking debug console.log on register, stray console.logs.

Verified: tsc clean on all changed files, eslint clean, production build passes.

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

* fix(auth): sanitize residual error logs in register flow

Follow-up to PR review (compliance swarm V16 / GDPR Art.5(1)(f)): the
remaining console.error calls in the register flow passed raw error
objects, which Supabase may populate with PII (email) in nested fields.
Log only sanitized message strings instead.

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

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-30 13:14:13 +02:00
Jakob WennbergandClaude Opus 4.8 4dfd790de5 feat(bookkeeping): Ny verifikat modal, ledger-style list, SIE no-underlag exemptions (#698)
* feat(bookkeeping): Ny verifikat modal, ledger-style list, SIE no-underlag exemptions

Verifikat UX
- "Ny verifikat" opens in a modal (NewJournalEntryDialog) instead of an inline tab;
  the review step renders inline in the dialog rather than stacking a second dialog.
- JournalEntryForm: konteringsrader are the focus, with a compact pre-filled metadata
  bar (datum/serie/text/valuta/period) on top; verifikationstext auto-fills from the
  first row's account.
- JournalEntryList: belopp shown on collapsed rows; expanded view is an aligned
  Konto/Benämning/Debet/Kredit table.

SIE imports no longer flood "Att hantera: saknade underlag"
- Import gains an opt-in (off by default) toggle to mark imported verifikat as "Inget
  underlag krävs"; a "Rekommenderas vid migrering" badge nudges it for historical years.
- Multi-select batch-mark in the list for selective cleanup.
- Filter-scoped bulk mark (POST /api/bookkeeping/no-doc-required/bulk-missing): marks
  every missing-doc verifikat matching the active filters across all pages, with a
  dry_run count to confirm scope — the scalable remedy for a post-import flood.
- Shared helper markEntriesNoDocRequired + per-entry batch route.

Tests: no-doc helper, batch route, bulk-missing route.

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

* fix(bookkeeping): address PR #698 review findings

- JournalEntryForm: restore the explicit "no underlag" acknowledgement in the
  modal's inline review. When no document is attached, the confirm button reads
  "Bokför utan underlag" (BFL 5 kap 6-7 §§), equivalent to the blocking dialog the
  non-bare flow shows — the bare path no longer posts behind only a passive banner.
- batch no-doc route: guard the ownership query with source_type IN
  NEEDS_DOC_SOURCE_TYPES so a crafted request can't exempt non-document-requiring
  entries (defense in depth on top of company + posted scoping).
- bulk-missing route: resolve doc/exemption status by querying only the candidate
  ids (chunked) instead of loading the company's full document_attachments and
  journal_entry_no_doc_required tables into memory — data minimisation + bounded
  memory for large migrations (the most-repeated reviewer finding).

Triaged as non-issues (left as-is): partial-import exemption (gated on
result.success == zero errors), reason write-back (sidecar row is FK-linked and
carries the reason), and "bulk-exempting manual entries" (consistent with the
existing per-entry NoDocRequiredToggle). No DB migration — reuses the existing
journal_entry_no_doc_required table.

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

* fix(bookkeeping): centralize bulk-missing date/series validation in Zod

Move the ISO-date and verifikationsserie format checks into the Zod schema so
malformed input is rejected with a clean 400 instead of being silently nulled
(or, for a shaped-but-invalid date, throwing a 500 via fetchAllRows). The date
refinement rejects values like 9999-99-99 / 2026-02-30 that a bare
/^\d{4}-\d{2}-\d{2}$/ regex lets through. Addresses the PR #698 reviewer nit on
split schema-vs-runtime validation. +2 route tests.

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

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 20:41:30 +02:00
Jakob WennbergandClaude Opus 4.8 f7cd1b86e7 fix(import): preserve customized SIE #KONTO account names (#669)
* feat(import): add syncMappedAccounts helper for account create + rename

Single home for the create-missing-accounts logic that exists in three
near-identical copies (executeSIEImport, the SIE execute route, and the
arcim-migration extension), plus a new rename pass that carries customized
SIE #KONTO names into accounts that already exist (e.g. K1-seeded defaults).

The file's name applies only to identity mappings (source === target);
remapped targets keep their BAS/current name. With updateAccountNames=false
the behavior matches the legacy code exactly. Not wired up yet.

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

* fix(import): preserve SIE #KONTO account names; add updateAccountNames option

Customer report: account names customized in Fortnox did not follow into
Accounted via SIE import. The import always used BAS default names for
accounts in the BAS reference and never touched accounts that already
existed (the K1-seeded chart), so the file's names were silently dropped.

executeSIEImport now routes account creation through syncMappedAccounts,
which prefers the file's #KONTO name for identity-mapped accounts and
renames existing accounts whose name differs (surfaced as a warning).
New option updateAccountNames (default true) restores the old behavior
when disabled. The duplicated pre-create blocks in the execute route and
the arcim-migration extension are removed — executeSIEImport owns account
sync on every path now, including the Fortnox re-sync (idempotent renames).

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

* feat(mcp): expose update_account_names on gnubok_import_sie

Optional boolean on the tool schema, staged into the pending operation and
threaded through commitImportSie to executeSIEImport. Defaults to true at
both stage and commit time — the commit-side default also covers operations
staged before the param existed (Boolean(undefined) would have silently
flipped it off).

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

* fix(api): v1 SIE import generated no account mappings

The route passed [] as mappings to executeSIEImport, which the
mapping-coverage guard (added in #613) rejects for any real file — and
before that guard, every voucher was silently skipped as unmapped. The
route has never produced a working import for files with vouchers.

Generate mappings server-side from the file's #KONTO records plus stored
per-company overrides (same as the dashboard execute route), reject
unmappable files with a clean 400 before the operation row is created,
and expose options.updateAccountNames (default true).

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

* feat(import): "Använd kontonamn från filen" toggle in import review step

New switch (default on) controlling whether the SIE file's #KONTO names
are carried into the chart of accounts. Helper text shows how many
identity-mapped accounts carry names that differ from the BAS defaults.
The page already serializes the whole options object to the execute
route, so no further wiring is needed.

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

* fix(import): address PR #669 review — parallel renames, rename audit trail

- Rename pass now runs UPDATEs concurrently in bounded batches of 25
  (greptile P2): a re-sync with many custom names no longer serializes
  N round trips, and a pathological full-chart rename cannot stampede
  the API. Per-rename failures stay non-fatal via Promise.allSettled.
- Persist the per-account rename detail (number, from, to) into
  sie_imports.migration_documentation as accountRenames — the
  behandlingshistorik record per BFNAR 2013:2 (swedish-compliance
  review); the result warnings only carry the count.

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

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 20:35:49 +02:00
Mattsson 24107338fa Fix/balance inconsitency (#306)
* feat: implement fiscal period date fields component and validation logic

* feat: update fiscal period validation and naming logic

* feat: implement RPC for computing prior opening balances

- Added `compute_prior_opening_balances` RPC to aggregate opening balances for balance-sheet accounts when no opening balance entry is set.
- Updated tests across various reports to utilize the new RPC for fetching prior balances.
- Refactored `getOpeningBalances` to call the RPC when necessary, improving performance and reliability.
- Introduced a script to repair fiscal period chains for companies with broken periods, ensuring proper linking and continuity.
- Enhanced error handling and validation in the repair script to ensure data integrity during the process.

* feat: implement duplicate opening-balance repair for multi-year SIE imports

* feat: enhance SIE entry listing and deduplication logic for opening balances

* fix: refine companyHasPriorActivity logic to exclude storno entries and improve balance counting
2026-04-21 21:39:57 +02:00
Jakob WennbergandClaude Opus 4.7 2ea5a72b3d feat: dynamic voucher series dropdown in SIE import (#274)
* feat: dynamic voucher series dropdown in SIE import

Populate the voucher series picker on the import review step from the
company's own data instead of a hardcoded A/B/C/I list. Shows all A–Z
series with inline labels for the company default ("standard") and any
series that already have a running sequence ("används redan"), and
preselects the company default (falling back to B, then the first
existing series, then A).

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

* fix: address voucher series dropdown review feedback

- Log non-PGRST116 Supabase errors from company_settings and
  voucher_sequences fetches instead of silently swallowing them.
- Disable the series Select until the async load completes so users
  don't briefly see the 'B' fallback before it snaps to the real
  company default.
- Replace the seriesInitializedRef guard with a seriesLoaded state that
  resets on company.id change, so switching companies re-runs the
  preselection instead of sticking with the prior company's choice.

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

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-20 09:47:37 +02:00
Mattsson a1a816b4a5 Delete features (#218)
* Implement company and account deletion features

- Add event types for company and account deletion to CoreEvent.
- Enhance Supabase middleware to handle company context resolution and cookie management for archived companies.
- Create API routes for deleting accounts and companies, including necessary validations and event emissions.
- Implement tests for account and company deletion endpoints to ensure proper functionality and error handling.
- Add retention notice component to inform users about bookkeeping data retention during destructive actions.
- Create database migrations to support soft deletion of companies and anonymization of user accounts, ensuring compliance with retention laws.

* feat: enhance account deletion process and update user notifications

* Add service client for onboarding completion check and update escape hatch visibility

* Enhance invite flow and email handling for company members

* Refactor company context and RLS policies for active company isolation

- Update `switchCompany` to remove unnecessary revalidation as client handles navigation.
- Revise `getActiveCompanyId` to prioritize `user_preferences` and validate against non-archived memberships.
- Modify `setActiveCompany` to ensure `user_preferences` is the authoritative source while maintaining cookie compatibility.
- Enhance middleware to resolve active company using `user_preferences` and fallback to first non-archived membership.
- Introduce new API route `/api/company/current` to fetch the active company ID for cross-tab synchronization.
- Implement `CompanyTabSync` component for real-time active company enforcement across tabs.
- Create migration for RLS policies to enforce single-active-company isolation using `current_active_company_id()`.

* feat: implement viewer role enforcement for write permissions

- Added `useCanWrite` hook to determine if the current user has write permissions based on their role in the active company.
- Updated various components (JournalEntryForm, CustomerForm, DeadlineForm, etc.) to disable write actions and show a lock icon with a tooltip for users without write permissions.
- Introduced `requireWritePermission` function to enforce write permissions at the API level, returning a 403 response for viewers.
- Created tests to verify the behavior of the viewer role and write permissions.
- Added database migration to enforce read-only access for viewers at the database level.
2026-04-11 17:06:32 +02:00
Jakob WennbergandClaude Opus 4.6 5cb8c3c1e4 feat: support contact links and SIE import UX polish (#138)
* feat: event log, pending operations, and MCP staging

- Event log system: persist bus events to event_log table for external
  automation platforms. Batch insert for transaction.synced. Daily
  cleanup cron at 02:00 UTC.
- Pending operations: MCP write tools (categorize, create customer,
  create invoice) now stage to pending_operations instead of executing
  directly. Users review and commit/reject from /pending in the web UI.
- Granskning page: card-based review UI with expandable previews,
  commit/reject dialogs. Only shown in nav when pending ops exist.
- Commit route re-executes using core lib functions (no extension
  imports). Guards against stale state (double-commit, deleted entities).

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

* feat: stage new MCP write tools after main merge

Add staging for 4 new write tools from #133:
- mark_invoice_paid, send_invoice, mark_invoice_sent,
  match_transaction_invoice
- Expand pending_operations CHECK constraint
- Add commit executors with full execution logic
- Add UI labels and generic preview component
- Remove confirm parameter from categorize (single-call staging)
- Fix UUID in pending op title (fetch transaction description)
- Hide Granskning nav when no pending ops

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

* fix: address PR review feedback

- Fix TS build error: use `select('*, customer:customers(*)')` for
  match_transaction_invoice to avoid array type inference
- Add status guard to commitSendInvoice (prevents duplicate sends)
- Replace auth.admin.getUserById with user email from session auth
- Restore optimistic lock check in commitMatchTransactionInvoice
- Fix tool description typo: expense_software → expense_office

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

* feat: add support contact links and improve SIE import UX

Add a SupportLink component with a contact dialog throughout the app
(nav, help page, settings, MFA, error pages, empty states). Improve
SIE import flow with phased loading states, structured skip breakdowns,
and an elapsed-time counter. Fix MFA enroll stale factor cleanup and
URL encoding for settings return path.

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

* fix: address PR review — open redirect, XSS, test cleanup, fallback email

- Validate returnTo is a relative path in MFA enroll (prevents open redirect)
- Add afterEach import to event-log-handler tests (fixes handler leak)
- HTML-escape user-supplied subject and message in support email body
- Replace hardcoded personal email with support@gnubok.se fallback

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

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 11:14:17 +01:00
Jakob WennbergandClaude Opus 4.6 e8fb84b4fd feat: import system improvements, INK2 fix, and Swedish text corrections (#10)
- SIE parser: Windows-1252 and CP437 encoding detection and decoding
- Bank file parser: add Nordea Business (Företag) CSV format
- Bank file parser: improve format detection for SEB, Länsförsäkringar, generic CSV
- INK2 engine: calculate årets resultat (7222) from income statement for open fiscal years
- Dashboard: parallel Supabase queries, simplified dashboard page
- Fix Swedish characters (å, ä, ö) in BAS data descriptions, validation messages, AI consent disclosures
- Import wizard UI improvements across all steps
- Migration: add 'bas_range' match type to sie_account_mappings constraint
- Extensive new tests for SIE parser encoding and bank file parser

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-11 18:05:37 +01:00
Jakob Wennberg a25d10a528 Initial copy from influencer-biz 2026-02-13 14:00:46 +01:00