* fix(journal-entry): add error logging for delete operation in journal entries
* fix(journal-entry): enhance error logging and add tests for delete_last_voucher functionality
* fix(journal-entry): restore enforce_journal_entry_immutability function to handle DELETE and un-reversal updates
* fix(tests): refactor delete_last_voucher tests to use insertPostedEntryWithLines for consistency
* feat(skatteverket): production-ready momsdeklaration submission
Brings the Skatteverket extension up to a state where it can ship moms
declaration submission to Vercel production. Verified end-to-end against
SKV's Komplett testtjänst — all 8 momsdeklaration operations tested
(kontrollera, spara/hämta/radera utkast, lås/lås upp, hämta inlämnade,
hämta beslutade) plus signing-link return.
Bundles three coherent changes:
1. Skatteverket extension (the main work)
- extensions.config.json: enable `skatteverket`, drop `invoice-inbox`
and `ai-agent` (those were enabled in config but lacked AWS env vars
in prod, so they loaded but failed at runtime)
- lib/reports/vat-declaration.ts: extend ACCOUNT_RUTA to populate
Ruta 06 (uttag 3401–3403), Ruta 20–24 (reverse-charge bases from
4xxx cost accounts), Ruta 50 (import 4545–4547), and Ruta 42
(3404/3994/3980); delete the supplier-type heuristic that made
Ruta 20 and Ruta 23 always 0
- extensions/general/skatteverket/lib/token-store.ts: work around
three real prod schema-drift issues — wrong column on read/delete
(was `company_id`, schema only has `user_id`), missing
UNIQUE(user_id) constraint that makes UPSERT fail (switched to
DELETE+INSERT), missing RLS policies (switched to service-role
client). Refresh path now reuses existing row's company_id when
none is passed.
- extensions/general/skatteverket/index.ts: 9 sites switched from
ctx.companyId to ctx.userId for the token-store key; pass
companyId from the OAuth callback
- extensions/general/skatteverket/types.ts + components/reports/
SkatteverketPanel.tsx: align field names with v1.0.24 RAML
(signeringsLank/kontrollResultat/resultat/kod/status/beskrivning).
Without this, the signing link never displayed.
- SkatteverketPanel: add Lås upp + Radera utkast + Hämta utkast +
Hämta beslut buttons so the full lifecycle is reachable from the UI
- lib/reports/__tests__/vat-declaration.test.ts: rewritten to match
the refactored calculator; new fixtures for cost-account-based
reverse charge (Ruta 20/21/22/23/24), Ruta 50 import, Ruta 06
uttag, Ruta 42 expansion; SKV §4.1.1.4 cross-field contract checks
- supabase/migrations/20260428120000_skatteverket_tokens_user_id_unique.sql:
idempotently adds the missing UNIQUE(user_id) constraint
- scripts/*: dev-only helpers used during the prod-of-test
verification (create test company, seed VAT data, inspect token
state, etc.)
2. Journal-entries cancelled-status filter
- app/api/bookkeeping/journal-entries/route.ts: when no status filter
is supplied, exclude `cancelled` entries by default
- supabase/migrations/20260428153500_journal_entries_with_related_exclude_statuses.sql
3. Swedish e-invoicing skill (reference docs only — no runtime code)
- .claude/skills/swedish-e-invoicing/
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(skatteverket): address PR review findings
- panel: handleFetchDraft read `result.data?.last` (typo) — switched to
`result.data?.locked` to match the field defined in
SkatteverketUtkastResponse and the v1.0.24 RAML. The "(låst)" suffix on
the success message would silently never appear before this fix.
- api-client: getValidToken had no concurrency guard, so two parallel
SKV requests from the same user could both call /token with the same
refresh_token. SKV rotates the refresh_token on first use, so the
second call would 401 with REFRESH_EXHAUSTED-adjacent failures. With
the new 6-button UI on SkatteverketPanel, rapid clicks made this a
realistic trigger. Added an in-process Promise map keyed on userId
that coalesces concurrent refresh attempts; cross-process races are
mitigated by re-reading tokens inside the critical section before
calling refreshAccessToken (if another process refreshed already, we
use the newer token instead of burning the old refresh_token).
- migration 20260428120000: dedup query used `created_at < max(...)`,
which failed to remove duplicates inserted in the same second. The
subsequent ALTER TABLE … ADD CONSTRAINT would then abort. Switched
to ctid (Postgres physical row identifier) to break timestamp ties.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(skatteverket): throw on token-store SELECT error before destructive DELETE
The company_id pre-read in storeTokens used destructuring that discarded
the error field. If the service-role SELECT failed for any reason (network
blip, overloaded DB, transient permissions issue), `existing` became null,
`resolvedCompanyId` stayed undefined, and execution fell through to the
DELETE. The old row got deleted successfully, then the INSERT omitted
company_id and failed with the NOT NULL constraint violation — leaving
the user with no token row at all and forcing a fresh BankID handshake.
Now we capture the SELECT error and throw before the DELETE runs.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(bookkeeping): implement reset bookkeeping functionality with safeguards
* feat(migrations): restore relaxed trigger for retroactive first fiscal year
- Introduced new error classes for better error categorization:
- JournalEntryNotBalancedError
- FiscalPeriodNotFoundError
- EntryDateOutsideFiscalPeriodError
- JournalEntryNotFoundError
- CannotReverseNonPostedError
- CannotCorrectNonPostedError
- EntryAlreadyReversedError
- CurrencyRevaluationAlreadyExistsError
- InvalidMappingResultError
- BookkeepingDatabaseError
- Updated existing functions in engine.ts and transaction-entries.ts to throw specific errors instead of generic ones.
- Enhanced error response handling in get-error-message.ts to provide localized messages for new error types.
- Added unit tests for new error classes and error handling functions to ensure correctness and coverage.
* feat: 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>
* 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>
* 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.
* 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>
* feat: separate AR/AP/accounting into distinct nav groups (#92)
Split the flat "Finans" sidebar group into three visually distinct
sections — Försäljning (AR), Inköp (AP), and Redovisning — so users
coming from Fortnox immediately find customer invoicing and supplier
invoices as top-level concepts.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: journal entry detail view, correction chain, and account name display
- Add journal entry detail page at /bookkeeping/[id] with full entry view
- Add correction chain API and component showing storno relationships
- Add JournalEntryStatusBadge component for entry status display
- Show debit/credit account names in template picker and review dialogs
- Expand client-side BAS account name mapping with additional accounts
- Show account codes on transaction inbox suggestion buttons
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: address review feedback — N+1 query, duplicate name, nav dedup
- Batch reverse-lookup into single query per BFS iteration (was N+1)
- Differentiate account 2393 from 2893 in display names
- Extract shared loop for desktop/mobile nav group rendering
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: add date sorting support to journal entries API
Add sort_date query parameter (asc/desc) that orders results by
entry_date instead of the default voucher_series + voucher_number.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: add date sort toggle to journal entry list
Add ascending/descending sort button next to the missing attachment
filter. Defaults to newest first, toggles between nyast/äldst först.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: add date range filter inputs to journal entry list
Add Från/Till text inputs (YYYY-MM-DD) for filtering entries by date
range. Applies on blur or Enter, with an X button to clear both fields.
Both fields are optional and work independently of the sort toggle.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
- Update BAS account catalog with comprehensive SRU codes and K2 flags
- Add currency revaluation service with tests and API route
- Add expenses page and account deletion API
- Enhance booking templates with new patterns and improved tests
- Improve transaction categorization with template picker and description matching
- Polish dashboard, onboarding, import, and transaction UIs
- Refactor year-end service for multi-step closing
- Move SRU generator to ne-bilaga, remove standalone SRU export
- Remove unused dev docs, mock data, and extension hooks
- Add invoice delivery note sequences migration
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Change missing extension env vars from throw to log.warn (graceful degradation)
- Move initialized flag after all init steps complete
- Add error.tsx and loading.tsx for dashboard error boundaries
- Fix cron route auth header checks
- Add ensureInitialized() to journal entry reverse and invoice mark-paid/sent routes
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Remove all sector-specific extensions (construction, ecommerce, export,
hotel, restaurant, tech) — only general-purpose extensions remain
- Move NE-bilaga and SRU export from extensions to core reports (lib/reports/)
- Move moms-box-mapping from extensions/export/shared to lib/vat/
- Replace per-extension API routes with catch-all dispatcher
(app/api/extensions/ext/[...path]/route.ts)
- Add manifest.json for each extension with metadata, env vars, and deps
- Add api-routes.ts pattern for extension-defined API endpoints
- Add code generation scripts (generate-extension-registry, create-extension)
- Add extensions.config.json for opt-in extension loading
- Add extensions.schema.json for config validation
- Add email service interface with noop default (lib/email/service.ts)
- Add CI workflow (core-build.yml) to verify core builds with zero extensions
- Add migration 045: expand account_type CHECK for untaxed_reserves
- Update CLAUDE.md with comprehensive extension system documentation
- Update all report engines and bookkeeping services for new imports
- Clean up extensions.schema.json to only list existing extensions
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add rollback logic to storno-service correctEntry to restore ledger
consistency if step 2 fails after reversal. Add correction API route at
/api/bookkeeping/journal-entries/[id]/correct. Add CorrectionEntryDialog
component and correction button to JournalEntryList.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add test coverage for invoices, transactions, bookkeeping, and supplier
invoice API routes. Includes test helpers (createMockRequest,
parseJsonResponse, createMockRouteParams, createQueuedMockSupabase) and
fixture factories (makeInvoice, makeCustomer, makeSupplier,
makeSupplierInvoice, makeCompanySettings).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>