Commit Graph
45 Commits
Author SHA1 Message Date
Jakob WennbergandClaude Opus 4.7 05078c9d8e feat(bokslut): year-end wizard with bokslutsdispositioner + asset register (#508)
* feat(bokslut): year-end wizard with bokslutsdispositioner + asset register

Ships the first user-visible bokslut surface for K2 aktiebolag. The year-end
engine, INK2/INK2R/INK2S generator, and reconciliation reports already existed
in lib/core/bookkeeping/ and lib/reports/; this work wires them into a real
multi-step UI, adds the missing dispositioner calculators (bolagsskatt,
periodiseringsfond, överavskrivningar, SLP), and introduces a fixed-asset
register that feeds planenliga avskrivningar into the same flow.

PHASE 1 — Wizard around the existing year-end engine
- Replaces the "Kommer snart" stub at /bookkeeping/year-end with a 4-step
  wizard (Kontroll → Dispositioner → Förhandsgranska → Verkställ) plus a
  Klart result view
- New aggregator lib/bokslut/readiness-aggregator.ts composes
  validateYearEndReadiness with bank-reconciliation status and entity-typed
  reminders into one fetch backing the preflight step
- New endpoint GET /api/bookkeeping/fiscal-periods/[id]/bokslut-readiness

PHASE 2 — Bokslutsdispositioner calculators
- lib/bokslut/tax-provision/{bolagsskatt,sarskild-loneskatt}-calculator.ts —
  20.6 % on taxable result → 8910/2512 (with non-deductible / non-taxable
  manual adjustments and schablonintäkt pass-through) and 24.26 % SLP on
  posted pension costs → 7533/2514
- lib/bokslut/reserves/periodiseringsfond-service.ts — proposeAvsattning
  (25 % cap, BAS 212X cohort accounts) + proposeAteforing (FIFO 6-year
  mandatory reversal with schablonintäkt computation) + balance lookup
- lib/bokslut/reserves/overavskrivningar-service.ts — 30-rule + 20-rule
  helpers + proposeOveravskrivningar (8853/2153)
- New endpoint /api/bookkeeping/fiscal-periods/[id]/bokslutsdispositioner
  (GET ordered proposals, POST commits user-chosen ones as separate
  year_end vouchers via the journal engine)
- New DispositionsStep UI: per-card accept/skip + editable amount where
  meaningful; mandatory p-fond reversals can't be skipped
- INK2 bug fix: ink2-engine.ts SRU mapping ranges previously pointed at
  accounts BAS doesn't seed (8810/8830/8840). Corrected to 8811 (avsättning),
  8819 (återföring), 8830 (lämnade koncernbidrag) so calculator output now
  flows into INK2 correctly. Regression-locked with 6 new mapping tests.

PHASE 3 — Anläggningsregister + depreciation engine
- New migration 20260516120000_assets_and_depreciation.sql: assets table
  (category, BAS-triple, K3 components JSONB reserved) and
  depreciation_schedules (asset+period+journal_entry link). RLS via
  user_company_ids(), immutability triggers after disposal/posting.
- lib/bokslut/assets/asset-service.ts — CRUD + disposal that posts a proper
  gain/loss entry against 3973/7973
- lib/bokslut/assets/depreciation-engine.ts — computeAnnualDepreciation
  (linear, pro-rata at acquisition/disposal/end-of-life) +
  proposeAnnualPostings + commitAnnualPostings (one entry per asset)
- New endpoints /api/assets (CRUD + dispose) and
  /api/bookkeeping/fiscal-periods/[id]/depreciation (preview + commit)
- /assets list+create page with K2 schablon defaults (3y datorer,
  5y inventarier, 25y byggnader); sidebar entry added
- DepreciationPanel mounted at the top of DispositionsStep; posting
  refreshes dispositions so bolagsskatt picks up the new result

Out of scope (per the agreed plan): K3 framework, iXBRL filing to
Bolagsverket (manual export only for now — regulatory risk flagged for
FY2026 closings), inventory module, koncernredovisning, revisor workflow.

Verification
- 116 unit tests pass across lib/bokslut/, lib/reports/ink2/, and the
  existing lib/core/bookkeeping/year-end-service suite
- Zero lint or typecheck errors in any new file
- Migration applied successfully via Supabase MCP

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

* fix(bokslut): address PR #508 review — P1 correctness + P2 conventions

P1 — stale reminders in readiness-aggregator (greptile)
Remove the depreciation_manual / bolagsskatt_manual / periodiseringsfond_manual
nudges. After Phase 3, the wizard handles all three automatically via
DepreciationPanel and the dispositioner calculators — surfacing them as
manual reminders told users to repeat work the page already did. Only the
accruals_manual reminder (Phase 4 hasn't shipped) and the EF-only
ef_skatt_via_ne reminder remain.

P1 — netBookValueAfter ignored prior accumulated depreciation
proposeAnnualPostings now fetches all prior posted depreciation_schedules
for the company (excluding the current period) and sums them per asset, so
the displayed restvärde reflects every previously-booked year of avskrivning
instead of only this year. Without the fix, a 5-year asset in year 3 would
have shown 48 000 instead of the correct 24 000 net book value.

P1 — ordering bug in dispositioner POST handler
The 25 % p-fond avsättning cap derives from the current trial balance, so
mandatory återföring entries must post first. Added a server-side sort by
canonical bokslut order (återföring → överavskrivningar → avsättning → SLP
→ bolagsskatt) regardless of the client array order. The cap can no longer
be evaluated against a stale pre-återföring net result.

P2 — depreciation_schedules missing updated_at
New migration 20260516140000_depreciation_schedules_updated_at.sql adds the
column + trigger via update_updated_at_column(). Per CLAUDE.md migration
conventions, never modified the original migration. DepreciationSchedule
type updated.

P2 — addMonths end-of-month overflow
Replaced setUTCMonth (which overflows: Jan 31 + 1 month → Mar 3) with a
day-clamping implementation that produces Feb 28/29. Without the fix,
lifeEndExclusive landed one day too late and slightly over-depreciated.
New regression test asserts Jan 31 + 12 months stays in January.

P2 — pg-real tests for new triggers and RLS
tests/pg/assets.pg.test.ts (13 tests) covers:
  - enforce_asset_post_disposal_immutability blocks every financial field
    after disposal, allows notes/name through
  - assets_disposal_atomic CHECK requires both disposed columns set together
  - enforce_depreciation_schedule_immutability blocks edits after
    journal_entry_id is linked, allows them before
  - depreciation_schedules delete RLS policy filters out posted rows
  - assets + depreciation_schedules RLS isolates across companies

Verification
- 117 unit tests pass (was 116, +1 for the addMonths regression)
- New pg-real suite syntactically + type-correct; will execute in CI
- Zero lint or typecheck errors in any touched file
- Migration 20260516140000 applied to remote Supabase via MCP

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

* fix(bokslut): address compliance-swarm + swedish-review findings

Real bugs surfaced by the bots on the first push that weren't covered by
greptile's inline P1/P2 set:

- Immaterial asset disposal hit the wrong BAS accounts. disposeAsset always
  posted gain/loss to 3973/7973 regardless of category. For category
  'immaterial' it must use 3013 (vinst) / 7813 (förlust) per BAS — using the
  tangible accounts misclassifies in INK2R. Now branches on category. Two
  new regression tests pin each branch.

- acquisition_cost CHECK was too loose. CreateAssetSchema accepted 0 (just
  nonnegative). Tightened to z.number().positive() — a zero-cost asset
  creates a no-op depreciation row and a balance sheet line that nothing
  reconciles against.

- UpdateAssetSchema let users remap BAS accounts arbitrarily. Bot flagged
  this as a defense-in-depth gap (V4.5). Added BAS_RANGES_BY_CATEGORY
  validation at both the schema layer (Create) and the service layer
  (Update) so user-supplied account overrides must stay inside the
  category's expected BAS range. INK2R mappings and the depreciation
  engine's category-driven defaults now can't drift.

Swedish accounting review:

- Building/markanläggning defaults — clarified UI copy. The 25-year
  schablon is K2-redovisning, not the IL skattemässig rate. New helper text
  spells this out. Markanläggning default lowered from 20→10 years
  (Skatteverket guidance allows 10 % rate; 20 was on the upper bound
  without justification).

- createAsset doesn't post the acquisition entry by design — that gap
  wasn't called out anywhere in the UI. Added a tip box in
  CreateAssetDialog explaining that the acquisition must already be in the
  books; the register only drives depreciation.

- Disposal VAT (ML 3:3 / 7:3) not handled — sale of a deduct-eligible
  anläggningstillgång is in principle 25 % momspliktig. Documented this as
  a known limitation in the disposeAsset docstring so any future UI
  surfacing the disposal endpoint warns the user.

Documented (not fixed yet) — bot was right but wider-scope work:

- SOC 2 PI1.3: dispositioner POST loop is not transactional. A failure
  midway leaves partial postings. Added a code comment explaining the
  recovery path (re-POST omitting committed kinds — each calculator
  re-derives from current TB). Real atomicity via an RPC wrapper is Phase
  5+ work.

False positives intentionally not changed:

- 4× OWASP V8.2.1 cross-tenant findings — service functions already filter
  by company_id; the bot can't see past the route handler.
- V2.3 client-supplied amount clamping — proposeAvsattning and
  proposeAteforing both clamp via Math.min already.
- A.8.15 audit events — withRouteContext already logs completion.
- Schablonintäkt journal entry — per IL 30:6a it's a skattemässig
  justering, never booked. Current implementation is correct.
- Voucher series 'A' — matches existing executeYearEndClosing convention;
  not changing here in isolation.

Verification
- 119 tests pass (was 117, +2 for the immaterial-disposal branches)
- Zero lint or typecheck errors on any touched file

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

* fix(bokslut): address compliance round 4 — BAS account overlap + method gate

Two real bugs the compliance bot caught after my round-3 fixes (both new on
its re-run, not in the original report):

- BAS account overlap not prevented: my BAS_RANGES_BY_CATEGORY uses the same
  class range for asset and accumulated (e.g. immaterial: both 1010–1099,
  building: both 1100–1199). Nothing stopped a user from picking the same
  account for both, which would silently net acquisition cost against
  accumulated depreciation in one bucket and corrupt INK2R 720x mappings.
  CreateAssetSchema now rejects bas_asset_account === bas_accumulated_account
  in a superRefine cross-field check; updateAsset enforces the same invariant
  by reading the existing asset and validating the merged result.

- declining-balance methods silently fell back to linear. The DB enum allowed
  declining_balance_30 / declining_balance_20, but the engine's
  computeAnnualDepreciation only implements linear math. A determined caller
  (MCP, curl, future UI) could create an asset labelled as räkenskapsenlig
  avskrivning and get linear charges — silently wrong numbers under a
  misleading method. Both CreateAssetSchema and UpdateAssetSchema now refine
  the depreciation_method enum to require 'linear'. The DB enum stays open
  for a future phase to add proper support. Stale comment in
  depreciation-engine.ts updated to reflect the new invariant.

False positives I'm explicitly not chasing further on this round:
- 3× repeated OWASP V8.2.1 cross-tenant — services already filter by
  company_id; bot can't see past the route handler. Round 3 already added
  service-layer tests and inline reasoning.
- V2.3 atomicity upgrade to high — bot now flags it harder *because* I
  documented it in round 3. The existing executeYearEndClosing has the same
  non-transactional sequential-write pattern; wrapping just this endpoint
  in an RPC while leaving the rest inconsistent is worse than the doc
  comment. Real atomicity is Phase 5+.
- Disposal VAT user-facing warning — no UI surfaces dispose yet; docstring
  in the service is sufficient until the UI ships.

Verification
- 119 tests pass
- Zero lint or typecheck errors on any touched file

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

* fix(bokslut): address compliance round 5 — disposal integrity + öavskr accounts

Round-5 bot feedback after my round-4 push (the sticky reports re-edited
themselves with two new genuine findings; bot count: 18 → 14 active).

P1 — accumulated_depreciation was client-supplied (OWASP V8.2.1, swedish
compliance review): the dispose endpoint accepted accumulated_depreciation
as a request-body number. A malicious or buggy caller could inflate it to
manipulate the book-value calculation and pocket a phantom gain. Now:

- DisposeAssetSchema no longer accepts accumulated_depreciation
- disposeAsset sums planned_depreciation from depreciation_schedules where
  journal_entry_id IS NOT NULL for the asset, server-side
- New regression test "server-derives accumulated_depreciation — caller
  cannot inflate gain" pins the server-derivation against the prior attack
- Limitation: manual avskrivningsverifikationer posted outside the engine
  aren't captured. Phase 5+ can swap this for a trial-balance scan on
  bas_accumulated_account if that gap matters.

P2 — överavskrivningar hardcoded 8853/2153 regardless of asset category
(swedish-asset-accounting): for buildings BAS uses 8852/2152 and for
immateriella tillgångar 8851/2151. Edge case for K2 SME (öavskr on
buildings is rare; on immateriella rarer still) but worth not lying about
the accounts. Now:

- New OVERAVSKRIVNING_ACCOUNTS table maps category → expense/accumulated
  pair (machinery_equipment, building, immaterial, group)
- proposeOveravskrivningar accepts optional category, defaults to
  machinery_equipment (the dominant K2 case — no behaviour change for
  existing callers)
- POST handler item schema accepts optional category
- Label + description strings now name the actual accounts used
- 3 new tests cover the building, immaterial, and default branches

False positives I'm still declining to chase (already covered in prior
commit messages):
- 3× repeated OWASP V8.2.1 cross-tenant — services scope by company_id;
  bot can't see past route handler
- V2.3 atomicity — existing executeYearEndClosing has the same pattern;
  wrapping just this endpoint is inconsistent; real fix is Phase 5+ RPC
- Disposal VAT user-facing warning — no UI surfaces dispose yet

Verification
- 123 tests pass (was 119, +3 for öavskr category branches and +1 for the
  server-derivation regression test, with one prior test rewritten to use
  the new server-supplied accumulated path)
- Zero lint or typecheck errors on any touched file

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

* fix(bokslut): pre-merge polish — building disposal accounts, SLR-2026 rate

Last polish round before merge. All three from the round-5 sticky
Swedish-accounting-review update:

- Building / markanläggning disposal posted gain/loss to 3973/7973. BAS
  2026 routes those to 3971/7971 (the SRU mapping points them at a
  different INK2R field, so the existing accounts misclassify). Extended
  the existing immaterial branch (3013/7813) into a three-way:
    immaterial            → 3013 / 7813
    building / land_imprv → 3971 / 7971
    other tangible        → 3973 / 7973
  Two new regression tests pin the building and land_improvement branches.

- DEFAULT_SCHABLONINTAKT_RATE was 0.03, based on SLR 2024-11-30 (1.96 %).
  For closings of inkomstår 2026 the rate is SLR 2025-11-30 (2.55 %) + 1 pe
  = 3.55 %. The wrong rate under-taxes the schablonintäkt, which feeds into
  bolagsskatt. Updated to 0.0355 and rewrote the doc comment to track both
  years so the next bump is obvious.

- Jämkning of input VAT for buildings / markanläggning disposed within the
  10-year jämkningsperiod (ML 9 kap 8–11 §§) is out of scope for this PR
  but should not be silently absent — added a KNOWN LIMITATION block to the
  disposeAsset docstring so any future UI surfacing disposal checks the
  10-year window and warns the user.

After this push the PR has 125 passing tests, all CI green, no merge
conflicts, and the only remaining bot complaints are repeat false
positives or Phase 5+ scope (RPC atomicity, full asset disposal UI,
K3 component depreciation).

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-05-16 15:55:27 +02:00
Jakob WennbergandClaude Opus 4.7 ba45b8f661 refactor(ui): editorial monochrome design refresh (#473)
* refactor(ui): editorial monochrome design refresh

Design-system layer only, no functional changes. Cascades app-wide
from 8 files in the primitives + tokens layer.

- Swap Fraunces → Hedvig Letters Serif for display typography
  (single-weight; drop font-medium from CardTitle and PageHeader)
- Token sweep: pure white background, warm beige secondary
  (40 11% 89%), achromatic primary, calibrated 45 5% 85% border,
  halved-opacity shadows
- Flatten Card: remove shadow, rounded-xl → rounded-lg, full-opacity
  border (was border-border/60)
- Flatten Button: remove shadow-sm, remove active:scale-[0.98],
  drop outline border morph, transition-all 300ms → transition-colors
  150ms
- Soften Dialog overlay (bg-black/80 → bg-black/40 with dark variant)
  and use halved --shadow-md token on DialogContent
- Sidebar: bg-card/90 → bg-background, full-opacity hairline border,
  warm-beige active state (bg-secondary), hover bg-secondary/60
- .hover-lift utility: replace translateY + box-shadow with flat
  background-color shift
- PageHeader title: text-2xl font-medium → text-3xl md:text-4xl
  (no font-medium)
- CLAUDE.md Brand & Aesthetic, Typography, and Forbidden Patterns
  sections rewritten to reflect new system

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

* refactor(ui): address PR review

- Remove dead .hover-lift utility (no callers; CLAUDE.md now bans
  hover-lift patterns, so leaving the class would contradict the docs)
- Bump Dialog overlay bg-black/40 → bg-black/50 — gives a more
  perceptible separation layer over the new pure-white background
  while keeping the lighter editorial feel

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-05-13 23:01:53 +02:00
Jakob WennbergandClaude Opus 4.7 ec06a2d04d fix(nav): render Beta/Dev/comingSoon badges for the main group (#447)
The Huvudmeny group has its own render loop separate from AR/AP/
Personal/Accounting; it only emitted icon + label and ignored the
betaBadge/devBadge/comingSoon flags. That's why Dokumentinkorg (group:
main, betaBadge: true) never showed the Beta tag even though Löner and
Anställda (group: personal) did. Adding the same badge precedence block
to both render paths (desktop sidebar + mobile drawer).

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 13:47:55 +02:00
Jakob WennbergandClaude Opus 4.7 17c67fece0 Inbox UX overhaul + cross-currency supplier-invoice fixes (#444)
* feat(kpi): expense mix and top suppliers charts

Replace the single monthly-trend chart with two additional compact visuals
on /kpi: expense composition donut (BAS class 4-7) and top suppliers bar
(supplier_invoices sum_sek over the fiscal period). KPIReport gains
expenseComposition and topSuppliers fields, computed from the trial
balance and supplier_invoices rows already fetched in the API.

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

* feat(nav): swap Deadlines sidebar slot for Dokumentinkorg

Sidebar main-menu slot now points to the invoice-inbox extension. The
/deadlines page stays accessible via dashboard widgets and direct links —
only the prominent nav entry changes. Most users open gnubok to act on
incoming documents, not to read tax deadlines.

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

* fix(supplier-invoices): cross-currency totals, FX residual, review SEK display

Five fixes around foreign-currency supplier invoices:

- Form layout: move Valuta / Växelkurs / Reverse charge from collapsed
  "Övrigt" into a visible row above the line-item table. Auto-fetch the
  Riksbanken rate when switching to a non-SEK currency; never clobber a
  user-typed rate; clear it when switching back to SEK.

- Form submit: reset() the form on successful submit so the
  useUnsavedChanges hook detaches its beforeunload listener before the
  router.push, killing the "Are you sure you want to leave?" prompt that
  fired during Turbopack-mediated navigations.

- BankTransactionPicker: drop the strict currency filter that hid every
  SEK transaction when the invoice was in EUR/USD. Cross-currency rows
  fall to the bottom with an "Annan valuta" hint instead of producing a
  meaningless numeric diff.

- match-supplier-invoice route: when the bank transaction currency
  differs from the invoice currency, compute the FX diff against the
  AP-booked SEK and pass it to createSupplierInvoicePaymentEntry so
  7960/3960 catches the residual instead of leaving a permanent stub on
  2440. Fix also covers the "EUR transaction paying a SEK invoice" case
  that the first iteration missed.

- Review dialog: buildJournalPreview now multiplies amounts by the
  exchange rate so the "Verifikation som bokförs" table shows the actual
  SEK numbers that hit the DB, not the EUR magnitudes labelled with no
  unit. Header gains an "(i SEK)" hint when foreign currency.

Test coverage for the FX residual path covers SEK-SEK (no diff),
SEK-into-EUR-invoice (loss), SEK-into-EUR-invoice (gain), foreign-tx-
into-SEK-invoice, and the no-rate fallback.

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

* feat(inbox): rate limits, multi-file UX, onboarding, retry, supplier autolink

Big workspace pass on /e/general/invoice-inbox. Highlights:

Backend
- New table inbox_rate_counters + RPC check_and_increment_inbox_quota.
  Postgres-backed (no Upstash dep) per-company limit: 30/min, 500/day.
  Applied at /upload, /inbound, and /items/:id/retry-extraction.
- POST /items/:id/retry-extraction — re-runs the deterministic extractor
  on a stored document when the previous attempt errored.
- POST /items/:id/match-supplier — links a freshly-created supplier
  back to the inbox item so the next action prefills correctly.
- POST /api/transactions/create-from-document — creates an uncategorized
  manual transaction from an inbox item for the "I have a receipt, no
  bank transaction" case. The user categorizes through the normal flow.
- /inbound caps email at 20 attachments/email; truncated count goes to
  processing_history as AttachmentsTruncated. Rate-limit drops emit
  RateLimitedDropped and return 200 so Resend doesn't retry.
- attach-document side effect: when the document came from an inbox
  item, the inbox row's matched_transaction_id is updated so the UI can
  flip it to "Kopplad till transaktion" without a round-trip.

New migration: re-introduces matched_transaction_id on
invoice_inbox_items as a plain FK (the AI metadata that the previous
migration stripped doesn't come back).

Workspace UI
- Onboarding card replaces the thin empty-state with a 3-step
  checkmark guide (Aktivera adress → Ladda upp → Matcha eller bokför).
  Auto-hides when all three steps are done; localStorage-backed dismiss.
  Beta badge + link to gnubok.se/priser.
- Responsive layout: 3-pane at lg, 2-pane at md, master-detail toggle
  on phone (list xor detail with a back button).
- Filter pills (Alla / Behöver åtgärd / Bearbetade / Fel) + search
  input above the list — client-side over the existing items list.
- Multi-file upload queue with "Laddar X av N…" progress counter on
  the button. Sequential to avoid hammering pdfjs. Selection stays put
  during a batch (only single-file drops auto-jump the detail pane).
- Bulk select + delete with sticky action bar. Items linked to a
  supplier invoice are skipped with a count toast.
- Retry button in the FieldsRail error branch.
- "Skapa transaktion från underlag" CTA in the match dialog when no
  unmatched bank transactions exist. Prefills date/amount/description
  from the extracted data; user picks the sign.
- "Skapa leverantör" inline CTA when the extractor caught a supplier
  name with no match against existing suppliers. POSTs /api/suppliers
  with the extracted fields, then auto-links via /items/:id/match-supplier.
- Matched-state CTA renamed to "Bokför transaktionen" with link to
  /transactions?highlight=<id> so the categorize panel auto-opens.

Tests
- lib/rate-limits/__tests__/inbox.test.ts — RPC wrapper happy/error/scope
- app/api/transactions/create-from-document/__tests__/route.test.ts —
  auth, validation, 404/409/200/500, inbox-link failure tolerated
- extensions/general/invoice-inbox/__tests__/retry-extraction.test.ts —
  auth, rate limit, 404, 409, 400 no-doc, success, extraction failure
- attach-document tests extend coverage to the new inbox-link side
  effect (both success and best-effort failure paths)
- inbound-webhook test mocks the rate-limit module so the queued-mock
  sequence in each existing test doesn't have to know about it

CLAUDE.md gains a row for lib/rate-limits/ so the new helper is
discoverable.

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

* feat(transactions): paperclip indicator and highlight-row param

Close the feedback loop after a user attaches a receipt to a transaction
from the inbox: the row in /transactions now shows a paperclip icon
when transaction.document_id is set, with a click handler that fetches
a signed download URL and opens the document in a new tab. Works for
both uncategorized and history views.

When the inbox sends a user to /transactions?highlight=<id>, the page
now scrolls that row into view and auto-opens the categorize panel if
the transaction is still uncategorized. Behind a double-rAF so the row
DOM exists when scrollIntoView fires.

QuickReviewDialog no longer prompts to upload underlag when the
transaction already has a doc attached (which it does after the inbox
match flow). Shows "Underlag bifogat — Visa" instead, opening the
existing doc in a new tab.

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

* fix(pr-444): address review feedback (Greptile + compliance bots)

Migration rules
- New migration 20260512092423: adds updated_at trigger on
  inbox_rate_counters (CLAUDE.md rule 2) and explicit USING (false) RLS
  policies for the four DML verbs to make the SECURITY DEFINER-only
  intent explicit (rule 1).
- New pg-real test inbox-rate-limit.pg.test.ts covering happy path,
  minute-cap rejection, day-cap rejection, per-company isolation, and
  the updated_at trigger firing. CLAUDE.md mandates *.pg.test.ts for
  every new RPC because mocks pass on broken PL/pgSQL.

Bugs
- Stale exchange rate on currency switch (Greptile P1) —
  userTouchedRateRef was scoped per session, not per currency. Switching
  EUR (with a hand-edited rate) → USD kept the EUR rate. Now tracks the
  last fetched currency in a ref and resets the touched flag on
  currency change while still honoring manual edits within a single
  currency.
- topSuppliersResult.error silently swallowed (Greptile P2) — failed
  queries used to render an empty chart matching the no-data state.
  Logged now.
- Currency from extracted_data not validated (GDPR Art.25(2), OWASP V4.5,
  Swedish compliance bot) — extracted PDF currency was inserted into
  transactions.currency without sanitisation. Allowlisted against the
  six supported ISO 4217 codes; coerce to SEK otherwise.
- Idempotency gap on create-from-document (OWASP V2.3) — two concurrent
  POSTs with the same inbox_item_id could each pass the
  matched_transaction_id IS NULL read and insert duplicate transactions.
  UPDATE now includes .is('matched_transaction_id', null) as an
  optimistic-lock release and returns 409 with an orphan-transaction
  rollback when the predicate doesn't match.
- FX residual on cash-method match path (Swedish compliance bot) —
  createSupplierInvoiceCashEntry has no exchange_rate_difference path,
  so a cross-currency match would silently leave a 1930 reconciliation
  gap. Added a guard that returns MATCH_SI_CASH_FX_UNSUPPORTED (400)
  before the JE is created. Users on cash method can switch to accrual
  or book the FX diff manually.

Design system
- gap-y-1.5 / gap-1.5 in KPIExpenseMixChart — replaced with gap-y-2 /
  gap-2 (CLAUDE.md design tokens; 2.5/1.5/5/hardcoded pixels are
  forbidden spacing values).

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

* chore(migrations): rename to match applied versions

The mcp__plugin_supabase_supabase__apply_migration tool stamps its own
timestamp when it applies a migration to the live project, so the
version recorded in supabase_migrations.schema_migrations differs from
my local generation-time filenames. Renaming the local files so a
production CD run sees the migrations as already-applied (matching
versions) instead of trying to re-apply them — which would fail for
the trigger/RLS migration (CREATE TRIGGER and CREATE POLICY don't
support IF NOT EXISTS).

Follows the pattern from d854efcd ("chore(migration): rename to match
applied version").

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

* fix(create-from-document): scope orphan rollback DELETE by company_id

Defence in depth on the inbox-link race rollback. newTx.id is a fresh
UUID from a company-scoped insert two statements above, so the existing
single-key DELETE is already safe, but adding .eq('company_id', companyId)
makes the cross-company invariant explicit on every write — addresses
the OWASP ASVS V2.3 finding from the compliance swarm on PR #444.

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

* feat(nav): mark Dokumentinkorg with Beta badge

Same signal we use for Löner and Anställda — the inbox flow (AI
extraction, supplier autolink, manual transaction creation) is in
end-to-end customer testing.

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-05-12 13:12:17 +02:00
Jakob WennbergandClaude Opus 4.7 d0fbc2b616 refactor(ui): app-wide UI/UX consistency pass (#436)
* refactor(ui): app-wide UI/UX consistency pass

Net: +1,159 / −1,373 LOC across 77 files. No new features, no behavior
changes. Locks in a uniform design system across every dashboard surface.

What changed:

- **Foundation**: sidebar width 232→256px (md:w-64), spacing scale locked
  (Tailwind 1/2/3/4/6/8/10/12; 2.5/5 forbidden), card padding p-6 default
  (p-4 for compact metric cards), space-y-8 between page sections.

- **Tables unified**: all 33 thead blocks now share the Resultatrapport
  pattern via shadcn Table primitive (text-[11px] font-medium uppercase
  tracking-wider text-muted-foreground). Hand-rolled <table> instances
  converted where they were data tables; form/edit grids kept distinct.

- **Status badges unified**: every status indicator routes through
  shadcn <Badge variant>. Eliminated raw Tailwind colors
  (bg-amber-100, bg-emerald-500/10, bg-blue-100, bg-purple-100, etc.)
  in favor of the gnubok semantic palette (success=sage, warning=ochre,
  destructive=terracotta).

- **Empty states unified**: list pages migrated from hand-rolled
  "flex flex-col items-center py-12" divs to the EmptyState primitive.

- **Loading skeletons unified**: hand-rolled bg-muted rounded animate-pulse
  divs replaced with shadcn <Skeleton> across 15 files.

- **Touch targets**: 6 back-buttons + edit-pencil + inbox delete bumped
  from 24/32/36px to shadcn's 40px icon default. Added aria-labels on
  9 icon-only navigation buttons.

- **Date formatting**: formatDate() for accounting data (ISO yyyy-MM-dd,
  table-friendly) vs formatDateLong() for metadata (Swedish long form).
  Raw {x.invoice_date} renderings routed through formatDate() in 18 sites.

- **Toast titles**: eliminated 33 generic "Fel" titles. Each toast title
  now carries the action ("Kunde inte skapa lönekörning" etc.) with
  description carrying the error detail.

- **Page-level cleanups**:
  - Dashboard: dropped greeting hero + Snabbåtgärder/Att hantera nav
    duplicates + Visa detaljer collapsible.
  - Reports: 5-col mega-menu replaced with left-rail layout
    (new ReportsNav component).
  - Bookkeeping: fixed layout jump between Verifikationer/Ny verifikation
    tabs (moved FiscalYearSelector inside journal tab).
  - Bookkeeping: added voucher sort (A1 first / latest first) alongside
    existing date sort. Required matching API param sort_by.
  - KPI page: FiscalYearSelector instead of raw <select>; InfoTooltip
    instead of inline info-button toggle; bigger numbers.
  - Salary section: enum values translated to Swedish labels, mobile
    table collapses to Anställd+Netto on <md, KPI typography aligned
    with dashboard.
  - Invoice forms: styled RequiredMark + aria-required, tabular-nums
    on amount inputs.

- **CLAUDE.md**: new "Design System Tokens" subsection documents the
  locked spacing scale, primitives table, typography rules, date helpers,
  and forbidden patterns so future contributors don't drift.

Tests: 2,906 passing (unchanged). Lint: unchanged from main baseline.

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

* fix: address PR review feedback (Greptile + compliance bot)

- **formatDate / formatDateLong timezone fix**: switch from new Date() to
  parseISO. Bare yyyy-MM-dd strings are now parsed as local midnight rather
  than UTC midnight, eliminating the off-by-one display in west-of-UTC
  timezones flagged by Greptile.

- **DashboardContentProps cleanup**: removed unused firstName and settings
  fields from the interface, and the corresponding fetch (profiles table)
  + computation in app/(dashboard)/page.tsx. The greeting was dropped in
  the dashboard cleanup; these props were dead weight.

- **Voucher sort behavior documented**: extended the comment in the journal
  entries API route to explain why voucher sort intentionally uses strict
  fiscal_period_id filtering (BFL 5 kap 6–7 §§ — voucher numbers are
  series-scoped within a fiscal year). The row-count delta between date
  sort and voucher sort is now a documented design choice.

- **delete_last_voucher migration + draft-delete test included**: the UI
  already shipped the "Radera utkast" path in the previous commit; this
  pulls in the backing RPC migration that allows draft deletes (with the
  full safety logic — drafts skip series/period checks since they have
  voucher_number=0, posted entries go through the existing unchanged
  path). This was originally meant for a separate PR but the UI shipped
  half the feature without it.

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

* chore(migration): rename to match applied version

The function delete_last_voucher is already applied to the production DB
under version 20260509103736 (verified via pg_get_functiondef — exact
byte-for-byte match to file content). The previous file timestamp
20260509120000 would cause a fresh `supabase db push` to attempt re-applying
under a different version row. Renaming the file aligns local tracking
with what the database actually has.

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

* fix: address compliance bot findings (payroll label + VAT visibility)

- sick_karens label: drop "(första sjukdagen)" qualifier. Per sjuklönelagen
  6 §, karensavdrag is a single calculated amount (20% of one week's
  sjuklön) deducted from the first sick day's pay — not bounded to the
  first day. The qualifier could mislead users when the first sick day
  and return-to-work span a weekend. Swedish-payroll bot recommendation.

- Omvänd skattskyldighet badge: variant outline → warning. The reverse-
  charge indicator is compliance-critical (ML 16 kap) — missing it leads
  to incorrect input VAT deduction. Outline was too subtle; warning's
  ochre fill matches its semantic weight.

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-05-11 16:34:07 +02:00
Mattsson af11460f53 fix(dashboard): enable salary features with "Beta" badge for testing (#424) 2026-05-09 12:48:55 +02:00
Mattsson 81e9dd224e Add/csv import options (#420)
* feat(import): add customer and supplier parsing functionality

- Implemented customer file parsing in `lib/import/customers/parser.ts` with support for Excel and CSV formats.
- Created types for detected customer columns and parsed customer rows in `lib/import/customers/types.ts`.
- Added tests for customer classification logic in `lib/import/shared/__tests__/classify.test.ts`.
- Developed classification functions for customers and suppliers in `lib/import/shared/classify.ts`.
- Introduced shared column utility functions in `lib/import/shared/column-utils.ts`.
- Implemented supplier file parsing in `lib/import/suppliers/parser.ts` with validation for various fields.
- Created types for detected supplier columns and parsed supplier rows in `lib/import/suppliers/types.ts`.
- Added tests for supplier column detection and parsing in `lib/import/suppliers/__tests__/column-detector.test.ts` and `lib/import/suppliers/__tests__/parser.test.ts`.

* fix(labels): update 'Svenskt företag' to 'Svenskt företag eller organisation' for clarity

* feat(import): refactor encoding handling for Swedish files and add tests for character preservation

* feat(recapt): implement clearRecaptIdentity function and integrate into logout flow

* feat(bookkeeping): implement copy functionality and next voucher sequence retrieval

* feat(import): enhance customer and supplier import functionality with normalization and event handling
2026-05-08 15:42:06 +02:00
Jakob WennbergandClaude Opus 4.7 15d4f429f3 feat: consolidate /expenses into /supplier-invoices, dual-channel support feedback, compliance skills (#417)
* feat(supplier-invoices): consolidate /expenses into /supplier-invoices and add bank matching

Collapse the duplicate AP entry points by redirecting /expenses, /expenses/new,
and /expenses/[id] into the canonical /supplier-invoices routes, and absorb the
expense-entry flow into /supplier-invoices/new. Add a BankTransactionPicker so
a supplier invoice can be registered and matched to an existing outgoing bank
transaction in one step. Extend the transaction → invoice match dialog to
handle supplier invoices alongside customer invoices, including the new
/match-supplier-invoice endpoint and Swedish copy variants. Update the sidebar
to point Leverantörsfakturor at /supplier-invoices and hide the legacy entry.

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

* feat(support): send feedback to both Recapt and email channels

Previously submitFeedback used Recapt when present and only fell back to email
on failure, so feedback captured by the SDK never reached the support inbox.
Always POST to /api/support/contact in parallel with the Recapt call and
return the list of channels that succeeded; feedback is considered delivered
if either channel succeeds.

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

* chore(skills): add compliance skill references for GDPR, ISO 27001, OSS, OWASP ASVS, SOC 2

Add reference material for five compliance domains alongside the existing
.claude/skills/ set so future audits and CI gating work has a documented
mapping to controls, violation patterns, tool orchestration, and cross-framework
crosswalks.

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

* fix(supplier-invoices): address review issues in register-and-match flow

- BankTransactionPicker: move createClient() inside the effect and drop the
  supabase client from the deps array. Calling createClient() in the component
  body produced a fresh reference on every render, which combined with the
  setIsLoading(true) inside the effect to fire an infinite re-fetch loop while
  the dialog was open.
- /supplier-invoices/new: replace the submitMode useState with a useRef.
  setSubmitMode() in the button onClick and the read in the form onSubmit run
  in the same React event batch, so onSubmit always saw the previous render's
  value and the bank picker never opened on first use.
- /supplier-invoices/new: route AB companies through the existing review
  dialog before booking the register-and-match flow. handlePickTransaction now
  stores the picked transaction and opens the review dialog for AB; on
  confirm, handleConfirm posts the create and then matches the stored
  transaction. EF retains the one-step create+approve+match path.

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

* fix(transactions): populate potential_supplier_invoice so the match flow is reachable

fetchTransactions and loadMoreTransactions previously joined potential_invoice
from the invoices table but never looked up potential_supplier_invoice, so
TransactionInboxCard's hasSupplierInvoiceMatch check was always false and
handleConfirmInvoiceMatch's supplier branch was unreachable. Mirror the
existing customer-invoice pipeline: collect potential_supplier_invoice_id
values, batch-fetch the matching supplier_invoices (with their supplier),
build a map, and spread the result onto each TransactionWithInvoice in
parallel with the customer-invoice fetch.

Update uncategorizedTransactions sort and transactionsWithMatches filter to
also recognize supplier-invoice candidates so they bubble to the top of the
inbox and aren't excluded from match-driven views.

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-05-07 18:13:43 +02:00
Mattsson fa7d4075cf Supp/invoice bfl errors (#390)
* feat(accounting): update accounting method validation and messaging for aktiebolag and enskild firma

* Remove AI subsystem and related code

- Deleted AI proposals and requests persistence logic from `lib/ai/proposals/persist.ts`.
- Removed re-validation logic for proposals in `lib/ai/proposals/re-validate.ts`.
- Cleaned up schemas related to AI flows in `lib/api/schemas.ts`.
- Removed AI-related fields from bookkeeping engine in `lib/bookkeeping/engine.ts`.
- Eliminated AI event types from `lib/events/types.ts`.
- Updated tests to reflect the removal of AI-related functionality in `lib/extensions/__tests__/sectors.test.ts`.
- Adjusted initialization logic in `lib/init.ts` to exclude AI proposal handler registration.
- Cleaned up transaction ingestion logic in `lib/transactions/ingest.ts` to remove AI flow checks.
- Updated helper functions in `tests/helpers.ts` to remove AI-related settings.
- Removed AI-related types and interfaces from `types/index.ts`.
- Added migration script to drop AI-related tables and settings from the database.

* fix(migrations): ensure foreign key constraint is dropped before removing AI tables

* feat(invoice-inbox): implement deterministic invoice field extraction and inbox provisioning

- Added `extract-invoice-fields.ts` for extracting fields from PDF invoices using regex and pdfjs-dist, replacing the previous AI classifier.
- Introduced `inbox-provisioning.ts` to manage company inbox addresses and rotation of inboxes using Supabase RPCs.
- Created `resend-inbound.ts` for handling inbound email events and attachments via the Resend API.
- Defined the extension manifest for the invoice inbox, specifying required environment variables and descriptions.
- Migrated database schema to remove AI-related columns and tighten the status enum in `invoice_inbox_items`.

* feat(invoice-inbox): remove AI-specific columns and tighten status enum

* fix(skattekonto): remove manual entry creation reference from transaction input

* fix(schemas): remove accounting method validation for aktiebolag in UpdateSettingsSchema
2026-05-05 09:53:37 +02:00
MattssonandClaude Opus 4.7 c86dbdc60d feat(branding): add hiddenNavHrefs to hide sidebar items via env var (#382)
Extends the branding service with a hiddenNavHrefs: string[] field so
forks can hide sidebar entries (e.g. /salary, /customers) without
patching DashboardNav.tsx. Configurable via NEXT_PUBLIC_BRANDING_HIDDEN_NAV
as a comma-separated list. Default is [] — vanilla gnubok unchanged.

Routes remain reachable; this is a nav-visibility switch only. Settings
sidebar is intentionally out of scope.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 22:53:34 +02:00
Mattsson cc41ae0f1d Supp/04 27 (#361)
* fix: update fiscal period validation to account for locked periods

* fix: restrict receipt alerts and visibility to development environment
2026-04-27 12:12:33 +02:00
MattssonandClaude Opus 4.7 1af977950b Ai/full autonomous flow (#359)
* Refactor bookkeeping error handling and introduce new error classes

- Introduced new error classes for better error categorization:
  - JournalEntryNotBalancedError
  - FiscalPeriodNotFoundError
  - EntryDateOutsideFiscalPeriodError
  - JournalEntryNotFoundError
  - CannotReverseNonPostedError
  - CannotCorrectNonPostedError
  - EntryAlreadyReversedError
  - CurrencyRevaluationAlreadyExistsError
  - InvalidMappingResultError
  - BookkeepingDatabaseError

- Updated existing functions in engine.ts and transaction-entries.ts to throw specific errors instead of generic ones.
- Enhanced error response handling in get-error-message.ts to provide localized messages for new error types.
- Added unit tests for new error classes and error handling functions to ensure correctness and coverage.

* feat(ai): implement AI proposal application and persistence

- Add apply.ts to handle the application of AI proposals, including match and booking steps.
- Introduce persist.ts for inserting and managing AI requests and proposals, ensuring unique constraints.
- Create re-validate.ts for validating proposals before acceptance, checking for stale conditions.
- Define database migrations for ai_requests and ai_proposals tables, including constraints and indexes.
- Enhance journal_entries with AI provenance tracking, linking entries to AI proposals.
- Update categorization_templates to distinguish AI-corrected templates.
- Add company settings for toggling AI flow and managing backfill processes.
- Extend processing_history to include AI-related events for better tracking.

* feat: add uncategorized transactions API and UI for transaction selection

- Implemented a new API endpoint for fetching uncategorized transactions with pagination and filtering options.
- Created ChangeTransactionDialog component for selecting alternative transactions based on AI proposals.
- Developed ReceiptDetailDialog to display detailed information about receipts, including upload functionality.
- Added TransactionDetailDialog for viewing transaction details with links to the transaction list.
- Introduced receipt quality assessment logic to evaluate extracted receipt data.
- Implemented feature flagging for the AI bookkeeping agent to control availability in different environments.

* feat: add manual receipt extraction dialog and integrate AWS Textract for expense analysis

- Added ManualExtractDialog component for user input when AI fails to extract receipt data.
- Implemented ReceiptsList component to manage and display uploaded receipts, including upload and rescan functionalities.
- Introduced Textract integration for analyzing expenses, extracting fields like total, vendor, and date.
- Updated package.json to include @aws-sdk/client-textract dependency.

* fix(ai): handle livsmedel VAT transition (12% → 6%) in booking prompt and re-validate guard

Add date-aware guidance to BOOKING_SYSTEM_PROMPT for the temporary livsmedel
VAT cut (Prop. 2025/26:55, 2026-04-01 to 2027-12-31), with restaurang/servering
carve-out at 12%. Add a re-validate safety net that rejects clearly-stale rate
labels for grocery-chain merchants relative to the entry date.

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-27 10:32:15 +02:00
MattssonandClaude Opus 4.7 11621bb79f Feat/skv integration full (#284)
* feat: add script to import Skatteverket monthly tax tables as fallback TypeScript module

- Implemented a new script `import-tax-tables.ts` to parse fixed-width TXT tax tables from Skatteverket (SKV 434).
- The script generates a TypeScript module for emergency fallback when the Skatteverket open-data API is unavailable.
- Supports command-line argument for specifying the year and handles parsing of B-rows only.
- Outputs a structured TypeScript file containing tax data for specified years.

* feat: gate salary module behind dev-only flag

Temporarily disable the Lön module in production while the feature is
being completed. Sidebar entries ("Löner", "Anställda") still render but
are not clickable and show a "Kommer snart" badge. Middleware redirects
/salary* to / and returns 404 on /api/salary/* so the feature can't be
reached by direct URL. All gates check NODE_ENV === 'development' so
local dev keeps full access for continued development.

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

* feat: refactor bank file import wizard to streamline column mapping and enhance CSV handling

* fix: bump migration timestamp to avoid collision with logos_bucket

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

* feat: enhance AGI generation and salary entry calculations with improved status checks and error handling

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-20 21:03:23 +02:00
Jakob WennbergandClaude Opus 4.6 4fbfadb2b7 feat: invoice inbox extension — conversion, workspace UI, Gmail UX (#255)
* feat: invoice inbox extension — conversion, workspace UI, Gmail UX

Complete the invoice-inbox extension with full end-to-end flow:

- Add POST /items/:id/convert route to create supplier invoices from
  classified inbox items, with accrual journal entry and document linking
- Add PATCH /items/:id/reject route to dismiss non-relevant items
- Add workspace UI at /e/general/invoice-inbox with items table,
  status filtering, convert dialog, and match confirmation
- Add Gmail connection banner (connect/disconnect/status) in workspace
- Add one-click supplier creation from AI-extracted data
- Add transaction auto-matching with fuzzy name + currency-aware amount
- Add event emission (received, extracted, confirmed) on classification
- Redirect OAuth callback to workspace instead of /settings/banking
- Fix extension catch-all body clone for POST routes with path params
- Fix duplicate Löner nav entry from salary module merge
- Remove summary cards from expenses and supplier invoices pages
- Fix supplier-invoices/new amount input (valueAsNumber → Controller)

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

* fix: address Greptile review — company_id filters, currency guard, skipAuth clone

- Add company_id filter to reject route update (defense in depth)
- Add company_id filter to document_attachments journal entry link
- Guard sekMatch with tx.currency === 'SEK' to prevent false matches
- Clone request in skipAuth branch for consistency with auth branch

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-04-16 13:59:49 +02:00
Mattssonandgreptile-apps[bot] bb0db7a588 Salary module improvements (#250)
* feat: implement salary module with personnummer encryption, salary entries, tax tables, and AGI tracking

- Added personnummer encryption and decryption functions for secure storage.
- Created salary entries handling for journal entries including gross salary, tax withholding, and employer contributions.
- Implemented tax table lookup functionality for calculating tax amounts based on monthly income.
- Developed SQL migration for salary module including tables for payroll configuration, tax rates, employees, salary runs, salary run employees, salary line items, and AGI declarations.
- Established row-level security policies for all new tables to ensure company-scoped access.

* feat: add salary calculation modules for 2026

- Implemented engångsskatt calculation for one-time payments with tax brackets.
- Added löneväxling functionality for salary sacrifice to pension, including employer savings and warnings.
- Created pain.001 generator for salary batch payments in compliance with Swedish banking standards.
- Developed PDF template for payslips, including detailed breakdowns and employer costs.
- Generated seed data for Swedish tax tables for 2026, including SQL insert statements.
- Implemented traktamente calculations for per diem and mileage allowances, adhering to Skatteverket regulations.
- Added seed script for populating tax tables in the database.

* feat: Update meal reduction percentages in traktamente calculation

fix: Remove obsolete seed script for 2026 tax tables

feat: Extend SalaryRunStatus type to include 'corrected' status

feat: Implement KU10 XML generation endpoint for annual employee income statements

feat: Add endpoint for creating corrections to booked salary runs

feat: Implement endpoint for sending payslip PDFs to employees

feat: Create KU10 XML generator for annual reporting

feat: Add salary transaction matcher for auto-linking bank transactions to salary entries

chore: Add database migration for salary correction support

* feat: replace select elements with custom Select component for employment and salary types

* feat: enhance salary calculations with pension entry and avgifter category support

* feat: enhance employee management with salary type, tax status, and validation improvements

* feat: Implement AGI submission flow to Skatteverket

- Added AGI submission route to handle the submission process.
- Created AGI client for interacting with Skatteverket's API.
- Introduced AGI mappers to convert salary run data into the required AGI JSON payload format.
- Enhanced API client to support custom base URLs for Skatteverket API requests.
- Added types for AGI submission payload and validation results.
- Implemented tests for AGI mappers to ensure correct payload structure and data handling.

* feat: enhance salary module with Skatteverket integration and update dashboard navigation

* Update app/api/salary/runs/[id]/agi/submit/route.ts

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>

* Update app/api/salary/runs/[id]/approve/route.ts

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>

* feat: integrate write permission check and remove Skatteverket extension

---------

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
2026-04-15 20:55:29 +02:00
Mattsson 04dbb31d7e Salary module (#245)
* feat: implement salary module with personnummer encryption, salary entries, tax tables, and AGI tracking

- Added personnummer encryption and decryption functions for secure storage.
- Created salary entries handling for journal entries including gross salary, tax withholding, and employer contributions.
- Implemented tax table lookup functionality for calculating tax amounts based on monthly income.
- Developed SQL migration for salary module including tables for payroll configuration, tax rates, employees, salary runs, salary run employees, salary line items, and AGI declarations.
- Established row-level security policies for all new tables to ensure company-scoped access.

* feat: add salary calculation modules for 2026

- Implemented engångsskatt calculation for one-time payments with tax brackets.
- Added löneväxling functionality for salary sacrifice to pension, including employer savings and warnings.
- Created pain.001 generator for salary batch payments in compliance with Swedish banking standards.
- Developed PDF template for payslips, including detailed breakdowns and employer costs.
- Generated seed data for Swedish tax tables for 2026, including SQL insert statements.
- Implemented traktamente calculations for per diem and mileage allowances, adhering to Skatteverket regulations.
- Added seed script for populating tax tables in the database.

* feat: Update meal reduction percentages in traktamente calculation

fix: Remove obsolete seed script for 2026 tax tables

feat: Extend SalaryRunStatus type to include 'corrected' status

feat: Implement KU10 XML generation endpoint for annual employee income statements

feat: Add endpoint for creating corrections to booked salary runs

feat: Implement endpoint for sending payslip PDFs to employees

feat: Create KU10 XML generator for annual reporting

feat: Add salary transaction matcher for auto-linking bank transactions to salary entries

chore: Add database migration for salary correction support

* feat: replace select elements with custom Select component for employment and salary types

* feat: enhance salary calculations with pension entry and avgifter category support
2026-04-15 11:17:39 +02:00
Jakob WennbergandClaude Opus 4.6 7bf7565852 feat: delete last voucher, notes field, schema cache fix (#230)
* feat: delete last voucher, notes field, schema cache fix

Address three customer feedback items from William (wigu.se):

1. Delete last voucher per series (Fortnox model):
   - New `delete_last_voucher` RPC with full safety checks (last-in-series,
     open period, no references, owner/admin only)
   - Session variable bypass for immutability/retention/line triggers
   - Full JSONB audit trail (BFNAR 2013:2 behandlingshistorik)
   - DELETE endpoint + UI with confirmation dialogs
   - Storno restoration when deleting a reversal entry

2. Notes/comment field on vouchers:
   - `notes` column on journal_entries (always-editable internal metadata)
   - Immutability trigger updated to allow notes-only updates on posted entries
   - PATCH endpoint, inline-edit UI on detail page, form textarea

3. Schema cache fix:
   - NOTIFY pgrst applied to production (immediate fix)
   - Retroactive migration + CLAUDE.md migration rule added

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

* fix: address Greptile review — tighten trigger, lock voucher sequence

P1: The notes-only exception in enforce_journal_entry_immutability was
too broad — it only checked 7 verifikation fields, allowing silent
mutation of correction_of_id, reverses_id, reversed_by_id, committed_at,
and user_id on posted entries. Now guards all metadata fields; only
notes and updated_at may differ.

P2: Lock voucher_sequences row FOR UPDATE before the MAX(voucher_number)
check in delete_last_voucher to serialise against concurrent
commit_journal_entry calls, preventing voucher number gaps.

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-04-13 16:12:03 +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
MattssonandClaude Opus 4.6 0dd1f5ebc1 feat: multi-tenant company refactor (GNU-19) (#153)
* feat: multi-tenant company refactor (GNU-19)

Introduce companies table, company_members, and user_preferences to
support multiple companies per user. All data scoping changes from
user_id to company_id across the entire codebase.

Key changes:
- Database migration: new tables, company_id on 40+ tables, backfill,
  RLS rewrite from user_id to company-member-based, updated RPCs
- Types: Company, CompanyMember, CompanyRole, UserPreferences types;
  company_id added to all entity interfaces; companyId on all events
- Engine: all 7 core functions take companyId; storno, period, year-end
  services updated; 16 report generators updated
- Middleware: company context resolution (cookie → prefs → first company)
- API routes: ~120 routes updated with requireCompanyId()
- Frontend: CompanyProvider context, layout/dashboard/onboarding updated
- Extensions: context factory, 9 extensions, all lib files updated
- Tests: 1880 tests passing, all helpers updated with company_id defaults

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

* feat: add database migrations for multi-tenant company and team system (GNU-19)

Adds company_invitations, company creation RPC, team_members, account
deletion RPC, and teams table refactor migrations. Updates base
multi-tenant migration with cascading FKs and onboarding_step column.

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

* feat: add team types and update core infrastructure for multi-tenancy (GNU-19)

Adds TeamRole, MemberSource, and Team types. Refactors Supabase service
client to be stateless, updates middleware for team-aware routing, extends
CompanyContext with team/role fields, and updates extension service types
to accept companyId.

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

* refactor: thread company_id through business logic functions (GNU-19)

Replaces user_id scoping with company_id across all lib modules:
bookkeeping, documents, transactions, invoices, reconciliation, tax,
deadlines, and import. Updates corresponding tests.

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

* refactor: thread company_id through API routes and extensions (GNU-19)

Updates all existing API routes to extract and pass companyId. Updates
enable-banking and arcim-migration extensions for company-scoped
transaction ingestion and sync.

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

* feat: add company and team management API routes (GNU-19)

Adds CRUD endpoints for company members, company invitations, team
members, and team invitations. Includes invite token utilities, email
templates, and company switch server action.

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

* feat: add team/company UI components, pages, and dashboard updates (GNU-19)

Adds CompanySwitcher, ConsultantEmptyState, Step0RoleChoice, company
members and team management panels. Updates dashboard layout for
team-aware routing, onboarding for multi-step role choice, and auth
callback for team invite acceptance. Ignores supabase/.branches/.

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

* fix: add null guards for company in import page (GNU-19)

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

* fix: move appUrl declaration to outer scope in invite route (GNU-19)

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

* fix: add optional chaining for company.name in members section (GNU-19)

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

* fix: add optional chaining for second company.name in members section (GNU-19)

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

* fix: add null guards for company in extension components (GNU-19)

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

* fix: pass companyId to executeSIEImport in arcim-migration extension (GNU-19)

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

* fix: update tests to use companyId instead of userId and improve type handling

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 16:41:52 +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 f3ae3cd361 feat: event log, pending operations, and MCP staging (#135)
* 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>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 10:07:36 +01:00
Jakob WennbergandClaude Opus 4.6 4d4a9a40c9 feat: AR/AP navigation separation and bookkeeping improvements (#109)
* feat: separate AR/AP/accounting into distinct nav groups (#92)

Split the flat "Finans" sidebar group into three visually distinct
sections — Försäljning (AR), Inköp (AP), and Redovisning — so users
coming from Fortnox immediately find customer invoicing and supplier
invoices as top-level concepts.

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

* feat: journal entry detail view, correction chain, and account name display

- Add journal entry detail page at /bookkeeping/[id] with full entry view
- Add correction chain API and component showing storno relationships
- Add JournalEntryStatusBadge component for entry status display
- Show debit/credit account names in template picker and review dialogs
- Expand client-side BAS account name mapping with additional accounts
- Show account codes on transaction inbox suggestion buttons

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

* fix: address review feedback — N+1 query, duplicate name, nav dedup

- Batch reverse-lookup into single query per BFS iteration (was N+1)
- Differentiate account 2393 from 2893 in display names
- Extract shared loop for desktop/mobile nav group rendering

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-23 20:01:07 +01:00
Jakob WennbergandClaude Opus 4.6 8cce21983f fix: trial balance 1000-row limit and rename /nyckeltal to /kpi (#79)
* fix: trial balance silently truncated at 1000 entries, rename /nyckeltal to /kpi

Trial balance bug:
- The old implementation fetched journal entry IDs (capped at 1000 by
  Supabase default limit), then queried lines via .in(entryIds) which
  also hit URL length limits with large arrays of UUIDs.
- SIE imports create thousands of entries → KPIs showed zero.
- Replaced with a single joined query (journal_entry_lines → journal_entries)
  using fetchAllRows() pagination. No row limit, no URL length issue.
- Removed the non-existent generate_trial_balance RPC call.

Page rename:
- /nyckeltal → /kpi (CLAUDE.md: all code in English)
- Nav label stays "Nyckeltal" (user-facing Swedish UI)

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

* fix: select filtered columns in joined query, rename component, add redirect

- Select user_id/fiscal_period_id/status from journal_entries!inner()
  so PostgREST applies embedded filters reliably (defense in depth)
- Rename NyckeltalPage → KpiPage per English code convention
- Add permanent /nyckeltal → /kpi redirect for existing bookmarks

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-21 17:21:34 +01:00
Jakob WennbergandClaude Opus 4.6 5d66dd6bfc feat: MCP server, API keys, OAuth, and KPI dashboard (#72)
* fix: prevent Chrome auto-translate from crashing React during onboarding

Chrome auto-translate modifies DOM text nodes when it detects a Swedish
page (lang="sv") in a browser set to English. React does not expect
external DOM mutations and throws, crashing the entire component tree
into global-error.tsx on every step transition.

Add translate="no" and <meta name="google" content="notranslate"> to
suppress browser translation. Also fix timezone-unsafe date parsing in
fiscal period validation (new Date("YYYY-MM-DD") + getDate() returns
local-timezone values, shifting dates by -1 day in Western timezones).

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

* fix: add notranslate meta tag to global-error.tsx for consistency

Per review feedback — global-error.tsx renders its own <html> document,
so it needs the same <meta name="google" content="notranslate"> tag as
layout.tsx to fully suppress Chrome translation on error pages.

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

* feat: add MCP server extension with OAuth, API keys, and KPI dashboard

Let users do bookkeeping through Claude Desktop, Claude Code, or any
MCP-compatible client. "Show my uncategorized transactions." "Book that
as office supplies." "Invoice Acme for 15,000 kr."

MCP server (extension):
- 10 tools: transactions, categorization, customers, invoices,
  trial balance, VAT report, KPI report, income statement
- JSON-RPC 2.0 protocol (no SDK dependency, works in serverless)
- Tool annotations, pagination, input validation per MCP best practices
- Same engine as web UI (VAT rules, exchange rates, event emission)

API key infrastructure (core):
- api_keys table with RLS, rate limiting (100 RPM), scopes column
- Atomic rate limit via DB RPC (validate_and_increment_api_key)
- Key management API routes + settings UI panel

OAuth 2.1 for Claude Desktop connectors:
- .well-known/oauth-protected-resource + oauth-authorization-server
- Authorization endpoint with consent page
- Token endpoint with PKCE verification
- Stateless encrypted auth codes (AES-256-GCM, no DB storage)
- Dynamic client registration

KPI dashboard:
- /nyckeltal page with hero cards, operational grid, trend chart
- GET /api/reports/kpi endpoint
- Gross margin, cash position, expense ratio, avg payment days,
  VAT liability, revenue/expense trend

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

* fix: address OAuth security vulnerabilities from code review

Critical fixes:
- Auth code replay: Track used codes in oauth_used_codes table with
  unique constraint. Codes are single-use per OAuth 2.1 §4.1.2.
- Open redirect: Validate redirect_uri against hardcoded allowlist
  of known Claude callback URLs + localhost for dev.

P1 fixes:
- Move API key creation from /authorize to /token endpoint. Keys are
  only created after PKCE verification, preventing orphaned keys on
  abandoned OAuth flows.
- Add ensureInitialized() to MCP server so event handlers load and
  transaction.categorized events reach extensions.

P2 fixes:
- Remove 'plain' from PKCE methods — only S256 is advertised and
  accepted.
- Fix extension count in sectors test (10 → 11 for mcp-server).

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

* fix: remove duplicate ensureInitialized() that caused circular import

The extension router (ext/[...path]/route.ts) already calls
ensureInitialized() before dispatching to handlers. The duplicate
call in server.ts created a circular import that Turbopack couldn't
resolve, breaking the Vercel build.

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-21 12:57:14 +01:00
Jakob WennbergandClaude Opus 4.6 e18b8dc789 feat: BFL-compliant descriptions, cancelled status, and TIC company lookup (#57)
* fix: include reversed entries in all reports (general ledger, trial balance, VAT, SIE, NE, INK2)

Reversed entries (storno) must appear alongside their original posted entries
in reports for a complete audit trail. Previously, filtering by status='posted'
excluded them, causing discrepancies when corrections had been made.

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

* feat: semi-manual invoice payment booking with editable journal lines

When marking an invoice as paid, users now see a dialog where they can:
- Choose which bank/cash account the payment goes to (1910, 1920, 1930, etc.)
- Review and edit the proposed journal entry lines before committing
- The happy path remains fast — lines are pre-filled correctly

Implementation:
- Pure proposePaymentLines() function for line computation (accrual + cash)
- PaymentBookingDialog with AccountCombobox, balance validation, date picker
- API accepts optional custom lines, falls back to auto-generation without them
- 18 tests (8 unit + 10 API) all passing

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

* fix: address Greptile review — validation fallback, balance check, error handling

- P1: Return 400 on invalid body instead of silently falling back to
  auto-generated lines (split JSON parse from schema validation)
- P1: Add server-side balance check for custom lines before committing
  (debit must equal credit, totalDebit > 0)
- P2: Wrap PaymentBookingDialog init() in try/catch with toast on
  failure and auto-close instead of silent empty state
- Add 2 new tests: unbalanced lines → 400, invalid schema → 400

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

* fix: OAuth callback redirect for local dev and timeout resilience

- Pass redirectUri dynamically from NEXT_PUBLIC_APP_URL so OAuth
  callbacks work on localhost (not just production)
- Encode consentId/provider in OAuth state (base64url JSON) so the
  callback doesn't depend on session storage
- Add skipAuth flag to extension API routes for OAuth callbacks
  (external provider redirects have no user session cookie)
- Wrap AbortError in descriptive timeout messages in arcim-client
- Make preview endpoint resilient to partial failures (company info
  and SIE fetch are individually non-blocking)
- Simplify login page (remove unused magic link auth mode)

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

* fix: create journal entry before marking invoice as paid

Move journal entry creation before the invoice status update so that
if accounting fails, the invoice is not permanently marked paid without
a corresponding entry. Previously the error was silently swallowed.

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

* fix: update mark-paid tests for journal-first ordering

Reorder mock queue to match new flow (settings before update), update
failure test to expect 500 instead of silent success, add try-catch
with proper error response in route handler.

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

* feat: add reverse charge VAT (ruta 20-32) and improve mobile UX across dashboard

Add full reverse charge (omvänd skattskyldighet) support to the VAT declaration:
- Map accounts 2614/2624/2634 to ruta 30/31/32 for self-assessed output VAT
- Calculate purchase bases (ruta 20-24) from supplier invoices by supplier type
- Include ruta 30-32 in ruta 49 formula and totalOutputVat summary
- Display reverse charge section in reports UI and composition chart
- Add comprehensive test coverage for all reverse charge scenarios

Improve mobile UX across the app:
- Convert nav drawer to bottom sheet with drag handle and safe area padding
- Add mobile card layout for PaymentBookingDialog journal lines
- Replace settings tab pills with dropdown selector on mobile
- Make wizard step indicators responsive (collapsed on mobile)
- Ensure all dialog footers stack buttons full-width on mobile
- Add 44px minimum touch targets throughout
- Make onboarding buttons full-width on mobile

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

* fix: address Greptile review — indentation, query efficiency, tab dedup

- Fix misleading try-block indentation in mark-paid route
- Filter reversed entries at DB level (.eq('status', 'posted')) instead
  of fetching then discarding in memory
- Extract shared settingsTabs array so mobile Select and desktop
  TabsList stay in sync automatically

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

* feat: add resilience fallbacks, Arcim retry logic, and client tests

Add FallbackPrompt component and integrate it across banking and migration
error states so users always have a manual import escape hatch. Add retry
with exponential backoff to Arcim API client for transient failures (429,
502, 503, 504) and timeouts. Expand import page deep-linking with ?mode=
parameter. Add persistent error banner on settings page for bank connection
failures. Include 18 new tests for the Arcim client covering retry, backoff,
pagination, timeout, env validation, and singleton resource unwrapping.

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

* fix: address Greptile review — setActiveTab, test cleanup, redundant clearTimeout

- Add missing setActiveTab('banking') when handling bank_error query
  param so the error banner is actually visible (P1)
- Guard env-var cleanup with try/finally in arcim-client tests to
  prevent state leakage on assertion failure (P2)
- Only mock retry-range setTimeout delays in backoff test, letting
  AbortController timers pass through real setTimeout (P2)
- Remove redundant clearTimeout in catch block — finally handles it (P2)

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

* feat: add BFL-compliant counterparty names to journal descriptions and cancelled entry status

Journal descriptions now include customer/supplier names for traceability
(e.g. "Kundfaktura 1001, Foretag AB"). Failed draft entries are marked as
'cancelled' instead of deleted, respecting immutability constraints.
Includes DB migration for the new journal_entries status value.

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

* fix: address Greptile review — Swedish typos, missing source type, trigger and reversal cleanup

- Fix Swedish spelling: leverantor → leverantör in all supplier description prefixes
- Add supplier_credit_note to supplierSourceTypes in VAT declaration so credit
  notes correctly reduce reverse-charge bases (ruta 20–24)
- Mark orphaned concurrent reversals as cancelled instead of attempting deletion
  that the immutability trigger blocks
- Allow posted → cancelled transition in trigger for orphaned reversal cleanup
- Restrict cancelled entry line trigger to DELETE-only (block INSERT/UPDATE)

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

* fix: use main's Step3TaxRegistration (onboarding restructured in PR #54)

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

* chore: retrigger Greptile review

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

* feat: add TIC company lookup extension, extension nav items, and legacy toggle fallback

Introduces the TIC (Bolagsuppgifter) extension for automatic company data lookup
via org number during onboarding. Adds dynamic extension nav items in the sidebar,
legacy general extension fallback for toggle checks, and company lookup type
definitions in core.

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

* fix: address Greptile review — restore push-notifications, filter nav by toggles, fix timeout error name

- Restore push-notifications to LEGACY_GENERAL_EXTENSIONS (was silently
  dropped when extracting the shared constant)
- Remove tic and arcim-migration from legacy defaults (new extensions
  should not default to enabled for all users)
- Filter getExtensionNavItems() against user's enabled extensions so
  disabled extensions don't appear in the sidebar
- Fix AbortSignal.timeout() error name check — Node.js throws
  TimeoutError, not AbortError

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

* fix: add all bundled extensions to legacy defaults (email, arcim-migration, tic)

Bundled extensions configured in extensions.config.json should default
to enabled. Adds email, arcim-migration, and tic alongside the
existing legacy defaults so they are accessible without explicit
toggle rows.

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-19 15:31:56 +01:00
Jakob WennbergandClaude Opus 4.6 ceec8c02a8 feat: reverse charge VAT (ruta 20-32) + mobile UX improvements (#50)
* fix: include reversed entries in all reports (general ledger, trial balance, VAT, SIE, NE, INK2)

Reversed entries (storno) must appear alongside their original posted entries
in reports for a complete audit trail. Previously, filtering by status='posted'
excluded them, causing discrepancies when corrections had been made.

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

* feat: semi-manual invoice payment booking with editable journal lines

When marking an invoice as paid, users now see a dialog where they can:
- Choose which bank/cash account the payment goes to (1910, 1920, 1930, etc.)
- Review and edit the proposed journal entry lines before committing
- The happy path remains fast — lines are pre-filled correctly

Implementation:
- Pure proposePaymentLines() function for line computation (accrual + cash)
- PaymentBookingDialog with AccountCombobox, balance validation, date picker
- API accepts optional custom lines, falls back to auto-generation without them
- 18 tests (8 unit + 10 API) all passing

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

* fix: address Greptile review — validation fallback, balance check, error handling

- P1: Return 400 on invalid body instead of silently falling back to
  auto-generated lines (split JSON parse from schema validation)
- P1: Add server-side balance check for custom lines before committing
  (debit must equal credit, totalDebit > 0)
- P2: Wrap PaymentBookingDialog init() in try/catch with toast on
  failure and auto-close instead of silent empty state
- Add 2 new tests: unbalanced lines → 400, invalid schema → 400

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

* fix: OAuth callback redirect for local dev and timeout resilience

- Pass redirectUri dynamically from NEXT_PUBLIC_APP_URL so OAuth
  callbacks work on localhost (not just production)
- Encode consentId/provider in OAuth state (base64url JSON) so the
  callback doesn't depend on session storage
- Add skipAuth flag to extension API routes for OAuth callbacks
  (external provider redirects have no user session cookie)
- Wrap AbortError in descriptive timeout messages in arcim-client
- Make preview endpoint resilient to partial failures (company info
  and SIE fetch are individually non-blocking)
- Simplify login page (remove unused magic link auth mode)

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

* fix: create journal entry before marking invoice as paid

Move journal entry creation before the invoice status update so that
if accounting fails, the invoice is not permanently marked paid without
a corresponding entry. Previously the error was silently swallowed.

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

* fix: update mark-paid tests for journal-first ordering

Reorder mock queue to match new flow (settings before update), update
failure test to expect 500 instead of silent success, add try-catch
with proper error response in route handler.

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

* feat: add reverse charge VAT (ruta 20-32) and improve mobile UX across dashboard

Add full reverse charge (omvänd skattskyldighet) support to the VAT declaration:
- Map accounts 2614/2624/2634 to ruta 30/31/32 for self-assessed output VAT
- Calculate purchase bases (ruta 20-24) from supplier invoices by supplier type
- Include ruta 30-32 in ruta 49 formula and totalOutputVat summary
- Display reverse charge section in reports UI and composition chart
- Add comprehensive test coverage for all reverse charge scenarios

Improve mobile UX across the app:
- Convert nav drawer to bottom sheet with drag handle and safe area padding
- Add mobile card layout for PaymentBookingDialog journal lines
- Replace settings tab pills with dropdown selector on mobile
- Make wizard step indicators responsive (collapsed on mobile)
- Ensure all dialog footers stack buttons full-width on mobile
- Add 44px minimum touch targets throughout
- Make onboarding buttons full-width on mobile

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

* fix: address Greptile review — indentation, query efficiency, tab dedup

- Fix misleading try-block indentation in mark-paid route
- Filter reversed entries at DB level (.eq('status', 'posted')) instead
  of fetching then discarding in memory
- Extract shared settingsTabs array so mobile Select and desktop
  TabsList stay in sync automatically

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-18 10:42:57 +01:00
Jakob WennbergandClaude Opus 4.6 a7be1aab17 feat: comprehensive UI design audit and normalization (#28)
* feat: import system improvements, INK2 fix, and Swedish text corrections

- 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>

* feat: arcim migration wizard UX fixes, Sentry setup, and extension scaffolding

Arcim migration wizard improvements:
- Progress bar now excludes non-interactive steps (migrating/result)
- Fix OAuth text to match target="_blank" behavior (new tab, not redirect)
- Display month names instead of "Månad X" in preview
- Fix Swedish typo "förifylla" in no-company-info message
- Replace native checkboxes with shadcn Switch in options step
- Add ConfirmationDialog before starting migration
- Show progress percentage during migration
- Add "Nästa steg" guidance and navigation links in result step
- Add "Försök igen" button in error state (returns to options)
- Add Bokio company ID help text (GUID from URL)
- Add Fortnox integration add-on hint on connection failure

Also includes: SIE import system improvements, INK2 fixes, Swedish text
corrections, Sentry error tracking setup, and arcim-migration extension
scaffolding.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: address PR review feedback

- Fix OAuth error recovery blank page (restore provider from URL params)
- Pass real userId to MigrationWizard instead of empty string
- Remove ~50 debug console.log statements from sie-import.ts
- Fix comment referencing account 3740 → 3741

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat: comprehensive UI design audit and normalization

Dashboard audit:
- Fix muted-foreground contrast (4.31:1 → 5.08:1) for WCAG AA
- Add prefers-reduced-motion media query for all animations
- Replace border-l-2 accent anti-pattern with subtle full-border colors
- Add aria-expanded to toggle buttons, role="status" to live counters
- Fix touch targets on deadline buttons (28px → 36px)
- Vary section spacing for rhythm (mb-12/mb-10/mb-8)
- Remove unused imports and dead code

Transactions audit + hardening:
- Add pagination (200 per page) with "Ladda fler" button
- Replace height animation with transform-only exit animation
- Show batch progress in floating action bar during processing
- Fix batch bar mobile overlap (bottom-20 on mobile)
- Replace clickable badges with proper button elements
- Add safe area padding to fullscreen swipe view
- Add response.ok check to suggestion fetch
- Add truncation to invoice number buttons

Invoicing audit:
- Remove border-l-4 accent pattern from invoice cards
- Replace string concatenation with cn() utility

Systemic sweep (34 files):
- All page headings: font-bold → font-display font-medium (Fraunces)
- All stat numbers: font-bold → font-display font-medium tabular-nums
- All hard-coded blue/amber/emerald colors → design tokens
- Remove all dark mode overrides (tokens handle automatically)
- Tint pure white card background to 99%

Design context added to CLAUDE.md with brand personality,
aesthetic direction, and 5 design principles.

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

* fix: bookkeeping flow audit — design system, accessibility, UX

- Replace raw <select> with shadcn Select component (JournalEntryForm)
- Add confirmation dialog for account deletion (ChartOfAccountsManager)
- Remove console.error from production code (JournalEntryList, JournalEntryForm)
- Fix contradictory h-7/min-h-[44px] button sizing → h-10 (ChartOfAccountsManager)
- Increase BAS catalog "Lägg till" touch target h-7 → h-9
- Improve loading state with spinner (JournalEntryList)
- Improve empty state with icon, description, and guidance (JournalEntryList)
- Add response.ok check on journal entry fetch
- Add aria-expanded to entry expand buttons
- Add tabular-nums to desktop debit/credit columns

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

* fix: onboarding and empty state improvements

Onboarding:
- Replace font-serif with font-display (Fraunces) for brand consistency
- Remove console.error calls from production code

Empty states:
- Fix broken /transactions/new link in EmptyTransactions (route doesn't exist)
- Add actionHref fallback to EmptyCustomers when no onAction prop provided
- Improve EmptyTransactions description copy

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

* fix: clarify Swedish UX copy — terminology, errors, descriptions

Terminology consistency:
- "Försenad" → "Förfallen" for overdue invoices (customers/[id])
- "bokföringsorder" → actionable description in bookkeeping page
- "verifikation har bifogats" → "underlag har bifogats" in doc warning
- "Fortsätt ändå" → "Bokför utan underlag" (specific action)

Error messages — replace generic "Fel" + "Något gick fel" with specific:
- "Något gick fel vid bokföring" → "Transaktionen kunde inte bokföras"
- "Något gick fel vid matchning" → "Transaktionen kunde inte matchas"
- "Kunde inte hämta X" → "Kunde inte ladda X" + recovery hint
- Add "Försök igen" guidance to all error toasts

Page descriptions — replace redundant with actionable:
- Invoices: "Skapa och hantera" → "Skicka, följ betalningar, skapa kreditnotor"
- Bookkeeping: list of features → actionable description

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

* fix: design critique — dashboard affordance, reports description

Dashboard:
- Add ChevronRight indicator to clickable summary cards
  (Att få betalt, Koppla bank) to distinguish from static cards
- Add cursor-pointer to linked cards

Reports:
- Replace feature list description with actionable guidance
  "Huvudbok, grundbok..." → "Generera skattedeklarationer..."

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

* fix: replace generic "Fel" error toasts with specific messages

Deadlines: 5 generic "Fel" → specific per-action titles
  (create, toggle, edit, delete, load)
Expenses detail: 5 generic "Fel" → specific per-action titles
  (load, approve, pay, credit, delete)
Expenses new: 3 generic "Fel" → instructional validation messages
  (supplier name, supplier selection, invoice number)
Customers: 1 generic "Fel" → specific load error with recovery hint

All error toasts now follow pattern:
  title = what failed, description = how to recover

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

* fix: replace all remaining generic "Fel" error toasts (37 instances)

Systematic sweep across 12 dashboard pages replacing generic
title: 'Fel' with context-specific error titles:

- Load errors: "Kunde inte ladda [resurs]"
- Action errors: "[Åtgärd] misslyckades"
- Validation: "[Fält] saknas"

Every error toast now tells the user what failed without needing
to read the description. Recovery hints added where missing.

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

* fix: import flow — normalize stat typography, remove console.warn

- Replace font-bold with font-display font-medium on 13 stat numbers
  across SIEPreviewStep, BankFilePreviewStep, BankFileConfirmStep,
  ImportResultStep (missed by systemic sweep since these are in
  components/import/, not app/(dashboard)/)
- Add tabular-nums to stat numbers displaying counts/currency
- Remove console.warn in ArcimMigrationWorkspace

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

* fix: final cleanup — console statements, remaining font-bold stats

Remove production console statements:
- Step1EntityType: remove debug console.warn (dead code after onNext)
- TransactionBookingDialog: remove console.error on doc link failure
- JournalEntryAttachments: remove 3 console.error calls

Normalize remaining font-bold stat displays:
- SwipeCategorizationView: 3 instances (completion, amount displays)
- NEDeclarationView: yearly result heading + value

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

* fix: address Greptile review feedback

loadMoreTransactions: add inbox item enrichment matching fetchTransactions
- Paginated transactions now fetch invoice_inbox_items in parallel
- Fixes missing document indicator, template suggestions, and inbox
  match card for transactions loaded via "Ladda fler"

fetchAllPages: add maxPages guard (default 500) to prevent infinite loop
- If Arcim gateway returns hasMore:true indefinitely, the loop now
  exits after 500 pages instead of running forever

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

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-14 14:48:40 +01:00
Jakob WennbergandClaude Opus 4.6 a1ca96453c feat: mobile UX improvements across dashboard (#9)
* feat: mobile UX improvements across dashboard pages

- Responsive layouts with stacked mobile forms and grid desktop views
- Safe area handling (viewportFit cover, bottom nav insets, main padding)
- Touch-friendly dialogs, tabs, and page headers
- Mobile nav drawer open/close animation
- Dashboard layout parallel Supabase queries
- SIE parser: handle quoted VER/TRANS fields
- Skip account override for template-based transaction booking

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: address PR review feedback from Greptile

- Fix nav drawer close timer race condition with useRef + clearTimeout
- Show locked VAT rate as read-only label on mobile instead of hiding it
- Remove dead bottom-16 class overridden by inline style

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-11 15:36:52 +01:00
109f860e22 Sandbox (#8)
* feat: add sandbox infrastructure — migration, types, and middleware

Add database migration for sandbox support:
- Add `is_sandbox` boolean column to company_settings
- Update 4 enforcement trigger functions (journal entry immutability,
  journal entry line immutability, retention enforcement, document
  deletion blocking) to bypass checks for sandbox users
- Add `cleanup_sandbox_user()` SECURITY DEFINER function that handles
  FK-safe deletion order (document_attachments → journal_entry_lines →
  journal_entries → supplier_invoices → auth.users cascade)
- Add `cleanup_expired_sandbox_users()` function that loops over
  sandbox users older than N hours with per-user error handling

Update TypeScript types:
- Add `is_sandbox: boolean` to CompanySettings interface
- Add `is_sandbox: false` to makeCompanySettings() test factory

Update middleware:
- Add `/sandbox` to public routes so the landing page is accessible
  without authentication

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat: add sandbox landing page, seed API, cleanup cron, and banner

Sandbox landing page (app/sandbox/page.tsx):
- Client component matching the existing auth page aesthetic
- Auth check: if logged in as real user, shows message to use incognito
- Otherwise shows feature overview (invoices, transactions, bookkeeping,
  reports) with "Starta sandbox" button
- On click: signInAnonymously() → POST /api/sandbox/seed → redirect
- Uses window.location.href for full page load (ensures middleware
  picks up new session cookies)

Seed API (app/api/sandbox/seed/route.ts):
- POST handler gated to anonymous users only (403 for real users)
- Idempotent: returns { seeded: false } if company_settings exists
- Seeds ~40 rows: profile, company_settings (is_sandbox: true,
  onboarding_complete: true), chart of accounts (via RPC),
  fiscal period, 3 customers (Swedish business, EU business,
  individual), 4 invoices (paid/sent/overdue/draft), 4 invoice
  items, 2 posted journal entries with 5 lines, 8 transactions
  (3 categorized, 2 income, 3 uncategorized), 2 deadlines
- Journal entries inserted directly (not via engine) to avoid
  event emission, using next_voucher_number() RPC

Cleanup cron (app/api/sandbox/cleanup/cron/route.ts):
- GET handler with CRON_SECRET Bearer token auth
- Creates service role Supabase client
- Calls cleanup_expired_sandbox_users RPC (24h default)

Sandbox banner (components/dashboard/SandboxBanner.tsx):
- Amber bar with dismiss button (client state, reappears on reload)
- Text: "Sandlådemiljö — dina data raderas automatiskt efter 24 timmar"
- "Skapa konto" link to /register

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat: integrate sandbox into dashboard — banner, nav, settings safeguards

Dashboard layout (app/(dashboard)/layout.tsx):
- Fetch is_sandbox from company_settings
- Render SandboxBanner at top of page for sandbox users
- Pass isSandbox prop to DashboardNav
- Hide RecaptIdentify analytics for sandbox users

Root page (app/page.tsx):
- Same sandbox banner and isSandbox prop treatment as dashboard layout
  (root page has its own layout, not wrapped by (dashboard)/layout)

DashboardNav (components/dashboard/DashboardNav.tsx):
- Add optional isSandbox prop
- Change logout button text to "Avsluta sandbox" when isSandbox
- Redirect to /sandbox instead of /login on logout for sandbox users
- Applied to both desktop sidebar and mobile drawer logout buttons

Settings page (app/(dashboard)/settings/page.tsx):
- Hide "Bank (PSD2)" tab entirely for sandbox users — prevents
  connecting real bank accounts from a temporary anonymous session
- Hide "Radera konto" card for sandbox users — account auto-deletes
  via cron, and the delete flow requires email confirmation

Vercel config (vercel.json):
- Add sandbox cleanup cron at 04:00 UTC daily
  (/api/sandbox/cleanup/cron)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: remove audit trigger for non-existent tax_codes table

Migration 018 referenced public.tax_codes which was never created
(migration 012 is a placeholder). This caused failures when running
migrations from scratch on a fresh database.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: remove ALTER FUNCTION for 3 non-existent functions

Removed search_path pinning for create_invoice_with_items,
seed_asset_categories, and update_reconciliation_session_counts —
none of these functions were ever created in any migration.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: remove ALTER for generate_invoice_number (created in later migration)

The function is created in migration 20260306 with search_path already
set, but migration 20260304 tried to ALTER it before it existed.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Fixed redirect issue

* Update app/api/sandbox/seed/route.ts

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>

* Update app/api/sandbox/seed/route.ts

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>

* Update app/sandbox/page.tsx

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>

* Fixed catch block issue

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
2026-03-11 14:54:53 +01:00
Jakob WennbergandClaude Opus 4.6 f3ec634a46 feat: open-source under AGPL-3.0, redesign UI to grayscale palette, add uncategorize API, fix VAT account names
Add LICENSE (AGPL-3.0-or-later), CONTRIBUTING.md, SECURITY.md, DCO, and NOTICE files.
Rewrite README for open-source audience with self-hosting instructions.
Redesign color palette to grayscale chrome theme across all components.
Add transaction uncategorize API route with tests.
Fix VAT account name mismatches in migration 052.
Improve import page with SIE file support and loading skeleton.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 18:18:11 +01:00
Jakob WennbergandClaude Opus 4.6 66a4027f1e feat: BAS data overhaul, currency revaluation, expenses, UI polish, and cleanup
- Update BAS account catalog with comprehensive SRU codes and K2 flags
- Add currency revaluation service with tests and API route
- Add expenses page and account deletion API
- Enhance booking templates with new patterns and improved tests
- Improve transaction categorization with template picker and description matching
- Polish dashboard, onboarding, import, and transaction UIs
- Refactor year-end service for multi-step closing
- Move SRU generator to ne-bilaga, remove standalone SRU export
- Remove unused dev docs, mock data, and extension hooks
- Add invoice delivery note sequences migration

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 14:19:56 +01:00
Jakob WennbergandClaude Opus 4.6 a2ea52954c fix: address user feedback — AI loan categorization, invoice UX, email extension, supplier module
- #43: Improve AI categorization to use account 2350 for loan repayments
  instead of incorrectly suggesting 2440 (supplier payables). Add explicit
  prompt guidance distinguishing loans from supplier debts.
- #45: Change unclear invoice unit "mån" to "månad"
- #46: Enable email extension in extensions.config.json so it appears in
  the marketplace and can be activated by users
- #47: Change "Makulera" to "Ta bort utkast" for draft invoices — reserve
  "Makulera" terminology for proforma invoices only
- #48: Show field-level validation errors when supplier creation fails
  instead of generic "Validation failed" message
- #49: Temporarily hide Leverantörer and Leverantörsfakturor from sidebar
  pending module rework

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 10:56:26 +01:00
Jakob WennbergandClaude Opus 4.6 aabe527f68 feat: improve UX across reports, navigation, invoicing, and settings
- Reorganize reports page from flat 11-tab bar into 4-column categorized
  grid (Bokslut, Skatt & moms, Huvudböcker, Avstämning)
- Move Import from Övrigt to Finans nav group for better discoverability
- Merge standalone Marketplace link into Tillägg section as "Utforska fler..."
- Rename abbreviated "Lev.fakturor" to full "Leverantörsfakturor"
- Add uncategorized transaction count badge to nav (desktop pill + mobile dot)
- Strengthen credit note confirmation with destructive styling and
  type-to-confirm pattern requiring exact invoice number
- Clarify invoice send vs mark-sent with "Skickad manuellt" label,
  visual hierarchy, and explanatory helper text
- Show Banking and Notifications settings tabs always, with placeholder
  message and link to extensions when not enabled

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 15:11:03 +01:00
Jakob WennbergandClaude Opus 4.6 9d684d7e9f feat: enable PSD2 bank integration via settings
- Uncomment Enable Banking extension in loader (now registered at runtime)
- Add subscriptionNotice field to ExtensionDefinition type
- Show confirmation dialog when enabling extensions with subscription requirements
- Fix Settings banking tab: toggle-aware visibility, URL-addressable tabs,
  BankSelector widget, correct API paths (/api/extensions/ext/enable-banking/*)
- Replace inline bank connection cards with BankConnectionStatus component
- Add actionable link to Settings from EnableBankingWorkspace
- Update CLAUDE.md with latest architecture docs

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 17:01:31 +01:00
Emil bbd673155f Fixed issue with cards and dropdown 2026-02-24 16:13:42 +01:00
Jakob Wennberg 6cef4e11eb New classification logic etc 2026-02-24 11:33:22 +01:00
Emil 0a0e74fdfb Merge remote-tracking branch 'origin/main' into code-quality-improvements 2026-02-21 16:10:02 +01:00
EmilandClaude Opus 4.6 6a5b2b7792 feat: overhaul all 12 sector extensions with full CRUD, validation, and tests
Add shared components (ConfirmDeleteDialog, EditEntryDialog, validation utils),
enhance all 12 extension workspaces with edit/delete dialogs, input validation,
period comparisons, and new analytics features. Fix critical bugs in
ProjectBilling margin calculation and EarningsPerLiter revenue allocation.
Add pure calculation modules with 183 new tests across all extensions.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-21 15:21:49 +01:00
Emil 026497ed75 Added extension functionality 2026-02-21 11:39:45 +01:00
Jakob WennbergandClaude Opus 4.6 d0c0d8a7d2 feat: UX improvements — nav, reports tabs, dashboard alerts, transaction hints, settings layout
- Move Reports to Finans nav group and auto-expand Övrigt on its pages
- Make report tabs horizontally scrollable with gradient fade on mobile
- Surface deadlines and alerts above the fold on dashboard
- Add dismissible categorization hint card on transactions page
- Split settings company form into 4 separate Cards for scannability
- Add monthly breakdown report, document upload zone, journal entry attachments
- Add batch category selector, receipt document linking, invoice form improvements

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-20 17:16:22 +01:00
Jakob WennbergandClaude Opus 4.6 838dc6b8b5 refactor: clean up codebase, remove dead code and obsolete docs
Remove influencer-era documentation, unused components, boilerplate
assets, and ghost tiktok cron job. Add supplier invoice management,
document API routes, and PWA icons. Replace boilerplate README.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-20 12:23:17 +01:00
Jakob Wennberg e8743e6e03 New base 2026-02-19 14:32:54 +01:00
Emil 71edcfd550 Started on new functionality 2026-02-17 23:42:59 +01:00
Jakob WennbergandClaude Opus 4.6 0aecf5b41c Refactor to generic ERP base
Remove influencer-specific features (campaigns, TikTok, gifts, shadow ledger,
contracts, briefings) and consolidate into a clean ERP foundation with core
bookkeeping, invoicing, receipts, tax reporting, and calendar functionality.
Reorganize database migrations into a clean numbered sequence.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-13 15:39:13 +01:00
Jakob Wennberg a25d10a528 Initial copy from influencer-biz 2026-02-13 14:00:46 +01:00