Commit Graph
9 Commits
Author SHA1 Message Date
Jakob WennbergandClaude Opus 4.7 39204cc0de UX polish bundle: Enable Banking lookback + sync progress, invoice inbox, matching previews (#548)
* fix(import): dedup opening-balance rows when account numbers differ only in whitespace

The parser's merge map keyed on the post-strip account_number, but rows like
"1930", " 1930 " and "1.930" could leak as separate entries when the
upstream string contained non-breaking spaces or zero-width chars that the
old .replace(/[^0-9]/g, '') ran on already-stripped output. Strip those
explicitly in the raw string and use /\D/g for the digit extraction.

Also adds defense-in-depth dedup inside OpeningBalanceEditStep so any
duplicates that survive the parser collapse before the user sees them.

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

* feat(enable-banking): anchor lookback picker to fiscal year, not days

Replaces the 90/180/365 days dropdown on the account-selection screen
with three explicit modes:

- "Senaste 90 dagar (snabbt)" — fastest path, matches PSD2 ceiling
- "Sedan räkenskapsårets början" (default) — resolves via
  fiscal_year_start_month, surfaces the literal date inline
- "Anpassat datum" — free date picker OR "Föregående räkenskapsårets start"

When the resulting range exceeds 90 days, the picker now surfaces a
quiet helper that points users at the SIE/bankfil import for older
history, so they don't waste an account-selection round-trip discovering
that banks usually cap at ~90 days.

The PATCH /accounts handler accepts initial_lookback_from_date alongside
initial_lookback_days; the new helper getCurrentFiscalYearStart() in
lib/company/fiscal-year.ts is reused.

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

* feat(enable-banking): dedicated sync progress modal replaces silent spinner

After the user confirms account selection, transactions fetch in the
background for 30–60 seconds. Previously this showed only the Spara-button
spinner with no indication of duration or what was happening — users
described being stuck on the page.

The new BankSyncProgressDialog opens immediately on Save, lists the enabled
accounts being synced, and disables manual close until the PATCH resolves.
On completion it shows the imported count and the actual date range the
bank returned, plus an amber escape hatch to SIE/bankfil import when the
returned range was truncated by >7 days from what was requested.

Failure path surfaces in the same modal rather than as a destructive toast
that disappears.

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

* refactor(invoice-inbox): drop duplicate Skapa leverantör button

The inbox detail panel had its own supplier-creation button that fired
/api/suppliers + match-supplier. The same action is reachable from the
supplier-invoice form's "Skapa & välj" card (showAISupplierHint), which
also prefills more fields. The duplicate button is gone; a quiet inline
hint replaces it so the user still knows why no supplier matched.

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

* ui(invoice-inbox): surface currency and totals above the long metadata tail

Move Valuta / Totalt / Moms in FIELD_DEFS so they sit immediately under
Leverantör / Org.nr / VAT-nr. These are the fields the user reads first
when triaging an inbox item; burying them after nine metadata fields
forces unnecessary scrolling on every single invoice.

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

* feat(invoice-inbox): accept .eml forwards and log rejected attachments

Gmail's "Forward as attachment" packages the original email as message/rfc822,
which our MIME allowlist silently dropped. Adds mailparser so we can unwrap
the inner attachments and ingest them under the inner email's subject/from.

Also persists every rejected attachment as an invoice_inbox_items row with
status='error', so users can see what was dropped instead of guessing why
nothing showed up in their inbox.

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

* fix(supplier-invoices): redirect back to inbox after creating from invoice-inbox

When the leverantörsfaktura form is opened from an invoice-inbox item, every
successful create previously kicked the user out to /supplier-invoices or the
just-created invoice's detail page — derailing the "process the next
document" workflow. The Tillbaka button likewise routed to the
supplier-invoice list rather than the inbox they came from.

Adds an afterCreate helper that lands inbox-originated submissions at
/e/general/invoice-inbox and preserves the original target everywhere else.

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

* ui(pending): show transaction/document context for match-and-attach reviews

The granskning page previously rendered attach_document_to_transaction and
match_transaction_invoice operations through the generic key/value preview,
so reviewers saw "document file name: Faktura.pdf / transaction amount:
-216 USD" without any visual indication of which two things were being
paired. The MCP tool already returns enriched preview data; we just
needed dedicated layouts.

Adds:
- AttachDocumentPreview — two-card layout (Transaktion | Dokument) with
  a "Visa dokument" button that fetches a signed download URL on demand
- MatchTransactionInvoicePreview — same layout (Transaktion | Faktura)
- DocumentViewButton — reusable signed-URL opener

Also tightens the matching tools' descriptions so AI clients are nudged
to verify human-readable context before staging.

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

* fix(review): address PR #548 review feedback

- invoice-inbox: hoist mailparser to a static import. The extension system
  generates a static import tree via setup:extensions and disallows dynamic
  imports — await import('mailparser') worked in dev but could fail in
  production standalone builds.
- enable-banking AccountPickerDialog: guard the Save path when "Anpassat
  datum" + "Specifikt datum" is selected with an empty date. Without this,
  lookback.body resolves to null and the PATCH silently falls back to the
  backend's 120-day default, ignoring the user's intent.
- enable-banking BankSyncProgressDialog: drop the empty-body useEffect.
  Close-prevention is already handled inline via the onOpenChange guard +
  onPointerDownOutside + onEscapeKeyDown handlers.
- lib/company/fiscal-year: pin both operands of daysBetween() to UTC when
  parsing ISO date strings. Mixing a UTC-parsed date with new Date() (local
  time) drifts by one day in any timezone east of UTC.

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

* fix(review): address compliance swarm + Swedish review feedback

Three actionable items from the post-fix compliance scan; the rest were
false positives or out of scope.

- enable-banking PATCH /accounts: reject future initial_lookback_from_date
  with 400 instead of silently falling through to the 120-day default.
  Compliance V2.2.
- AttachDocumentPreview: promote the overwrite warning to a destructive
  banner with BFL 7 kap context when the existing document is marked as
  räkenskapsinformation. A muted footnote was too easy to skip past for
  a verifikationsunderlag replacement.
- MatchTransactionInvoicePreview: surface transaction_date + invoice_date
  in the staged preview so reviewers can spot date drift before approving
  (BFL 5 kap 6§ — verifikation date must align with affärshändelse). Also
  shows a quiet hint when the two dates differ by > 31 days. Tool's SELECT
  + stage payload extended accordingly.

Skipped (with rationale):
- V5.3 inner.filename path traversal — lib/core/documents/document-service.ts
  already sanitizes filenames before constructing storage paths.
- V5.2 magic-number MIME — pre-existing pattern for all email attachments;
  scope is codebase-wide.
- V1.2 att.id composite ID — only used as a DB column value, never a path.
- V13.1 / CM-8 SBOM/SCA — repository-wide policy, not this PR.

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

* fix(review): address second-round compliance + Swedish review feedback

Compliance Swarm (defense-in-depth + valid finds):
- invoice-inbox: sanitise .eml inner attachment filenames and content-types
  before they flow into uploadAndExtract or the raw_email_payload JSONB.
  document-service already strips bad chars before constructing storage
  paths, but the swarm flagged the upstream input as unsanitised — easier
  to add a thin sanitiseFilename/sanitiseMime layer than to argue about
  defense-in-depth. Caps lengths too.
- DocumentViewButton: validate documentId as a UUID before interpolating
  into /api/documents/:id — staged preview_data is Record<string, unknown>
  on the wire, so refusing junk early gives a clearer error and keeps the
  internal API from seeing oddly-shaped path segments. (Compliance V1.2.)

Swedish review:
- MatchTransactionInvoicePreview: drop the BFL 5 kap 6§ citation from the
  date-drift hint — that section governs verifikationsinnehåll, not a 31-day
  tolerance. The hint stays (the practical concern is real) but no longer
  pretends to quote a legislated threshold.
- fiscal-year: document the implicit assumption that entity_type reflects
  the company's current tax-year status, not a mid-conversion state.

Skipped (with rationale):
- V5.2 magic-number MIME — pre-existing pattern across all email attachments.
- A.8.12 signed URL via window.open — pre-existing pattern shared with
  JournalEntryAttachments.tsx; refactor to server-side redirect is broader scope.
- A.8.15 logRejection failure path — pre-existing console.error pattern.
- CC9.2 mailparser vendor review / SBOM — out of PR scope.
- CC6.1 IDOR — /api/documents/:id already enforces company_id; false positive.
- Swedish #1 räkenskapsinformation flag origin — server-side already derives
  the flag from document_attachments.journal_entry_id in the staging tool;
  not caller-trusted.

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

* fix(review): fail-safe BFL warning + preserve merge validation errors

Two findings from the third compliance pass; both worth addressing.

- AttachDocumentPreview: treat an absent
  existing_document_is_rakenskapsinformation flag as räkenskapsinformation
  rather than as "safe to overwrite". The MCP staging tool sets the flag
  deterministically from document_attachments.journal_entry_id today, but
  a future code path that forgets it would silently downgrade the BFL 7
  kap warning. Only an explicit `=== false` from the server keeps the
  muted note path.
- Opening-balance merge: union validation_errors when collapsing duplicate
  account_number rows, both in the parser and the EditStep useState
  initializer. Previously a warning that fired on row 5 (e.g. BAS-class
  mismatch) was silently dropped if row 2 of the same account had no error,
  risking misclassified IB data downstream. Added a parser test covering
  the union behaviour for two rows of a class-3 (resultatkonto) account.

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

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 12:10:12 +02:00
Mattsson 9aced4790c feat(api): implement caching and logging in health check endpoint (#526)
* feat(api): implement caching and logging in health check endpoint

- Added in-memory caching for health check responses to reduce load on Postgres.
- Introduced logging for error handling in health check.
- Updated response structure to exclude error details from public responses.

feat(api): enhance OAuth consent UI and scope handling

- Improved consent UI to reflect exact requested scopes and added better user guidance.
- Updated scope handling logic to ensure least-privilege access.
- Enhanced styling for better user experience and accessibility.

chore(docker): improve security and resource management in Docker setup

- Updated Docker Compose configuration to enforce read-only file systems and resource limits.
- Added health checks and logging options for better observability.
- Introduced optional Caddy reverse proxy for TLS termination.

fix(migrations): resolve ambiguity in create_company_with_owner function

- Dropped orphaned 3-arg overload of create_company_with_owner function.
- Recreated canonical 4-arg version with cash account seeding logic.
- Ensured proper permissions for function execution in Postgres.

* feat: enhance security checks for team membership in company creation
2026-05-19 17:52:11 +02:00
Mattsson fa7d4075cf Supp/invoice bfl errors (#390)
* feat(accounting): update accounting method validation and messaging for aktiebolag and enskild firma

* Remove AI subsystem and related code

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

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

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

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

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

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

* fix(schemas): remove accounting method validation for aktiebolag in UpdateSettingsSchema
2026-05-05 09:53:37 +02:00
Jakob WennbergandClaude Opus 4.7 e13a450e21 chore: drop dead SPAR-reading code (#350)
We only request CompanyRoles from TIC enrichment (confirmed in
fetchAndStoreEnrichment), so enrichmentData.spar is never populated —
the address pre-fill paths in WelcomeOnboarding and createCompanyFromTicRole
were dead branches. Drop them so future readers don't wonder why code
looks for SPAR when we never request it.

- WelcomeOnboarding: remove the `loadSparAddress` useEffect and the
  isLoading spinner it gated. The server-side onboarding page already
  handles the auth redirect, so the client-side auth check in the
  effect was redundant. Also drops unused imports (useEffect,
  createClient, Loader2) and the `isLoading` state.
- createCompanyFromTicRole: remove the SPAR fallback in address
  resolution. Keep the extension_data row lookup — it's still needed
  for the one-time-use delete after successful provisioning.

If TIC enables SPAR/Address later AND we decide to use it for address
pre-fill, the read code lives in git history and we can restore it
surgically alongside re-adding 'SPAR'/'Address' to the request array.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 16:55:07 +02:00
Jakob WennbergandClaude Opus 4.7 8fd3f112f8 fix: surface active TIC companies + block duplicate org numbers (#344)
* fix: surface active TIC companies + block duplicate org numbers

Three fixes from live-prod testing:

1. Enrichment filter hid the user's directorships. Now accepts both
   Completed and PartiallyCompleted status from TIC (tenants without
   CompanyRoles enabled still get SPAR) and the /select-company role
   filter no longer requires companyStatus === 'Aktivt' — real TIC
   payloads have been observed with different values, and positionEnd
   alone is the authoritative "currently a director" signal. Added
   PII-free diagnostic logs so the next shape-mismatch is debuggable
   from Vercel logs without a round trip.

2. Manual wizard silently allowed duplicate org numbers. Added:
   - findExistingCompanyByOrgNumber helper in actions.ts (service role,
     bypasses RLS to see cross-tenant rows)
   - Server-side guard in createCompanyFromOnboarding — returns
     'org_number_exists' before the create RPC so we don't leave ghost
     companies
   - New /api/company/check-org-number endpoint for debounced client
     checks
   - Warning + disabled submit in Step2CompanyDetails
   - Friendly error toasts in WelcomeOnboarding + BankIdCompanyPicker
   - Mirror cleaned org_number onto companies.org_number on creation so
     future duplicate checks and lookups are reliable

3. /onboarding ignored ?org_number= when the picker routed there as a
   fallback. Now reads searchParams and pre-fills settings; also fixed
   a latent bug where Step1's entity-type change wiped the pre-fill on
   *first* selection (it should only reset on a genuine change).

Tests: duplicate-org guard (with formatted-input normalization),
check-org-number route (auth + 400 + exists true/false +
normalization).

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

* fix: address PR review feedback on duplicate-org guard

Greptile P1 findings + swedish-compliance feedback:

- findExistingCompanyByOrgNumber now throws on Supabase error instead
  of silently returning null. Previously a DB outage or RLS
  misconfiguration would bypass the entire duplicate guard and allow
  duplicates through.
- createCompanyFromOnboarding catches the throw and returns a
  user-facing error ("Kunde inte verifiera organisationsnummer"),
  failing closed instead of open.
- companies.update({ org_number }) error is now checked and triggers a
  rollback. Silent failure would leave the company without an
  org_number, breaking all future duplicate checks for that entity.
- New normalizeOrgNumber helper validates 10- or 12-digit input,
  strips the century prefix for 12-digit personnummer form, and
  rejects anything else. Malformed input would have corrupted SIE4
  (#ORGNR) and SRU (INFO.SRU) exports downstream.
- /select-company now uses loose `== null` for positionEnd — TIC has
  been observed returning `undefined` for open-ended positions, which
  strict `=== null` would silently filter out. Documented the two
  downstream isCeased guards so future maintainers don't remove one
  without the other.
- createCompanyFromTicRole refuses to provision when lookup.isCeased
  (BFL 2 kap — bokföringsskyldighet ends at avregistrering).
  BankIdCompanyPicker surfaces this client-side too.
- WelcomeOnboarding + BankIdCompanyPicker recognise new error codes:
  org_number_invalid, company_ceased.

Tests: +4 cases covering malformed input rejection, fail-closed
behaviour on DB error, 12-digit personnummer normalization, and the
ceased-company refusal path. Full suite: 2306 passing.

Out of scope for this PR (follow-up):
- Partial unique index on companies(org_number) WHERE archived_at IS
  NULL. Closes the race-condition window but needs a migration plus
  any existing-duplicate cleanup — too risky for this hotfix.
- Rate limiting on /api/company/check-org-number. Endpoint is
  auth-gated so not an immediate concern.

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

* fix: add Luhn validation and extract org-number normalization

Third round of PR review feedback (swedish-compliance):

- Add Luhn-10 check-digit validation to normalizeOrgNumber. Rejects
  structurally invalid org_numbers (wrong check digit) at the boundary
  instead of letting them propagate into SIE4 #ORGNR and SRU INFO.SRU,
  where Skatteverket and receiving accounting systems would reject
  them later anyway. Reuses the existing luhnValidate helper from
  lib/bankgiro/luhn.ts (Bankgirot 10-modulen — same algorithm applies
  to both Bolagsverket org numbers and Swedish personnummer).

- Extract normalizeOrgNumber into lib/company-lookup/normalize-org-number.ts
  so the server action and /api/company/check-org-number use the same
  rule. Previously the API route only stripped hyphens/spaces, so a
  12-digit input would miss a stored 10-digit duplicate and mislead the
  client debounce check ("not a duplicate" → submit → server rejects).

- /api/company/check-org-number now returns exists=false for
  Luhn-invalid input rather than querying the DB. The submit-time
  server action surfaces org_number_invalid, which is the right place
  for the error.

Test coverage: dedicated normalize-org-number.test.ts (10 cases
covering both-lengths, Luhn, whitespace tolerance, garbage). Updated
existing tests to use Luhn-valid numbers (real Volvo 5560125790,
synthetic personnummer 8001011231). New failing-Luhn test in
actions.test.ts. New 12-digit-normalization and
luhn-invalid-returns-false tests in route.test.ts.

Full suite: 2315 passing.

Not fixed (out of scope for this hotfix):
- 10↔12 digit round-trip fragility for personnummer born 2000+. This
  is a codebase-wide architectural choice (see lib/skatteverket/format.ts
  which uses a two-digit-year heuristic to choose 19/20 at export).
  Migrating to 12-digit storage is a separate refactor.
- Server-side re-fetch of TIC /lookup for isCeased. The trust boundary
  here is user-to-their-own-onboarding, not adversarial; doubling TIC
  API cost isn't proportionate.

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 14:47:58 +02:00
Jakob WennbergandClaude Opus 4.7 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
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
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
MattssonandClaude Opus 4.6 0dd1f5ebc1 feat: multi-tenant company refactor (GNU-19) (#153)
* feat: multi-tenant company refactor (GNU-19)

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 16:41:52 +02:00