Commit Graph
11 Commits
Author SHA1 Message Date
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
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
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 d8e0a22495 feat: counterparty templates, Skatteverket extension, complete VAT form (#117)
* 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>

* feat: counterparty templates, Skatteverket extension, VAT form completeness, and UI cleanup

- Add counterparty-based categorization templates (learned from user approvals
  and auto-ingestion) with fuzzy matching in the mapping engine
- Add Skatteverket extension for direct VAT declaration submission via API
- Complete VAT declaration form with all 30 SKV 4700 boxes (ruta 08, 35-42, 50, 60-62)
- Fix ruta 49 formula to include import VAT (ruta 60+61+62)
- Simplify dashboard UI: remove redundant icons from stat cards, customer cards,
  invoice list, supplier invoices; use Badge variants consistently
- Add SkatteverketPanel component to reports page
- Add categorization_templates and skatteverket_tokens migrations
- Update tests and helpers for new types

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

* fix: address PR review feedback — VAT detection, migration timestamps, dedup

- Fix detectVatTreatment to derive actual rate (12%/6%) from VAT line
  description instead of hardcoding standard_25
- Rename skatteverket_tokens migration to 20260324120001 to avoid
  duplicate timestamp with categorization_templates (fixes Supabase
  deployment failure)
- Make refreshAccessToken accept previousRefreshCount param to enforce
  refresh limit contract at the type level
- Fix rate limiter TOCTOU by claiming slot before await
- Extract formatRedovisare/formatRedovisningsperiod to shared
  lib/skatteverket/format.ts — eliminates duplication between
  mappers.ts and SkatteverketPanel.tsx

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-24 13:41:12 +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 3e7fa45ed6 feat: transaction categorization UX improvements and description matching
Add journal entry preview, human-readable account names, auto-apply VAT,
fallback template suggestions, example prompts, invoice match comparison,
and batch result feedback. Also includes user-description-match extension,
describe/batch-describe API routes, improved AI categorization with multi-
suggestion support, and template embedding search.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 16:02:06 +01:00
EmilandClaude Opus 4.6 2f953c1031 fix: validate account mappings and handle reverse charge in categorization
Add validation for non-empty debit/credit accounts before creating journal
entries. Generate fiktiv moms lines (2645/2614) for EU reverse charge expenses.
Change default unmapped expense account from 6900 to 6991. Add tests for
reverse charge handling and exhaustive category mapping coverage.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 12:58:50 +01:00
Emil fa5e14de83 Fixing bug 2026-02-21 17:14:08 +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