Commit Graph

160 Commits

Author SHA1 Message Date
Jakob Wennberg f3a3d07ed3 feat: one-click company setup from BankID directorships (#309)
* feat: one-click company setup from BankID directorships

After BankID auth, surface Bolagsverket companies where the user is a
director and provision a fully-configured gnubok company with one click
instead of walking the 4-step wizard. Also exposed via CompanySwitcher's
"Lägg till företag" for returning users.

- New /select-company route merges gnubok memberships with TIC
  CompanyRoles; cards flag already-registered org numbers.
- createCompanyFromTicRole server action derives entity_type, f-skatt,
  VAT, moms_period, and SPAR address defaults, then delegates to
  createCompanyFromOnboarding for consistent provisioning.
- TIC /bankid/complete now requests enrichment on login too, so
  returning users see fresh CompanyRoles in the picker.
- Middleware routes zero-membership users to /select-company when
  enrichment is available, /onboarding otherwise.
- Inline enrichment picker removed from WelcomeOnboarding (wizard is
  now the manual fallback); SPAR address pre-fill preserved.
- Unit tests for mapEntityType helper and createCompanyFromTicRole
  defaults (VAT-AB, non-VAT EF, unmappable, unauth).

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

* fix: address PR review feedback on BankID company picker

Greptile P1 + swedish-compliance bot findings:

- Move enrichment row cleanup out of createCompanyFromOnboarding and
  into createCompanyFromTicRole. The manual wizard also goes through
  createCompanyFromOnboarding, and was wiping the enrichment row before
  the returning-user "Lägg till företag" flow could use it.
- Refuse to provision when TIC /lookup is missing. Silently defaulting
  vat_registered to false for a momsregistrerat bolag would create a
  company that issues invoices without moms (ML 17 kap violation). The
  picker now routes to the manual wizard with org_number pre-filled
  when the lookup fails, so the user confirms VAT/F-skatt manually.
- Default accounting_method by entity type: enskild firma → cash
  (K1/kontantmetoden per BFNAR 2013:2), aktiebolag → accrual (K2/K3).
- Document that moms_period='quarterly' is a provisional middle-tier
  default; Skatteverket's assigned period depends on turnover and the
  user can correct it in /settings/tax.
- Fix the misleading "re-fetch from BankID" comment — /select-company
  only reads the cached enrichment row; it's refreshed only on the next
  BankID auth.
- Extend test coverage: lookup-missing refusal, EF kontantmetoden default.

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

* fix: tighten entity-type mapping and clarify K1 threshold

Second round of PR review fixes (swedish-compliance bot):

- mapEntityType now uses explicit allow-lists instead of substring
  matches. "Enskild stiftelse" / "Enskild näringsverksamhet utan firma"
  no longer false-match as enskild_firma (would have provisioned with
  K1/kontantmetoden — ML/BFL risk). Regression guard test added.
- Publikt aktiebolag explicitly included (same K2/K3 regime as private
  AB); Bankaktiebolag / Försäkringsaktiebolag excluded (FFFS regime).
- Remove misleading claim that onboarding UI flags moms_period as
  provisional — no such UI exists by design (approved one-click UX).
- Expand accounting_method comment to cite the 3 MSEK K1→K3 threshold
  (BFNAR 2013:2 vs 2017:3) so the EF→cash default is honest about its
  scope.

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-22 13:21:55 +02:00
Jakob Wennberg adf58a51c0 Prompt to activate missing BAS accounts at commit (#308)
* feat: prompt to activate missing BAS accounts at commit

Booking to an account not in the active chart previously threw a
generic 400 "Account(s) not found: 5010" and the user had to leave
the form to enable the account via /bookkeeping > BAS-katalog.

- New AccountsNotInChartError thrown from resolveAccountIds in the
  engine (and the parallel resolver in core/storno-service). The
  query also now filters on is_active=true, so deactivated accounts
  are treated the same as never-added ones.
- API routes that call the engine (journal-entries, reverse, correct,
  transactions/book + match-invoice + match-supplier-invoice +
  uncategorize, invoices/mark-paid, supplier-invoices + mark-paid +
  credit, salary/runs/correct, import/opening-balance/execute,
  pending-operations/commit) catch the typed error and return a
  structured 400: { error: { code: ACCOUNTS_NOT_IN_CHART,
  account_numbers, message } }.
- /api/bookkeeping/accounts/activate now also reactivates rows that
  already exist but are is_active=false, not only INSERTs. Returns
  { activated, reactivated, skipped, unknown }.
- New GET /api/bookkeeping/accounts/bas-lookup?numbers=... resolves
  BAS names client-side so the dialog can show "5010 · Lokalhyra"
  without bundling the full 1,276-account catalog.
- ActivateAccountsDialog lists the missing accounts (BAS names + any
  unknown non-BAS numbers) and confirms with a single action.
- useSubmitWithAccountActivation wraps an async submit: on
  ACCOUNTS_NOT_IN_CHART it opens the dialog, activates on confirm,
  then retries the original submit so the user never re-enters data.
- AccountCombobox accepts any 4-digit numeric value, not just items
  from the active chart — the activation dialog handles the rest.
- JournalEntryForm wired to the hook + dialog. Other submit surfaces
  now surface a clear Swedish message ("Följande konton behöver
  aktiveras: …") via getErrorMessage; wiring the dialog into those
  is an additive follow-up.

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

* docs: sync CLAUDE.md with current codebase state

Catch-up on growth since the last CLAUDE.md revision:
- Integrations list now includes AWS Bedrock, Upstash Redis,
  Google Drive, Recharts, PDF.js, @react-pdf/renderer, xlsx,
  fuse.js, ics.
- Extension table reflects cloud-backup enabled; adds
  inbox-smart-match and example-logger; reorders to match current
  extensions.config.json.
- Updated counts: 36 event types (was 30+), 35 MCP tools (was 26),
  ~60 tables (was ~47), 118 migrations (was 93), 19 report
  endpoints (was 16), 20 report generators (was 17).
- lib/ directory table now covers salary, providers,
  company-lookup, processing-history, support.ts; removes the
  deleted settings/ subdir.
- App routes table adds /salary/*, /help, /settings/salary,
  /settings/backup.
- API endpoints table adds /api/salary/*, /api/support/contact,
  /api/account/delete, /api/audit-trail/*, /api/log,
  /api/currency/rate, top-level extension routes.
- Tables section adds Salary, Third-party providers, Inbox &
  Migration groups; removes salary_payments (replaced by
  salary_runs + salary_line_items).
- Skills list updated to enumerate the Swedish domain skills by
  name instead of the old single /swedish-bookkeeping.

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

* fix: address PR review feedback on account activation

Seven fixes based on Greptile + Swedish compliance review on #308.

- ActivateAccountsDialog: disable the confirm button when any
  entered number isn't a valid BAS account. Previously activation
  would succeed for the knowns and the retry would immediately
  fail again on the unknowns, giving a confusing double-toast UX.
- pending-operations/commit: revert commitSendInvoice and
  commitMarkInvoiceSent to swallow AccountsNotInChartError
  silently. The prior PR upgrade made these blocking, which
  regressed invoice delivery for users whose AR accounts are
  inactive — and since the activation dialog isn't wired into
  those flows yet, there's no one-click recovery. The silent
  catches now append an InvoiceJournalEntrySkipped event to
  processing_history so the missing verifikation is actionable
  in audit trails rather than silently understating the
  momsdeklaration (revenue / utgående moms unposted).
- engine.reverseEntry: resolve account IDs with includeInactive=true
  so storno of an already-committed entry goes through even when
  the user has since deactivated one of its accounts. Blocking
  the reversal would leave the original entry uncorrected in
  violation of BFL 5 kap 5§ (rättelse must be documented). The
  default (includeInactive=false) still applies to createDraftEntry
  so new bookings to inactive accounts continue to trigger the
  activation dialog.
- supplier-invoices POST + credit: roll back the just-inserted
  supplier_invoices row (items cascade-delete) on any JE failure,
  not only AccountsNotInChartError. An orphan supplier_invoices
  row without a registration / credit JE leaves leverantörsskuld
  (2440) and ingående moms (2641) unposted — a silent
  understatement / overstatement in the momsdeklaration (ML
  2023:200 / BFL 5 kap). The catch now returns a clear Swedish
  error message for non-activation failures (typically period
  lock or DB error) instead of silently logging.

Test mocks for chart_of_accounts updated for the new query chain
(eq.in.eq instead of eq.eq.in after the is_active conditional).

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-22 09:58:54 +02:00
Jakob Wennberg 4cd0a55761 Copy voucher, MRU booking templates, and PDF export for reports (#303)
* feat: copy voucher, MRU booking templates, and PDF export for reports

- Add "Kopiera verifikat" action on the journal-entry detail page that
  prefills a new draft with the source entry's lines, description, and
  notes. Date defaults to today so locked-period posts can't happen by
  accident; source_type resets to manual.
- Track per-company MRU for booking_template_library rows via a new
  booking_template_usage table (fire-and-forget touch endpoint hooked
  into both pickers) and sort the list most-recently-used first for
  the active company.
- Generate downloadable PDFs for balansräkning and resultaträkning
  using the existing @react-pdf/renderer toolchain. Adds a reusable
  parameterized template and two API routes, with download buttons
  on the matching report views.

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

* fix: address PR review feedback on copy-voucher + report PDFs

Compliance review (Swedish accounting):
- Balance-sheet PDF now refuses to render when
  tillgångar ≠ eget kapital och skulder; the stale "Differens" summary
  row is gone. The on-screen view still surfaces the existing
  "Balanserar ej" warning so users can diagnose the imbalance before
  downloading. ÅRL 3 kap / K2 / K3 require exact balance.
- Both PDF routes now 400 when the requested fiscal period cannot be
  resolved — identifiable period is part of räkenskapsinformation
  under BFL 7 kap.
- Income-statement PDF adds the mandatory
  "Resultat efter finansiella poster" subtotal when financial items
  are present, per K2/K3 uppställningsform (ÅRL bilaga 2).
- Copy-voucher flow now shows a clear banner ("Kopia av verifikat X —
  nytt, fristående verifikat skapas") so users cannot mistake the copy
  for a rättelse/storno.

Code review (Greptile):
- New migration adds updated_at column + trigger to
  booking_template_usage (project convention; applied to the
  Supabase project).
- Replace localeCompare on ISO timestamps with plain relational
  comparison to avoid any locale-dependent ordering.
- UUID-format validation on the copy_from query param before it goes
  into the fetch URL.

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

* fix: second round of Swedish compliance fixes on report PDFs

- Balance-sheet PDF imbalance check now compares rounded-to-whole-kronor
  totals (SFL 22:1 convention). The previous 0.5-öre tolerance could
  reject a legitimate balance sheet when accumulated floating-point
  noise across hundreds of ledger lines exceeded the threshold. The
  on-screen view still surfaces the öre-precise "Balanserar ej" badge
  for diagnostic visibility.
- Both PDFs now carry a prominent "Arbetsutkast — ej undertecknat"
  notice per ÅRL 2 kap 7 §. Prevents a downloaded PDF from being
  mistaken for or filed as an approved årsredovisning.
- Income-statement PDF now follows K2/K3 uppställningsform
  (ÅRL bilaga 2) by splitting class 8 into three blocks with named
  subtotals: Finansiella poster (80–84), Bokslutsdispositioner (88),
  Skatter (89). The summary now always shows a "Skatt på årets
  resultat" row so the reader can verify the tax calculation, and
  adds "Resultat efter finansiella poster" / "Bokslutsdispositioner"
  subtotals when each block is present.

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

* fix: harden report PDFs against out-of-band filing + future BAS growth

- Append "-utkast" to downloaded PDF filenames. The filename survives the
  PDF's disclaimer context — a file named balansrakning-2026-01-01.pdf
  in a Downloads folder or forwarded attachment is ambiguous, whereas
  balansrakning-2026-01-01-utkast.pdf makes the draft status legible
  even without opening the document.
- Add a catch-all "Övriga finansiella poster" bucket in the
  income-statement PDF for any class-8 section whose account prefix
  isn't one of the known K2/K3 blocks (80–84 / 88 / 89). Counted in
  the "Resultat efter finansiella poster" subtotal so arithmetic stays
  consistent. Future-proofs the PDF against a generator change that
  starts emitting 85–87 sections.

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-21 22:28:41 +02:00
Mattsson 24107338fa Fix/balance inconsitency (#306)
* feat: implement fiscal period date fields component and validation logic

* feat: update fiscal period validation and naming logic

* feat: implement RPC for computing prior opening balances

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

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

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

* fix: refine companyHasPriorActivity logic to exclude storno entries and improve balance counting
2026-04-21 21:39:57 +02:00
Jakob Wennberg 28df5d851e fix: cancel orphan draft when commitEntry fails + add compliance review CI (#302)
createJournalEntry now cancels the draft with a CAS guard (status='draft')
if commitEntry throws, so callers don't leave undeletable stuck drafts when
the commit RPC rejects (balance trigger, period lock, overload ambiguity).

Also adds a PR-triggered GitHub Actions workflow that runs Claude against
the diff using the swedish-* skills as authoritative references and posts
advisory compliance feedback as a PR comment.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 17:36:09 +02:00
Mattsson 885dd8a2e4 feat: implement fiscal period date fields component and validation logic (#301)
* feat: implement fiscal period date fields component and validation logic

* feat: update fiscal period validation and naming logic
2026-04-21 17:08:38 +02:00
Jakob Wennberg b5df2fb292 feat: invoice-inbox polish + SIE source voucher traceability (#299)
* fix: consolidate commit_journal_entry to single 4-arg signature

Replaces the phantom-overload drop migration with an idempotent consolidation
that leaves only the 4-arg-with-defaults signature, callable with either 2 or
4 named args. Fixes the "Could not choose the best candidate function"
ambiguity caused when the commit-metadata migration CREATE OR REPLACE'd a
4-arg version alongside the existing 2-arg one.

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

* feat: preserve SIE source voucher identity on journal entries

Adds source_voucher_series / source_voucher_number columns to journal_entries
so per-verifikat traceability survives the importer's skip-empty-voucher
logic. The SIE importer populates the original series/number even when
skipped vouchers cause gnubok's target numbering to drift from the source
file's sequence. Required for BFNAR 2013:2 kap 8 behandlingshistorik.

- Migration adds columns + partial index + extends immutability trigger
- importVouchers() records rawSeries/rawNumber per voucher
- JournalEntry type + test fixtures gain the new fields
- Bookkeeping detail page surfaces "Ursprungligt verifikat" when present

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

* feat: polish invoice-inbox workspace for production use

- Bedrock image fit: shrink images > 5 MB via sharp before Bedrock upload
  so HEIC/high-res phone photos don't fail with the 5 MB cap
- Swedish error mapping: toSwedishInboxError translates Bedrock /
  infrastructure errors to Swedish sentences stored in error_message
- History timeline endpoint (GET /items/:id/history) returns the
  processing_history events correlated to the inbox item
- Workspace UI: inline diagnostic timeline inside the convert dialog,
  same-email row grouping ("+N dokument" chip), inferred-VAT affordance
  with "needs review" signalling, Riksbanken exchange-rate prefill for
  foreign-currency invoices so the supplier-invoice create path populates
  *_sek audit columns

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

* feat: extend inbox-smart-match to supplier invoices

Both receipts and supplier invoices expose structurally identical match
anchors (date, amount, currency, counterparty name) so the matcher can
reuse the same narrowing + LLM prompt. Adds getMatchAnchors() as a shared
extractor across ReceiptExtractionResult / InvoiceExtractionResult, and
updates the event handlers to process supplier_invoice items alongside
receipts. LLM prompt re-phrased as "dokument" rather than "kvitto" and
loosened the date-window heuristic since invoice payments can lag behind
the invoice date by weeks.

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

* refactor: drop unused category selector from TransactionForm

The manual "Lägg till transaktion" dialog predates the current categorization
flow (SwipeCategorizationView, BatchCategorySelector, AI suggestions). The
category dropdown here never drove journal-entry creation — onSubmit fanned
it out to CreateTransactionInput.category, which is optional. Removes the
dropdown, the unused watch() hook, and the categories lookup table.

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

* fix(migrations): restore drop-phantom file and rebump timestamps

Supabase branch DB failed with PK violation on schema_migrations because
my two migrations collided with timestamps already on main:
  20260421120000 → journal_entries_with_related_rpc (PR #298)
  20260421130000 → drop_legacy_supplier_invoice_user_id_uniqueness (PR #296)

Rebumped to 20260421140000 and 20260421150000 so each migration has a
unique version (Supabase uses only the 14-digit prefix as the PK).

Also restored the 20260420130000_drop_phantom_commit_journal_entry_overload
migration I had deleted — CLAUDE.md rule #5 forbids modifying existing
migrations. My consolidate migration is still compatible: drop_phantom
drops the 4-arg overload (no-op where absent), then consolidate recreates
it with defaults.

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

* fix(inbox-smart-match): anchor invoices on dueDate with wider window

The original ±7d window around invoiceDate filtered out all real payments
for invoices with standard 30–60 day terms — the matcher would see zero
candidates before the LLM was called, making the supplier-invoice matcher
effectively dead.

New anchor selection:
- Receipts: receipt date ±7 days (unchanged; paid on the spot)
- Invoices with dueDate: dueDate ±14 days (covers early/late payments)
- Invoices without dueDate: invoiceDate -7/+45 days (covers 30-day terms)

MatchAnchors now carries windowDaysBefore/After so the window can vary per
document shape. Added three getMatchAnchors tests asserting window sizes.

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-21 14:33:38 +02:00
Mattsson 64cd6a0989 Fix/footer UI (#296)
* feat: enhance journal entry handling with follow-up entries and related RPC

* fix: improve validation for journal entry lines to ensure proper submission criteria

* feat: add commit_method and rubric_version columns to journal_entries for enhanced tracking

* fix: ensure conditional addition of commit_method and rubric_version columns in journal_entries

* Update supabase/migrations/20260421120000_journal_entries_with_related_rpc.sql

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

---------

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
2026-04-21 12:55:43 +02:00
Jakob Wennberg dd920355b3 fix: use service-role client for processing_history writes (#289)
appendProcessingHistory was accepting a caller-provided SupabaseClient,
but processing_history has no INSERT RLS policy (by design — audit trail
writes should not be user-controllable). Extension handlers pass the
authenticated user client (ctx.supabase), which caused silent RLS
violations caught by try-catch — the audit trail was wired up but empty.

Switch to creating a service-role client internally, matching the
existing event_log pattern (event-log-handler.ts uses
createServiceClientNoCookies). Remove the supabase parameter from both
appendProcessingHistory and appendProcessingHistoryBatch. Update all
5 call sites in invoice-inbox and inbox-smart-match.

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-21 09:20:12 +02:00
Jakob Wennberg 0076aa85f8 feat: arcim inbox (Resend Inbound) + smart-match extension + commit metadata (#286)
* feat: multi-series SIE import, reusable FiscalYearSelector, library templates in picker

- SIE import preserves each voucher's source series (B/C/I/V/...), essential
  for Fortnox migrations where series carry semantic meaning (kundfakturor,
  inbetalningar, etc.). Target numbering still goes through next_voucher_number
  per series; source (series, number) is stored in the migration mapping for
  BFNAR 2013:2 audit trail.
- Execute route reads company_settings.default_voucher_series as the fallback
  for vouchers arriving without a series (SIE4I).
- Extract shared FiscalYearSelector component; adopt in /reports and
  /bookkeeping.
- Transaction TemplatePicker now surfaces user-created library templates
  (company + team scope) alongside the static registry, with a helper to
  convert simple library templates into the BookingTemplate shape.
- Exclude 8999 "Årets resultat" from income statement financial section and
  monthly breakdown so year-end closing entries don't cancel the net result.

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

* test: skip Bokio SIE regression when fixtures are absent

/dev_docs is gitignored (contains anonymised customer exports), so the
integration test can't find its input files in CI. Gate the suite on
fixture presence so it still runs locally.

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

* fix: address Greptile review feedback

- convertLibraryToBookingTemplate: default entity_applicability to 'all'
  when the source template has no entity_type, so TemplatePicker doesn't
  silently hide it for companies with a set entity type.
- FiscalYearSelector: fire onReady in the no-company early-return branch
  so consumers (e.g. ReportsPage) don't get stuck in a loading skeleton
  while the company context is still hydrating.

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

* feat: arcim inbox + smart-match extension + commit metadata

Three threads, all gated off in extensions.config.json (invoice-inbox and
inbox-smart-match are not in the enabled list for this PR).

invoice-inbox: Gmail OAuth -> Resend Inbound (v2.0.0)
- Remove gmail-scanner / gmail-helpers
- Add resend-inbound.ts (webhook verify, attachment fetch) and
  inbox-provisioning.ts (per-company @arcim.io address with rotation)
- Replace /gmail/* routes with /inbox/address and admin-only /inbox/rotate
- Workspace UI: card layout + MatchBlock surfacing AI transaction matches
- classify-document: tightened discount/total prompt; cap confidence at
  50% when line items do not reconcile with amount_incl_vat
- Manifest requires RESEND_API_KEY, RESEND_INBOUND_DOMAIN,
  RESEND_INBOUND_WEBHOOK_SECRET

inbox-smart-match (new extension)
- Event-driven AI matching of receipts to bank transactions
- Listens on inbox_item.classified (match now) and transaction.synced
  (retro-match receipts waiting for a transaction)
- Uses service-role client; processing_history append is scoped by
  company_id from the event payload

commit metadata + audit plumbing
- journal_entries gains commit_method and rubric_version columns
- commit_journal_entry RPC accepts both (BFNAR 2013:2 behandlingshistorik)
- processing-history PII detector strips UUID-shaped substrings before
  personnummer pattern matching (UUIDs were triggering false positives)
- New generic inbox_item.classified event

Migrations
- arcim_inbox: company_inboxes table, resend_email_id, email_body_text,
  auto-provision trigger, drops obsolete email_connections
- journal_entry_commit_metadata: new columns + updated RPC
- inbox_attachment_composite: resend_attachment_id + composite unique index
- inbox_smart_match: correlation_id, match_reasoning, expanded match_method
  CHECK, pending-match and correlation indexes

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

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-20 21:50:07 +02:00
Mattsson 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
Mattsson c0b74d50e4 Fix/original sie export (#279)
* feat: cloud backup to Google Drive + full-archive all-scope

Adds a cloud-backup extension that uploads a full-company backup ZIP to
the user's own Google Drive via OAuth (drive.file scope only). Refresh
tokens are AES-256-GCM encrypted before being stored in extension_data.

The full-archive export gains a scope=all mode for whole-company
backups (per-period SIE under sie/, per-period rapporter/ subfolders,
flat dokument/ manifest tagged with fiscal_period_id). An 80 MB size
guard short-circuits generation before the platform response limit.

Also fixes a latent bug in lib/core/audit/audit-service.ts where the
parameter was named userId while the query filtered by company_id; the
audit-trail API route was passing user.id so audit queries returned
empty unless user and company shared a UUID.

Drive-by: scope the dashboard "fresh start" localStorage key per
companyId so dismissing the setup checklist in one company no longer
carries over to others.

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

* fix: address review comments on cloud backup + archive export

- Extend audit trail to_date to end-of-day so last-day entries aren't
  silently excluded from period-scoped archives.
- Apply 413 size-limit guard regardless of include_documents, using the
  overhead-only figure when documents are excluded.
- Use crypto.randomUUID() for Drive multipart boundary to eliminate any
  collision risk with ZIP payload bytes.

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

* fix: migrate legacy setup-gate localStorage keys on dashboard

Users who previously dismissed the setup checklist via the old global
erp_setup_fresh_start or erp_checklist_dismissed keys were re-gated after
the switch to a company-scoped key. Fall back to the legacy keys on read
and migrate them to the scoped key on first hit.

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

* fix: update customer email handling and anonymization rules in supportmail-to-ticket skill

* test: update audit trail to_date expectation for end-of-day timestamp

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

* fix: enhance full archive generation to include SIE source files and master data dumps

* Update lib/reports/full-archive-export.ts

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

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
2026-04-20 12:33:58 +02:00
Jakob Wennberg 23664e79cb feat: multi-series SIE import, reusable FiscalYearSelector, library templates in picker (#278)
* feat: multi-series SIE import, reusable FiscalYearSelector, library templates in picker

- SIE import preserves each voucher's source series (B/C/I/V/...), essential
  for Fortnox migrations where series carry semantic meaning (kundfakturor,
  inbetalningar, etc.). Target numbering still goes through next_voucher_number
  per series; source (series, number) is stored in the migration mapping for
  BFNAR 2013:2 audit trail.
- Execute route reads company_settings.default_voucher_series as the fallback
  for vouchers arriving without a series (SIE4I).
- Extract shared FiscalYearSelector component; adopt in /reports and
  /bookkeeping.
- Transaction TemplatePicker now surfaces user-created library templates
  (company + team scope) alongside the static registry, with a helper to
  convert simple library templates into the BookingTemplate shape.
- Exclude 8999 "Årets resultat" from income statement financial section and
  monthly breakdown so year-end closing entries don't cancel the net result.

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

* test: skip Bokio SIE regression when fixtures are absent

/dev_docs is gitignored (contains anonymised customer exports), so the
integration test can't find its input files in CI. Gate the suite on
fixture presence so it still runs locally.

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

* fix: address Greptile review feedback

- convertLibraryToBookingTemplate: default entity_applicability to 'all'
  when the source template has no entity_type, so TemplatePicker doesn't
  silently hide it for companies with a set entity type.
- FiscalYearSelector: fire onReady in the no-company early-return branch
  so consumers (e.g. ReportsPage) don't get stuck in a loading skeleton
  while the company context is still hydrating.

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

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-20 11:40:15 +02:00
Mattsson d708a85d4c Feat/cloud backup (#277)
* feat: cloud backup to Google Drive + full-archive all-scope

Adds a cloud-backup extension that uploads a full-company backup ZIP to
the user's own Google Drive via OAuth (drive.file scope only). Refresh
tokens are AES-256-GCM encrypted before being stored in extension_data.

The full-archive export gains a scope=all mode for whole-company
backups (per-period SIE under sie/, per-period rapporter/ subfolders,
flat dokument/ manifest tagged with fiscal_period_id). An 80 MB size
guard short-circuits generation before the platform response limit.

Also fixes a latent bug in lib/core/audit/audit-service.ts where the
parameter was named userId while the query filtered by company_id; the
audit-trail API route was passing user.id so audit queries returned
empty unless user and company shared a UUID.

Drive-by: scope the dashboard "fresh start" localStorage key per
companyId so dismissing the setup checklist in one company no longer
carries over to others.

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

* fix: address review comments on cloud backup + archive export

- Extend audit trail to_date to end-of-day so last-day entries aren't
  silently excluded from period-scoped archives.
- Apply 413 size-limit guard regardless of include_documents, using the
  overhead-only figure when documents are excluded.
- Use crypto.randomUUID() for Drive multipart boundary to eliminate any
  collision risk with ZIP payload bytes.

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

* fix: migrate legacy setup-gate localStorage keys on dashboard

Users who previously dismissed the setup checklist via the old global
erp_setup_fresh_start or erp_checklist_dismissed keys were re-gated after
the switch to a company-scoped key. Fall back to the legacy keys on read
and migrate them to the scoped key on first hit.

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

* fix: update customer email handling and anonymization rules in supportmail-to-ticket skill

* test: update audit trail to_date expectation for end-of-day timestamp

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

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-20 10:49:59 +02:00
Jakob Wennberg 44082ff845 Fix SIE multi-year P&L accumulation, Fortnox supplier filter, processing_history (#267)
* fix: prevent P&L accumulation when importing multi-year SIE files

The opening-balance fallback summed all prior journal lines without
distinguishing balance sheet (class 1-2) from P&L (class 3-8). When
users imported one SIE file per year without running year-end closing
between them, resultatkonton accumulated across years instead of
resetting at each räkenskapsårsskifte. Reported by a customer.

Skip class 3-8 in the fallback path. P&L accounts must reset to zero
each fiscal year (årets resultat → 2099 → equity).

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

* fix: only import unpaid supplier invoices from Fortnox

Fortnox's /supplierinvoices list endpoint doesn't reliably expose
FullyPaid, which caused historic paid invoices to be imported as
unpaid. Switch to the ?filter=unpaid query and surface that scope
in the migration options UI.

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

* feat: add processing_history table for behandlingshistorik

Append-only event log per BFNAR 2013:2 kap 8. Includes:
- processing_history table with seq, correlation/causation chaining,
  aggregate (Document/BankTransaction/MatchProposal/Verifikation/etc.),
  open event_type validated against processing_event_types registry.
- Immutability via audit_log_immutable trigger (no UPDATE/DELETE).
- RLS scoped to user_company_ids; writes via service role only.
- appendProcessingHistory() helper with PII guard rejecting payloads
  containing personnummer/orgnr patterns.
- Shared TS types in types/index.ts.

No consumers wired yet — this is the persistence layer only.

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

* chore: add swedish-project-accounting skill

Reference skill covering projektredovisning: dimensional tagging,
WIP accounting, K2/K3 revenue recognition (successiv vinstavräkning,
färdigställandemetoden), entreprenadavtal, BAS patterns (1470,
1620, 2420, 2450, 4970), and SIE4 #DIM 6 encoding.

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

* fix: rename processing_history migration to avoid timestamp collision

Main already has 20260418120000_allow_retroactive_first_fiscal_year.sql
from #265. Bumping this migration's timestamp to 20260418130000 to
keep schema_migrations.version unique.

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

* fix: address Greptile review on processing_history

- Add BEFORE DELETE immutability trigger so the service role can't
  silently remove rows. Mirrors the pattern from migration 014
  (audit_log_no_update + audit_log_no_delete) and satisfies the
  immutability claim in BFNAR 2013:2 kap 8. Delivered as a follow-up
  migration since the original was already applied in some envs.

- Tighten PII patterns with \b word boundaries to avoid false
  positives on Bankgiro numbers (123456-7890) and invoice references
  like 202312-1234.

- Extend PII validation to actor.label, which previously bypassed
  the payload guard despite the docblock explicitly forbidding
  names/emails/personnummer there.

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-18 13:26:41 +02:00
Mattsson 8ed943198f fix: allow retroactive first fiscal year via SIE import (#265)
The enforce_period_start_day trigger and its sie-import pre-validation
rejected any non-first-of-month period_start whenever *any other* fiscal
period existed for the company. That blocked a real user flow: after
onboarding creates a default period (e.g. current year, day 1), importing
an SIE for an older förlängt första räkenskapsår (e.g. 2017-07-28 –
2018-12-31) failed with "Non-first fiscal period must start on the 1st
of a month".

Per BFL 3 kap., the chronologically first fiscal year is the one that
may be 6–18 months and start mid-month — which is a property of *when*
the period starts relative to others, not of insert order. The trigger
and pre-validation now allow mid-month start iff no existing period
starts earlier.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-18 10:26:02 +02:00
Mattsson c8a5f044c3 feat: add ensureFiscalPeriod function and related tests for fiscal period validation (#264) 2026-04-18 10:05:32 +02:00
Jakob Wennberg fe137346df fix: prevent silent data truncation in report generators (#257)
* fix: prevent silent data truncation in report generators

Supabase PostgREST silently truncates queries at 1000 rows. Several
report generators used bare .select() without fetchAllRows(), causing
incomplete financial data — a BFL compliance violation.

Wrapped 10 queries across 7 files with fetchAllRows():
- monthly-breakdown: journal_entry_lines (easily >1000/period)
- ar-ledger: unpaid invoices
- supplier-ledger: unpaid supplier invoices
- salary-journal: salary_run_employees (+ optimized with !inner join)
- vacation-liability: employees + salary_run_employees
- full-archive-export: document_attachments + journal_entry IDs
- ingest.ts: supplier invoices for auto-matching

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

* fix: address Greptile review — try/catch and client-side safety checks

- Wrap full-archive-export document fetch in try/catch to match the
  VAT section pattern — a failed document query should not prevent
  the rest of the archive from being generated.
- Restore client-side year/status safety checks in salary-journal and
  vacation-liability as defense-in-depth against PostgREST !inner
  filter regressions, per BFL lönejournal and BFNAR 2016:10 compliance.

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 15:46:27 +02:00
Mattsson 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
Jakob Wennberg 24466c6e94 fix: correct INK2R SRU sign convention and field ordering (#249)
* fix: correct INK2R SRU sign convention and field ordering

Cost fields (7511-7528) were negated to negative values, but Skatteverket
expects positive values on the INK2R form. Updated the engine to keep
debit-normal income statement balances positive and adjusted all result
calculations accordingly. Also switched SRU field output from Object.keys()
to canonical Skatteverket ordering arrays.

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

* fix: address Greptile review — deduplicate code arrays, add disposition test

Remove duplicate ASSET_CODES/EQUITY_LIABILITY_CODES arrays from the engine
and import the canonical arrays from types.ts. Add test coverage for
bokslutsdispositioner fields (7524, 7525, 7419) to exercise the new
subtraction sign path.

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-15 16:07:03 +02:00
Jakob Wennberg e46654ab25 feat: concurrency guards, account validation, and reversal side-effects (#247)
* feat: add concurrency guards, account validation, and reversal side-effects to bookkeeping engine

Prevent double-booking via CAS guards on mark-paid and categorize routes (409 on conflict),
make payment GL entries blocking (AP/AR must match GL), validate account resolution in engine,
and auto-sync invoice status on payment reversal. Adds journal_entry.reversed event type.

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

* fix: address Greptile review — company_id filter, voucher gaps, status restore

- Add missing company_id filter on supplier-invoice CAS update (defense in depth)
- Add voucher_gap_explanations insert on CAS-cancelled entries in both mark-paid
  routes (BFNAR 2013:2 compliance, matching categorize route pattern)
- Fix reversal status restore: check due_date to determine overdue vs sent/approved
  instead of always reverting to sent/approved
- Rename shadowed reversedLines variable to originalLines (P2 clarity)

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

* fix: derive reversal payment amount from payments table, not GL lines

The reversal GL entry is already a line-by-line mirror per BFL 5 kap 5§.
For the business-level invoice sync, use the payment record amount from
supplier_invoice_payments / invoice_payments instead of inspecting GL
account numbers — works identically for kontantmetod and faktureringsmetod
without needing to know which accounts were used.

Also adds company_id filter on all reversal sync queries (defense in depth).

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

* fix: allow reversal of partially_paid customer invoices

Widen the status filter from .eq('status', 'paid') to
.in('status', ['paid', 'partially_paid']) so that reversing a partial
payment GL entry correctly updates the invoice state.

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-15 15:23:08 +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 Wennberg b387a77bfd chore: remove Sentry, consolidate migrations, add test coverage (#244)
* chore: remove Sentry, consolidate migrations, add test coverage

Remove @sentry/nextjs and all Sentry integration code — error tracking
now handled by Recapt. Consolidate 22 incremental migrations into a
single schema sync migration. Add 6 new test suites (auth, invoice
matching, VAT rules, opening balances) and extend report tests with
edge cases. Update Docker image name to gnubok, sync crontabs and
extension presets, fix CSP missing space, simplify journal entry
missing-document dialog.

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

* fix: remove viewer bank import migration never applied to production

20260413150000_viewer_bank_import_permissions.sql (PR #234) was merged
to main but never applied to the production database. It references
current_active_company_id() which does not exist in production either.
This breaks fresh installs and Supabase preview branches because the
migration runs before the consolidated schema sync.

Remove it so the migration chain matches production. The viewer bank
import RLS policies should be re-added in a future migration alongside
the helper functions they depend on.

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

* fix: correct delete policies for tables without company_id column

Seven tables in the generic delete-policy loop don't have a direct
company_id column, causing fresh installs to fail with "column
company_id does not exist". Fix by moving them out of the loop:

- invoice_items, journal_entry_lines, receipt_line_items,
  supplier_invoice_items → join through parent table
- extension_toggles, notification_settings, push_subscriptions →
  user-scoped (auth.uid() = user_id)

All policies match their existing production definitions.

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-15 10:52:00 +02:00
Mattsson a3fea6fb7c feat: add opening balance import functionality (#238)
- Implemented OpeningBalanceResultStep component to display results of the import process, including success messages and error handling.
- Created OpeningBalanceUploadStep component for file upload with drag-and-drop support, including validation for accepted file types.
- Developed column detection logic in column-detector.ts to identify account number, name, debit, credit, and balance columns based on headers and data.
- Added parser functionality in parser.ts to handle parsing of opening balance files, including validation and BAS account matching.
- Created tests for column detection and parsing logic to ensure accuracy and reliability.
- Defined types for detected columns and parsed rows in types.ts to improve type safety and clarity in the codebase.
2026-04-14 15:35:50 +02:00
Mattsson bf36ebfd88 feat: booking template library with system templates and cross-company sharing (#235)
* feat: implement viewer role permissions for bank transaction imports and connections

* feat: add booking_template_library table with 30 system templates

Three-level scoping (system/team/company), RLS policies for
read/write/delete, and pre-seeded templates for EU reverse charge,
tax account, private transfers, salary, representation, year-end,
VAT netting, and bank/finance scenarios.

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

* feat: add template library types, helpers, and tests

- BookingTemplateLibrary/Line/Category types in types/index.ts
- applyTemplate() converts template lines + amount into form lines
- Category labels, scope helpers for UI display
- 8 unit tests for amount calculation, VAT, rounding, and scope detection

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

* feat: add booking template CRUD, export, and import API routes

- GET/POST/DELETE /api/settings/booking-templates (list, create, soft-delete)
- PUT /api/settings/booking-templates/[id] (update non-system templates)
- GET /api/settings/booking-templates/export (JSON download)
- POST /api/settings/booking-templates/import (bulk import from JSON)

All routes enforce auth, write permissions, and Zod validation.

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

* feat: add template picker UI and settings management panel

- BookingTemplatePicker: dialog with search, category/entity-type filter,
  line preview, and amount input — integrated into JournalEntryForm
- BookingTemplatesPanel: settings page with grouped templates
  (system/team/company), create dialog, export/import, soft-delete
- Settings templates page now shows both booking and counterparty templates

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

* Update supabase/migrations/20260413160000_booking_template_library.sql

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

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
2026-04-14 09:42:14 +02:00
Mattsson cd376e1cad feat: implement viewer role permissions for bank transaction imports and connections (#234) 2026-04-13 19:43:28 +02:00
Jakob Wennberg 9753f18533 fix: address user feedback — RC preview, bank sync lookback, CSV import robustness (#233)
Three confirmed issues from user feedback:

1. Reverse charge preview now uses per-item VAT rates and correct accounts
   (2645/2647, 2614/2624/2634) instead of hardcoded 25%/2614
2. Bank sync uses 90-day lookback on first sync (when last_synced_at is null)
   instead of hardcoded 7 days for all syncs
3. Bank file import improvements:
   - Shared date normalizer supporting DD.MM.YYYY, DD/MM/YYYY, YYYYMMDD
   - Silent row skips now reported with reason in issues[]
   - Decimal separator mismatch detection in generic CSV
   - Swedish error message with format diagnostics on detection failure
   - Date format selector in column mapping UI

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 17:34:50 +02:00
Jakob Wennberg 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 ade4ad5971 Fiscal period and multi bank (#228)
* feat: add fiscal period backward chaining and entry date validation

Support creating fiscal periods before the earliest existing period
(backward chaining) for backfill scenarios, alongside the existing
forward chaining. The engine now validates that entry dates fall within
the selected fiscal period, with a Swedish error message. The journal
entry form auto-selects the matching period and shows a warning with
a CreatePeriodDialog when no period covers the entry date.


* feat: support multi-bank-account for imports and reconciliation

Plumb a configurable settlement account through the entire bank import
pipeline — mapping engine, transaction entries, ingest, and
reconciliation — so secondary bank accounts (e.g. 1931, 1932) work
correctly instead of hardcoding 1930. Adds a get_unlinked_bank_lines
RPC that generalizes the existing get_unlinked_1930_lines with a
fallback for backwards compatibility. The bank file import UI now shows
a bank account selector when multiple 19xx accounts exist. Also adds
default_vat_code/sru_code to account creation and fixes uploadDocument
argument order in enable-banking sync.
2026-04-13 11:13:02 +02:00
Jakob Wennberg 258a64a849 feat: allow replacing completed SIE imports (#227)
* feat: allow replacing completed SIE imports

Users who import a SIE file, make adjustments in the source system, and
re-export can now replace the old import instead of being permanently
blocked by the "overlapping fiscal year" guard.

The old import's entries are cancelled (posted → cancelled) and the
import is marked as 'replaced'. Nothing is deleted — full audit trail
preserved per BFL 5 kap 5§ (rättelse) and BFNAR 2013:2 kap 8.

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

* fix: address Greptile review — atomic RPC, locked_at check

- P1: Wrap entry cancellation + import status update in a single DB RPC
  (replace_sie_import) to prevent inconsistent state on partial failure
- P2: Check locked_at in addition to is_closed for fiscal period guard
- P2: Use RPC return value for accurate cancelled entry count

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 10:48:49 +02:00
Mattsson 973a2b81a0 Voucher series switcher (#219)
* 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.

* Add default voucher series configuration for manual journal entries
2026-04-12 23:39:14 +02:00
Mattsson a1a816b4a5 Delete features (#218)
* Implement company and account deletion features

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

* feat: enhance account deletion process and update user notifications

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

* Enhance invite flow and email handling for company members

* Refactor company context and RLS policies for active company isolation

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

* feat: implement viewer role enforcement for write permissions

- Added `useCanWrite` hook to determine if the current user has write permissions based on their role in the active company.
- Updated various components (JournalEntryForm, CustomerForm, DeadlineForm, etc.) to disable write actions and show a lock icon with a tooltip for users without write permissions.
- Introduced `requireWritePermission` function to enforce write permissions at the API level, returning a 403 response for viewers.
- Created tests to verify the behavior of the viewer role and write permissions.
- Added database migration to enforce read-only access for viewers at the database level.
2026-04-11 17:06:32 +02:00
Jakob Wennberg 73fb97052b fix: resolve INK2/NE entity_type from companies table fallback (#217)
* fix: resolve INK2/NE entity_type from companies table fallback (#193)

The entity_type check in INK2 and NE-bilaga engines read from
company_settings where it is nullable, causing "only for aktiebolag"
errors when the column is null. Now falls back to companies.entity_type
(NOT NULL, always set). Reports page uses useCompany() context instead
of /api/settings for tab visibility.

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

* fix: address Greptile review — surface fallback errors, avoid direct mutation

- Surface Supabase errors in entity_type fallback queries instead of
  silently swallowing them (ink2-engine, ne-engine)
- Use spread instead of direct mutation on Supabase result object
  (settings route)

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

* fix: use separate variable to avoid const reassignment in settings route

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-11 14:38:02 +02:00
Jakob Wennberg bca00cc5bd fix: BankID fallback, bank import fixes, account deletion (#216)
* fix: BankID graceful fallback, bank import fixes, and account deletion

- BankID: surface service_unavailable state in login/register with password
  fallback messaging; structured error codes in tic extension; poll failure
  counter in BankIdAuth avoids infinite retry when TIC API is down.
- Transactions: allow deleting unbooked bank-synced and imported transactions
  (only posted entries remain protected); detect reconnect duplicates by also
  checking unbooked bank-synced rows in content-based dedup.
- Enable Banking: key external_id by account iban/uid instead of connection id
  so reconnects don't create duplicates.
- Bank import: detect SEB privatbanken CSV variant (Bokföringsdatum /
  Valutadatum headers) via regex.
- Banking settings: replace full-screen sync loader with toast notifications.
- delete_user_account: raise statement_timeout, pre-clear NO ACTION FK
  references, and disable audit/immutability triggers during CASCADE.

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

* fix: address Greptile review — ingest dedup scoping and migration EXCEPTION handler

- ingest: split buildExistingTransactionMap into two maps. Booked rows
  (any source) remain consumed by any incoming raw transaction, but
  unbooked enable_banking slots are only consumed when the incoming raw
  transaction is also enable_banking. This preserves reconnect dedup
  while preventing false positives where a pending bank-synced row
  silently blocks a legitimately separate CSV row with the same
  date/amount.
- delete_user_account: add EXCEPTION WHEN OTHERS handler that re-enables
  every legally required enforcement trigger before re-raising. Postgres
  transactional DDL already rolls back on abort, but the explicit guard
  makes the intent unambiguous and covers sub-transaction edge cases so
  enforcement triggers are never left disabled.

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-11 13:02:50 +02:00
Mattsson aa405b9a74 Fix/company creation bug (#212)
* feat: enhance JournalEntryForm with currency selection and exchange rate fetching

- Added currency selection to JournalEntryForm, allowing users to choose from multiple currencies (SEK, EUR, USD, GBP, NOK, DKK).
- Implemented fetching of exchange rates from Riksbanken API based on selected currency and entry date.
- Updated calculations for foreign amounts and SEK equivalents based on user input and fetched exchange rates.
- Improved form handling to reset currency-related fields when switching back to SEK.

feat: refactor WelcomeOnboarding to streamline company creation process

- Replaced direct company switching with a new server action to create a company from onboarding data.
- Added validation for fiscal period during onboarding steps, allowing for mid-month starts for the first fiscal period.
- Enhanced error handling and rollback mechanisms to ensure data integrity during company creation.

fix: update Step3TaxRegistration to allow flexible first-year start dates

- Modified date selection to include day, month, and year for the first-year start date.
- Updated validation messages to reflect changes in fiscal year start date handling.

test: expand validate-period-duration tests for fiscal period validation

- Added tests to validate that mid-month starts are allowed for the first fiscal period.
- Ensured that subsequent periods must start on the 1st of the month and enforced maximum duration constraints.

feat: implement currency rate API endpoint

- Created a new API route to fetch exchange rates for specified currencies, ensuring user authentication.
- Validated currency input and handled errors for invalid requests.

chore: update database constraints for fiscal periods

- Modified database constraints to allow custom start dates for the first fiscal period while enforcing day-1 starts for subsequent periods.

* fix: implement computeFiscalPeriod function for onboarding and refactor JournalEntryForm

* Fixed date issue

* Added migration
2026-04-10 11:02:28 +02:00
Mattsson bb336eba88 Fix/user feedback (#210)
* Add delete policies for provider consent tokens and provider OTC

* Add trade name support for companies in settings and documents

* Resolved currency selection issue

* Enhance invoice line display with foreign currency support and update delivery date schema to allow empty values

* Add currency display for journal entries and include currency metadata in transaction creation

* Add trade_name column to company_settings for external display
2026-04-09 16:22:51 +02:00
Jakob Wennberg b484e9a7b4 fix: Swedish VAT/SIE compliance, storno hardening, document integrity (#209)
* feat: add INK2 declaration improvements, invoice delivery date, and Swedish compliance skills

Expand INK2 engine with full INK2S/INK2R support and improved SRU generation.
Add delivery_date field to invoices and corresponding PDF/migration support.
Add Claude skills for Swedish asset accounting, invoice compliance, SIE import/export, SRU filing, and tax planning.

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

* fix: address PR review — map BAS 4500–4899, strip CRLF in SRU, document P3

- Map BAS accounts 4500–4599 (legoarbeten), 4700–4899 (diverse
  varuinköpskostnader) to SRU 7512 so they are not silently dropped
  from INK2R declarations
- Strip \r\n in sanitizeString to prevent CRLF injection in SRU fields
- Document P3 period suffix limitation for brutet räkenskapsår

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

* fix: correct BAS 4500-4599, 4700-4899 mapping from 7512 to 7511

Per the official BAS-to-SRU mapping, these account ranges are cost of
goods (legoarbeten, inkurans, svinn) and belong under 7511 (Råvaror
och förnödenheter), not 7512 (Handelsvaror). 7512 remains 4600-4699.

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

* fix: Swedish VAT compliance — representation VAT, domestic RC, full BAS 26xx mapping, SIE encoding

- Representation expenses now default to reduced_12 VAT (ML 13 kap 24-25 §§);
  income tax deduction was abolished 2017 but VAT deduction at 12% remains
- Domestic reverse charge (byggtjänster etc.) uses 2647 instead of 2645,
  with distinct line descriptions for Swedish vs EU/non-EU RC
- VAT declaration maps all BAS 26xx variant accounts (egna uttag 2612/2622/2632,
  uthyrning 2613/2623/2633, VMB 2616/2626/2636, import 2615/2625/2635,
  domestic RC 2647, frivillig skattskyldighet 2642) and revenue variants
  (3108/3105/3004/3100) to correct momsdeklaration rutor
- SIE parser: remove unreliable #FORMAT PC8 encoding detection (most software
  exports UTF-8 with PC8 header), parse #FLAGGA for import-already-done warning,
  default SIE type to 1 when absent, fix RTRANS/BTRANS documentation
- SIE export: add #RAR -1 (previous fiscal year), fix UB = IB + movements
- Error messages: add pattern matching for locked period trigger errors

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

* fix: address Greptile review — update ruta49 JSDoc, use null sentinel in error map

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

* fix: harden storno CAS guard, document integrity, and BFNAR archive compliance

- Storno: defer original→reversed until both entries succeed, add CAS guard
  for concurrent reversals, use cancelEntry() instead of delete
- Document: add document.accessed event, enrich archive manifest with metadata,
  add BFNAR 2013:2 systemdokumentation to full archive export
- Verify cron: run daily, configurable batch size, include company_id in audit
- Migrations: integrity audit actions, document version chain, metadata
  immutability, audit deletions, fix immutability for posted/cancelled

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

* fix: address Greptile review — allow is_current_version in immutability trigger, log cancelEntry errors

- Remove is_current_version from blocked fields in enforce_document_metadata_immutability
  trigger so create_document_version RPC can supersede documents linked to posted entries
- Add error logging to cancelEntry for observability on cleanup failures

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-09 16:12:03 +02:00
Jakob Wennberg 6ccd4f429c fix: Swedish VAT compliance — representation, domestic RC, full 26xx mapping, SIE (#206)
* feat: add INK2 declaration improvements, invoice delivery date, and Swedish compliance skills

Expand INK2 engine with full INK2S/INK2R support and improved SRU generation.
Add delivery_date field to invoices and corresponding PDF/migration support.
Add Claude skills for Swedish asset accounting, invoice compliance, SIE import/export, SRU filing, and tax planning.

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

* fix: address PR review — map BAS 4500–4899, strip CRLF in SRU, document P3

- Map BAS accounts 4500–4599 (legoarbeten), 4700–4899 (diverse
  varuinköpskostnader) to SRU 7512 so they are not silently dropped
  from INK2R declarations
- Strip \r\n in sanitizeString to prevent CRLF injection in SRU fields
- Document P3 period suffix limitation for brutet räkenskapsår

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

* fix: correct BAS 4500-4599, 4700-4899 mapping from 7512 to 7511

Per the official BAS-to-SRU mapping, these account ranges are cost of
goods (legoarbeten, inkurans, svinn) and belong under 7511 (Råvaror
och förnödenheter), not 7512 (Handelsvaror). 7512 remains 4600-4699.

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

* fix: Swedish VAT compliance — representation VAT, domestic RC, full BAS 26xx mapping, SIE encoding

- Representation expenses now default to reduced_12 VAT (ML 13 kap 24-25 §§);
  income tax deduction was abolished 2017 but VAT deduction at 12% remains
- Domestic reverse charge (byggtjänster etc.) uses 2647 instead of 2645,
  with distinct line descriptions for Swedish vs EU/non-EU RC
- VAT declaration maps all BAS 26xx variant accounts (egna uttag 2612/2622/2632,
  uthyrning 2613/2623/2633, VMB 2616/2626/2636, import 2615/2625/2635,
  domestic RC 2647, frivillig skattskyldighet 2642) and revenue variants
  (3108/3105/3004/3100) to correct momsdeklaration rutor
- SIE parser: remove unreliable #FORMAT PC8 encoding detection (most software
  exports UTF-8 with PC8 header), parse #FLAGGA for import-already-done warning,
  default SIE type to 1 when absent, fix RTRANS/BTRANS documentation
- SIE export: add #RAR -1 (previous fiscal year), fix UB = IB + movements
- Error messages: add pattern matching for locked period trigger errors

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

* fix: address Greptile review — update ruta49 JSDoc, use null sentinel in error map

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-09 14:13:15 +02:00
Jakob Wennberg 7a18d89c70 feat: INK2 declaration improvements, invoice delivery date & Swedish compliance skills (#204)
* feat: add INK2 declaration improvements, invoice delivery date, and Swedish compliance skills

Expand INK2 engine with full INK2S/INK2R support and improved SRU generation.
Add delivery_date field to invoices and corresponding PDF/migration support.
Add Claude skills for Swedish asset accounting, invoice compliance, SIE import/export, SRU filing, and tax planning.

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

* fix: address PR review — map BAS 4500–4899, strip CRLF in SRU, document P3

- Map BAS accounts 4500–4599 (legoarbeten), 4700–4899 (diverse
  varuinköpskostnader) to SRU 7512 so they are not silently dropped
  from INK2R declarations
- Strip \r\n in sanitizeString to prevent CRLF injection in SRU fields
- Document P3 period suffix limitation for brutet räkenskapsår

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

* fix: correct BAS 4500-4599, 4700-4899 mapping from 7512 to 7511

Per the official BAS-to-SRU mapping, these account ranges are cost of
goods (legoarbeten, inkurans, svinn) and belong under 7511 (Råvaror
och förnödenheter), not 7512 (Handelsvaror). 7512 remains 4600-4699.

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-09 13:16:10 +02:00
Mattsson bf5a8d9195 Fix/multiple company (#203)
* Refactor onboarding and dashboard logic; add silent team creation for users

- Removed unnecessary useCompany context in DashboardContent and SettingsSidebar components.
- Simplified onboarding setup logic to allow direct access to the dashboard for users without companies.
- Introduced WelcomeOnboarding component to handle user onboarding steps.
- Added migration to create silent teams for all users at signup, backfilling existing users without teams, and cleaning up incomplete companies.

* fix: update greeting logic and improve email handling in TIC extension

* Redirect to onboarding for users without companies and update onboarding flow

* Build issue fix

* Enhance onboarding experience by adding existing companies check
2026-04-09 11:54:12 +02:00
Mattsson 211033410c Fix/import data (#200)
* fix: enhance import data handling and consent management across components

* feat: Enhance SIE import functionality with validation and error handling improvements

- Added validation errors and warnings state management in SIEImportWizard.
- Improved error handling for duplicate, validation, and parsing errors during SIE file import.
- Enhanced user feedback with actionable guidance for common import errors.
- Updated SIEUploadStep to display validation errors and warnings.
- Improved error messages in API routes for better clarity and user experience.
- Added file size and type validation in the SIE parse route.
- Enhanced parsing logic to provide more detailed error messages for unbalanced vouchers and missing amounts.
- Created a new storage bucket for SIE file archival in Supabase with appropriate policies for user access.
- Updated tests to reflect changes in error messages and validation logic.

* fix: Improve type assertion for response in getPage method

* Update extensions/general/arcim-migration/lib/migration-orchestrator.ts

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

* Update supabase/migrations/20260408130000_sie_files_storage_bucket.sql

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

* fix: Add company ID verification for consent handling in accept and disconnect endpoints

---------

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
2026-04-08 18:17:00 +02:00
Jakob Wennberg 6c79f21679 fix: INK2 rounding tolerance and clearer warnings (#197)
* fix: add rounding tolerance to INK2 balance check and clarify warnings

INK2/SRU rounds each ruta independently to whole kronor, so with 11+
rutor the accumulated rounding can produce a 1-2 kr difference that
triggered a false "balance sheet not in balance" warning. Add a 2 kr
tolerance. Also clarify the unclosed fiscal year warning to indicate
that generation still works.

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

* fix: also warn when equity/liabilities exist but assets are zero

Address Greptile review feedback — the balance check guard should
trigger when either side has a non-zero total, not only when assets > 0.

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-08 17:04:48 +02:00
Jakob Wennberg 20818e3283 fix: Nordea Datum CSV variant + bank sync loading screen (#195)
* fix: support Nordea Datum CSV variant and add bank sync loading screen

Add 4th Nordea Business CSV format variant that uses standalone "Datum"
column header and YYYY/MM/DD date format. Also replace fire-and-forget
bank sync with an awaited flow showing a loading screen after first
bank connection, preventing users from navigating away before sync
completes.

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

* fix: address PR review feedback — cleanup timeout, deps, naming

- Clear success setTimeout on unmount via useRef to prevent stale updates
- Add isSyncing to useEffect dependency array for Strict Mode safety
- Rename headers_detect to headersDetect (camelCase consistency)
- Remove redundant toLowerCase() since firstLine is already lowercased

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-08 15:02:18 +02:00
Jakob Wennberg 6d75b9a1bf feat: BankID authentication via TIC Identity API (#192)
* feat: add BankID authentication via TIC Identity API

Integrate BankID as a login/signup method using the TIC Identity API.
Users can authenticate with BankID QR codes (desktop) or deep links (mobile),
link BankID to existing accounts, and skip TOTP MFA when BankID is linked.
Removes Step 0 (role choice) from onboarding for all users. Adds enrichment
data support for pre-filling company details from Bolagsverket during signup.

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

* fix: address PR review — server-side rate limit, unlink clears MFA bypass

- Add per-IP rate limit (5s cooldown) on /bankid/start to prevent
  unbounded billable TIC sessions from unauthenticated callers
- Add /bankid/unlink endpoint that deletes bankid_identities AND clears
  app_metadata.bankid_linked so MFA enforcement resumes after unlink
- Update BankIdSettings to call server-side unlink instead of client-side delete

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

* fix: move rate limiter to module scope, add BankID logo and year-end skill

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-08 13:44:14 +02:00
Mattsson 6486e0d9e2 Fix/transaction inconsitensies (#190)
* fix: request explicit page size from Enable Banking API to fetch all transactions

The API defaults to ~10 transactions per page when no limit is specified,
causing incomplete syncs for users with more transactions.


* fix: enhance DELETE operations and add missing RLS policies for multi-tenant support
2026-04-08 11:25:46 +02:00
Jakob Wennberg a25e75be25 fix: Nordea Business CSV variants, API keys UI polish, transaction categorization (#183)
* fix: MCP OAuth 303 redirect, send dialog auto-close, bank details null payload

- OAuth authorize: use 303 See Other instead of default 307, which
  preserved POST method and caused Claude's callback to return 405
- SendInvoiceDialog: close dialog and show toast after email send
  instead of leaving a success message that requires manual close
- BankDetailsSetupDialog: omit empty fields from payload instead of
  sending null, which fails Zod validation on non-nullable schema fields

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

* refactor: remove dead sentMessage state and fix stale comment

Remove sentMessage state, its success banner JSX, and the CheckCircle2
import — all unreachable after the dialog now auto-closes on email send.
Fix stale "to null" comment in BankDetailsSetupDialog.

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

* fix: support Nordea Business CSV variants, polish API keys UI, fix transaction categorization

- Extend Nordea Business bank file parser to handle three CSV export
  formats (classic, Betalare/Mottagare variant, Bokföringsdatum variant)
  with proper detection guards against SEB/LF misidentification
- Rework ApiKeysPanel: add CopyBlock component, destructive confirm on
  revoke, collapsible API-key-based connection methods, Claude.ai OAuth
  instructions as recommended path, simplified scope badges
- Stop deriving is_business from category on manual transaction creation;
  set null so categorization flow handles it correctly
- Show categorize button when journal_entry_id is missing regardless of
  is_business value

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

* fix: address PR review — await clipboard, fix zero-scope label, simplify condition

- Await navigator.clipboard.writeText and catch failures
- Change zero-scope label from "Enbart läs" to "Inga behörigheter"
- Simplify redundant ternary condition in TransactionHistoryList

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

* fix: repair broken ternary in TransactionHistoryList JSX

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-07 11:11:47 +02:00
Mattsson e42da5c32b Staging (#181)
* refactor: remove unnecessary secondary action from EmptyInvoices component

* feat: add direct provider layer and provider_consents migration

Replace Arcim Sync gateway dependency with direct provider clients
for Fortnox, Visma, Briox, Bokio, and Björn Lundén. Adds OAuth
config, rate limiting, retry logic, data fetching, and consent
storage via new provider_consents/tokens/otc tables.

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

* refactor: migrate arcim extension to direct provider APIs

Replace Arcim Sync gateway calls with direct provider API access.
Use FortnoxClient.getText() for SIE endpoints that return plain text
instead of JSON. OAuth callback now returns HTML with postMessage
to communicate with the opener window instead of redirecting.

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

* fix: use OAuth popup window instead of new tab

Open provider login in a centered popup that auto-closes on
completion via postMessage, keeping the user on a single tab.
Falls back to redirect flow if popup is blocked.

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

* feat: add connection status, consent reuse, and SIE duplicate detection

- Add listConsents() to query active consents by company
- Add GET /status route returning consents, SIE import history, and
  entity counts
- /connect reuses existing accepted consent instead of creating
  duplicates, and cleans up abandoned (status 0) consents
- /status only returns accepted (status 1) consents
- /sie-data checks each file's SHA-256 hash against sie_imports to
  report per-file import status (alreadyImported, importedAt)
- /sie-data blocks on SIE validation failure (mirrors manual upload)
- /import-sie validates unmapped accounts and auto-activates missing
  BAS accounts in chart_of_accounts (mirrors manual upload)

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

* feat: show active connections, SIE file status, and smart re-sync UI

- ProviderStep shows active connections with last import date, entity
  counts, "Synka igen" button, and disconnect option
- Already-connected providers greyed out in selection grid
- OptionsStep shows per-fiscal-year import status (imported vs new)
- SIE toggle disabled with explanation when all files already imported
- handleStartMigration skips already-imported SIE files
- Auto-skip mapping step and disable SIE on re-sync when up to date
- Result step hides empty "0 importerade" rows and shows
  "Allt är uppdaterat" when nothing new was fetched
- OptionRow supports disabled state

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

* fix: adjust COMING_SOON_PROVIDERS based on NODE_ENV for development and production

* Removed duplicate

* Removed duplicate

* refactor: redesign reports page navigation from grid boxes to bordered card layout

Replace the 4-column grid of uneven TabsList boxes with a CSS grid card
using auto-sized columns separated by 1px border dividers. All sections
now share equal height via items-stretch, with clear visual separation
between groups.

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

* chore: trigger Vercel deployment

* Update supabase/migrations/20260402010000_provider_consents.sql

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

* Update lib/providers/rate-limiter.ts

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

* chore: re-trigger checks after migration sync

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
2026-04-07 10:30:20 +02:00
Jakob Wennberg c4a6d16e94 feat: document inbox + fix MCP pending operations user_id (#172)
* fix: always use business PSU type for bank connections

EF (sole trader) users connecting to Nordea got personal accounts
because psu_type was set to 'personal' based on entity_type. Since
gnubok is accounting software, all bank connections should use
'business' PSU type regardless of entity type.

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

* feat: document inbox extension with AI classification

Add invoice-inbox extension for email-based document processing with
AI-powered classification, supplier matching, and inbox management.
Includes MCP tools for document upload/listing, migration, and
supporting changes across document service, API keys, and banking.

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

* fix: disable invoice-inbox extension, use dynamic import in MCP server

Keep invoice-inbox out of extensions.config.json until ready for
production. MCP server now dynamically imports classifyDocument to
avoid breaking when the extension is disabled.

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

* fix: correct file size error message in invoice-inbox upload

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-03 16:45:17 +02:00
Jakob Wennberg 1dcec370b6 fix: sanitize document filenames and add upload validation (#171)
* fix: sanitize document filenames and add server-side upload validation

Filenames with spaces or non-ASCII characters (e.g. Swedish ö, ä, å) caused
Supabase Storage to reject uploads with "Invalid key". This adds filename
sanitization, server-side size/type validation on both upload routes, and
fixes a duplicate-filename race condition in the upload UI.

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

* fix: require MIME type and handle empty sanitized filenames

Address review feedback:
- MIME type check now rejects files with missing/empty Content-Type
  instead of silently allowing them through
- Fallback to 'file' when sanitized base is empty (e.g. ööö.pdf)

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-03 14:44:38 +02:00
Jakob Wennberg 8855ef1485 fix: use range overlap for duplicate SIE period check (BFL 4:1) (#163)
* fix: use range overlap for duplicate period check (BFL 4:1 compliance)

The period duplicate check used exact-match on fiscal_year_start/end, missing
overlapping periods (e.g., partial-year file vs full-year import). Now uses
standard interval overlap test (start <= other_end AND end >= other_start).

Updated Swedish error messages to reflect overlap semantics.

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

* fix: align JSDoc citation to BFL 4:1 for period overlap check

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-02 12:21:42 +02:00