Commit Graph
20 Commits
Author SHA1 Message Date
Mattsson 7175daee87 Bug/skv konto numbers (#498)
* feat(skattekonto): add overdue transactions handling and split logic

* feat: enhance transaction handling and loading states

- Update BalanceHero component to display last synced date and additional information about Skatteverket updates.
- Refactor BookDirectlyDialog to simplify transaction linking logic and improve UI for transaction selection.
- Revamp InvoiceInboxWorkspace layout for better responsiveness and user experience, including improved skeleton loading states.
- Introduce new loading states for ExtensionWorkspace to match the live layout and improve user feedback during data fetching.
- Implement exchange rate fetching in QuickReviewDialog, ensuring transactions are always processed in SEK with error handling for rate fetching.
- Add structured error handling for unavailable exchange rates in the transaction API.

* feat(skattekonto): add 'Skattekonto – saldo & transaktioner' scope and update authorization checks

* feat: implement reverse charge handling in supplier invoice calculations and UI
2026-05-15 16:25:05 +02:00
MattssonandClaude Opus 4.7 c8461397c8 Bug/accounting ps eu (#474)
* feat(api): implement commit functionality for journal entries

* fix(extensions): make ExtensionSettings.clear() a real delete so disconnect flows work

The 2026-03-30 multi-tenant refactor dropped all RLS policies on extension_data
and recreated only SELECT/INSERT/UPDATE. Combined with `value jsonb NOT NULL`,
every extension that called `settings.set(key, null)` to clear stored state
(cloud-backup disconnect, skatteverket OAuth/AGI cleanup, arcim-migration
consent reset) silently failed — the upsert hit the NOT NULL constraint and
the error was swallowed, leaving users stuck with stale connection rows.

Adds an `extension_data_delete` RLS policy, a `clear(key)` method backed by a
real DELETE, switches the four affected handlers, and makes `set()` throw on
Supabase error so this class of silent failure can't recur.

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

* feat(journal-entries): add draft saving functionality to journal entry form

* feat: add periodisk sammanställning report generation and CSV export

- Implemented period date helpers in `period-dates.ts` for calculating start and end dates based on period type (monthly, quarterly, yearly).
- Created `periodisk-sammanstallning.ts` to generate the periodisk sammanställning report, including data fetching, validation, and warning handling.
- Developed CSV serializer in `periodisk-sammanstallning-csv.ts` for exporting the report in SKV574008 format.
- Added new columns to `company_settings` for storing periodisk sammanställning settings and tax contact information via migration.
- Introduced a new migration to add a `paid_with_private_funds` flag to `supplier_invoices` for tracking out-of-pocket expenses.
- Updated journal entries to include the new source type for privately paid supplier invoices.

* feat(migrations): add paid_with_private_funds flag to supplier_invoices and expand journal_entries.source_type CHECK

* fix(ai_requests): drop existing policies and trigger before creating new ones

* fix(migrations): ensure extension_data has a proper DELETE policy for ExtensionSettings.clear()

* fix(supplier-invoices): update error handling for invalid input in POST request

* fix: correct capitalization in project title

* fix(migrations): resolve duplicate version 20260513120000

Two migrations shared the same timestamp prefix, causing
schema_migrations_pkey collision on Supabase preview branches.
Bump extension_data_delete_policy to 20260513120001.

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-14 01:10:44 +02:00
Jakob WennbergandClaude Opus 4.7 07a7964e8d fix(supplier-invoices): guard against duplicate payment when bank tx already booked (#461)
* fix(supplier-invoices): guard against duplicate payment when bank tx already booked

Two-pronged fix for a UX trap where a supplier invoice could be marked paid
even though the bank payment was already booked on 2440, creating a duplicate
verifikation.

Prong A — mark-paid duplicate guard: before booking, scan for an unlinked
outgoing bank transaction matching this supplier (merchant_name ILIKE) within
±2% / ±60 days. If found, return 409 SI_PAID_LIKELY_DUPLICATE with candidates
so the UI can offer "link existing" instead. Override via { force: true }.

Prong B — categorize match suggestion: when the user assigns 2440 directly on
a negative business transaction and an open supplier invoice from the same
supplier covers the same amount, return 409 TX_CATEGORIZE_SUGGEST_SI_MATCH
with candidates and route the user to match-supplier-invoice. Override via
{ confirm_no_match: true }.

Frontend dialogs added on the supplier-invoice detail page and the
transactions inbox. Partial payments skip the mark-paid guard (deliberate
action). Tests cover the 409 path, the override path, and the no-candidates
happy path on both routes.

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

* fix(supplier-invoices): apply PR review fixes to duplicate-payment guards

- Add `is_business = true` filter to mark-paid candidate query so private
  bank withdrawals don't surface as false-positive duplicates
- Escape LIKE wildcards (`%`, `_`, `\`) in both ILIKE patterns to avoid
  silent over-matching when a supplier/merchant name contains those chars
- Round paymentAmount and remaining_amount to 2 decimals before the
  partial-payment guard comparison to avoid float-equality fragility
- Require credit account to be in the 1xxx (bank/cash) series for the
  Prong B 2440 intercept so 2440 against clearing/equity accounts isn't
  misinterpreted as a supplier payment
- Extract DUPLICATE_AMOUNT_TOLERANCE_PCT (0.02) and
  DUPLICATE_DATE_WINDOW_DAYS (60) into a shared helper module with the
  LIKE-escape utility

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

* fix(supplier-invoices): broaden 244x match, audit log overrides, drop JE id from response

Second-round PR review fixes:

- Widen Prong B regex from /^2440$/ to /^244\d$/ so payments mapped to BAS
  sub-accounts (e.g. 2441 leverantörsskulder i utländsk valuta) also trigger
  the suggestion (swedish-invoice-compliance bot)
- Log a structured warning when force=true or confirm_no_match=true is honored,
  with the relevant context (amount, date, accounts) so the override is
  traceable per BFNAR 2013:2 kap 8 (behandlingshistorik)
- Drop journal_entry_id from the SI_PAID_LIKELY_DUPLICATE candidate response
  payload (data minimization, GDPR Art.5(1)(c)); the UI never rendered it

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

* fix(supplier-invoices): third-round PR review — date window on Prong B, length cap, VAT message

- Add the missing date window to the Prong B categorize candidate query
  (swedish-compliance bot): without it, an open invoice from years back can
  surface as a "match" for an unrelated bank transaction. Uses the shared
  DUPLICATE_DATE_WINDOW_DAYS against invoice_date.
- Cap supplier/merchant names to 200 chars before they enter escapeLikePattern
  (OWASP V1.2.5 / ISO A.8.28). Bounds DB work on pathological inputs.
- Log a structured warning when the mark-paid guard is skipped because the
  invoice has no resolved supplier name (BFL 5 kap 7 § — motpart should be
  identifiable; the absence is itself worth surfacing).
- Update the SI-match suggestion error and the matching UI copy to call out
  the actual compliance risk: a duplicate 244x posting double-deducts ingående
  moms (ML 8 kap 3 §), not just bookkeeping symmetry.
- Reword "Bokför på 2440 ändå" to "Bokför på leverantörsskulder ändå" now that
  the regex covers BAS sub-accounts 244x.

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

* fix(supplier-invoices): correct Prong B framing — duplicate verifikation, not VAT double-deduction

Latest swedish-compliance review correctly walked back the earlier
finding that asked for ML 8 kap 3 § VAT framing. Plain 244x
categorization via account_override does not include VAT lines (account
class 2), so the risk is a duplicate verifikation (BFL 5 kap 5 §), not
a double VAT deduction. Update both the structured error message and
the dialog body to reflect the actual mechanism.

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

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 11:38:19 +02:00
MattssonandClaude Opus 4.7 5725c25bf1 Logs/improved logging (#398)
* feat(mcp): add create_transactions tool with /pending approval gate

New MCP tool gnubok_create_transactions stages 1–10 transactions per call
as pending_operations of type create_transaction (risk: medium). Each item
becomes its own card on /pending; on confirm, the executor inserts the row
into transactions with import_source='mcp' so MCP-staged ingestion is
distinguishable from PSD2 sync. Designed for skill workflows that pull
external data (e.g., Airtable) and want the user to gate the writes.

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

* fix(bas): strip concatenated group headers from corrupted account names

A chart-data import bug had glued the next group's header onto the last
account in each preceding group across all eight bas-data class files
(e.g. account 2670 read "Utgående moms på försäljning inom EU, OSS 27
PERSONALENS SKATTER, AVGIFTER OCH LÖNEAVDRAG"). The corrupted names
surface in transaction dropdowns, ledgers, SIE exports and årsredovisning,
and risk VAT miscategorization on the OSS (2670) and blandad-verksamhet
(6999) accounts specifically.

- Cleans 69 account_name and 64 description fields across class-1..8 files
- Adds a regression test asserting no name contains a concatenated header
- Ships an idempotent safety-net migration that updates already-seeded
  chart_of_accounts rows, gated on the corrupted string so user
  customizations are preserved

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

* feat(errors): add structured error codes and handling for various operations

- Introduced a new structured error registry in `structured-errors.ts` to standardize error handling across the application.
- Added Swedish and English messages for various error scenarios, including validation, authorization, and bookkeeping errors.
- Implemented a client-side error toast in `use-error-toast.ts` to display user-friendly error messages with remediation hints.
- Created a wrapper for recording operation outcomes in `record-operation.ts`, enhancing audit capabilities for operations.
- Developed a provider call wrapper in `with-provider-call.ts` to handle external HTTP calls with structured logging and error mapping.
- Added a new SQL migration to extend the processing history with new event types and aggregate types for better operational telemetry.

* Refactor supplier API routes to use context-based logging and error handling

- Replaced direct Supabase client usage in GET and POST routes with context-based approach using `withRouteContext`.
- Enhanced error handling to provide structured error responses for supplier creation and listing.
- Updated logging to include request IDs for better traceability.
- Introduced new error codes for supplier-related operations.
- Refactored tax deadlines cron job to utilize context and improved error handling.
- Updated ESLint configuration to enforce logging practices across API and lib directories.
- Enhanced arcim migration extension with structured error handling and logging.
- Added classification for provider errors to improve user-facing error messages.
- Introduced request ID in extension context for better log correlation.

* fix(route-context): update DynamicParams type for improved type safety in route handlers

* feat(transactions): add 'create_transaction' operation to PendingOperationType

* fix(route): ensure companyId is non-nullable in loadAndDeriveAbsence function

* fix(route-context): ensure companyId is always non-null by short-circuiting with COMPANY_CONTEXT_MISSING

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 11:12:02 +02:00
Mattsson 0222e084bb Refactor bookkeeping error handling and introduce new error classes (#356)
- 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.
2026-04-23 14:49:45 +02:00
Mattsson e137a9f452 Skill/fix (#355)
* fix: ensure customer email addresses are anonymized and not displayed in tickets

* feat: add uncredit functionality for supplier invoices

- Implemented the ability to uncredit supplier invoices, restoring the original invoice status and freeing up the invoice number.
- Added confirmation dialog for uncrediting actions.
- Updated the supplier invoice detail page to show an "Undo Credit" button for credited invoices.
- Enhanced the new supplier invoice page to handle conflicts when a duplicate invoice number is detected, allowing users to uncredit the existing invoice.
- Created API endpoint for uncrediting invoices, including handling of journal entries and invoice status updates.
- Added tests for the uncredit functionality to ensure proper behavior and error handling.

* feat: implement soft-delete for credited invoices and add reversed status

* fix: update uncredit logic to handle registration journal entries and improve user feedback

* fix: retain no-op migration stub for history alignment with future index changes
2026-04-23 11:57:12 +02:00
Jakob WennbergandClaude Opus 4.7 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 WennbergandClaude Opus 4.6 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 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 fad4899cb4 fix: update RPC calls to use company_id instead of user_id for invoice and arrival number generation (#154) 2026-03-31 17:34:51 +02:00
MattssonandClaude Opus 4.6 0dd1f5ebc1 feat: multi-tenant company refactor (GNU-19) (#153)
* feat: multi-tenant company refactor (GNU-19)

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 16:41:52 +02:00
Jakob WennbergandClaude Opus 4.6 86348c80c7 feat: BFL-compliant journal descriptions and cancelled entry status (#55)
* fix: include reversed entries in all reports (general ledger, trial balance, VAT, SIE, NE, INK2)

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

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

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

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

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

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

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

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

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

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

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

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

* fix: create journal entry before marking invoice as paid

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

* chore: retrigger Greptile review

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

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 17:53:54 +01:00
Jakob WennbergandClaude Opus 4.6 a9bf24ce0b feat: enhance supplier invoices, entity-aware categorization, and new tests
Expand supplier invoice module with overdue cron job, credit note journal
entries, and event emissions on approve/mark-paid/create flows. Add entity
type (EF/AB) awareness to transaction categorization UI and category
mapping logic. Add comprehensive tests for supplier-invoice-entries,
transaction-entries, and expanded API route coverage.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 17:08:26 +01:00
Jakob WennbergandClaude Opus 4.6 03b569d708 refactor: consolidate extension system to general-only with manifest-driven architecture
- 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>
2026-02-26 14:32:56 +01:00
Jakob WennbergandClaude Opus 4.6 acb85edf4a feat: wire Zod validation into API routes, improve types and components
- Add 8 new Zod schemas (UpdateCustomer, UpdateSupplier, UpdateSupplierInvoice,
  UpdateAccount, BankUnlink, RunReconciliation, CorrectJournalEntry,
  EvaluateMappingRules) and wire validateBody() into 24 JSON-body API routes
- Remove redundant manual validation checks replaced by Zod
- Add comprehensive schema tests (222 tests)
- Improve type definitions in types/index.ts with expanded interfaces
- Refactor extension types (push-notifications, receipt-ocr) for cleaner imports
- Update transaction components (BatchCategorySelector, SwipeCategorizationView,
  QuickReviewDialog, VatTreatmentSelect) and invoice inbox workspace
- Add invoice-inbox utilities and type decoupling tests
- Fix NE-bilaga, SRU export, and invoice PDF template type usage
- Update CLAUDE.md with expanded architecture documentation

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 16:00:38 +01:00
EmilandClaude Opus 4.6 ce66a21010 feat: add document bucket auto-creation, extension defaults, and chat widget on dashboard
Auto-create documents storage bucket on first upload. Default legacy general
extensions to enabled when no toggle row exists. Add ChatWidget to dashboard
root page with open-ai-chat event support and AI assistant quick action.
Add ensureInitialized to supplier invoices route for event emission.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 13:00:16 +01:00
Emil 0a0e74fdfb Merge remote-tracking branch 'origin/main' into code-quality-improvements 2026-02-21 16:10:02 +01:00
Jakob WennbergandClaude Opus 4.6 91e2c1705a feat: per-line VAT, invoice document types, ledger-based VAT declaration, bank reconciliation, and pagination
Per-line VAT rates:
- Add generatePerRateLines() to group invoice items by vat_rate with separate
  revenue + VAT lines per rate group (invoice-entries.ts)
- Add getAvailableVatRates() and getVatTreatmentForRate() (vat-rules.ts)
- PDF template shows per-line VAT column and per-rate totals for mixed-rate invoices
- Invoice create/review UI supports per-line rate selection
- Types: add vat_rate/vat_amount to InvoiceItem, vat_rate to CreateInvoiceItemInput

Invoice document types (proforma, delivery note):
- Add InvoiceDocumentType, document_type and converted_from_id to Invoice type
- PDF hides prices for delivery notes, adds proforma notice
- Email templates support all document types
- mark-paid skips journal entries for non-invoice document types
- Migration 031: invoice_document_type

Accounting method support:
- Add AccountingMethod type (accrual/cash)
- Migration 032: add_accounting_method column to company_settings

VAT declaration rewrite:
- Rewrite to read directly from general ledger (26xx/3xxx account lines)
  instead of aggregating invoices/transactions/receipts
- ACCOUNT_RUTA mapping drives momsdeklaration boxes from GL balances

Bank reconciliation:
- Transaction ingest now pre-fetches unlinked GL lines and attempts
  auto-reconciliation during import
- Add transaction.reconciled event type
- Add ReconciliationMethod type and reconciliation_method on Transaction
- Migration 030: bank_reconciliation
- New reconciliation engine, API routes, and BankReconciliationView component

Pagination (fetchAllRows):
- New lib/supabase/fetch-all.ts overcomes PostgREST 1000-row limit
- Adopted in all report generators, SIE/SRU export, account list APIs

Fiscal period validation:
- New validate-period-duration.ts enforces max 18 months per BFL 3 kap.
- Applied in period-service.ts and fiscal-periods API

Account mapper simplification:
- Remove Levenshtein/fuzzy matching, use exact account number match only

Swedbank parser improvements:
- Support abbreviated headers (Clnr, Bokfdag, Radnr)
- Use Referens column as counterparty

Chart of accounts management:
- Add DELETE endpoint with system account and usage protection
- PUT uses partial updates
- New AccountCombobox, AddAccountDialog, EditAccountDialog, ChartOfAccountsManager

Tax deadline corrections:
- Rewrite inkomstdeklaration_ab using Skatteverket lookup table
- Rewrite arsredovisning deadline to 7 months after FY end per ÅRL 8:3

Onboarding first fiscal year:
- Add first fiscal year toggle with date pickers and 18-month validation

UI terminology:
- Change "okategoriserad/kategorisera" to "obokförd/bokföra" throughout

Report column fix:
- Fix start_date/end_date to period_start/period_end in report queries

Supplier invoice input:
- CreateSupplierInvoiceItemInput uses amount field (legacy quantity/unit_price kept)

Misc:
- SIE import uses upsert for idempotent account creation
- account-descriptions.ts falls back to BAS reference data
- Add invoice_default_notes to CompanySettings
- Update CLAUDE.md to reflect current project state

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-21 14:57:15 +01:00
EmilandClaude Opus 4.6 2f83f232c6 test: add API route tests for 10 critical endpoints
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>
2026-02-20 16:12:51 +01:00
Jakob WennbergandClaude Opus 4.6 838dc6b8b5 refactor: clean up codebase, remove dead code and obsolete docs
Remove influencer-era documentation, unused components, boilerplate
assets, and ghost tiktok cron job. Add supplier invoice management,
document API routes, and PWA icons. Replace boilerplate README.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-20 12:23:17 +01:00