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>
This commit is contained in:
Mattsson
2026-03-31 16:41:52 +02:00
committed by GitHub
co-authored by Claude Opus 4.6
parent 1756af6c5e
commit 0dd1f5ebc1
290 changed files with 7690 additions and 1339 deletions
+79
View File
@@ -1,6 +1,56 @@
// Entity types
export type EntityType = 'enskild_firma' | 'aktiebolag'
// Company role for multi-tenant access
export type CompanyRole = 'owner' | 'admin' | 'member' | 'viewer'
// Team (consulting firm) roles and source tracking
export type TeamRole = 'owner' | 'admin' | 'member'
export type MemberSource = 'direct' | 'team'
// Team (consulting firm grouping)
export interface Team {
id: string
name: string
created_by: string
created_at: string
updated_at: string
}
// Company (multi-tenant identity)
export interface Company {
id: string
name: string
org_number: string | null
entity_type: EntityType
created_by: string
team_id: string | null
archived_at: string | null
created_at: string
updated_at: string
}
// Company membership
export interface CompanyMember {
id: string
company_id: string
user_id: string
role: CompanyRole
invited_by: string | null
joined_at: string
created_at: string
updated_at: string
}
// User preferences (cross-company)
export interface UserPreferences {
id: string
user_id: string
active_company_id: string | null
created_at: string
updated_at: string
}
// Transaction categories
export type TransactionCategory =
| 'income_services'
@@ -81,6 +131,7 @@ export interface Profile {
export interface CompanySettings {
id: string
user_id: string
company_id: string
// Entity info
entity_type: EntityType
@@ -151,6 +202,7 @@ export interface CompanySettings {
export interface BankConnection {
id: string
user_id: string
company_id: string
bank_name: string
provider: string
@@ -198,6 +250,7 @@ export type ImportSource =
export interface Transaction {
id: string
user_id: string
company_id: string
// Source
bank_connection_id: string | null
@@ -258,6 +311,7 @@ export type BankFileImportStatus = 'pending' | 'processing' | 'completed' | 'fai
export interface BankFileImport {
id: string
user_id: string
company_id: string
filename: string
file_hash: string
file_format: string
@@ -277,6 +331,7 @@ export interface BankFileImport {
export interface Customer {
id: string
user_id: string
company_id: string
// Basic info
name: string
@@ -313,6 +368,7 @@ export interface Customer {
export interface Supplier {
id: string
user_id: string
company_id: string
name: string
supplier_type: SupplierType
@@ -349,6 +405,7 @@ export interface Supplier {
export interface SupplierInvoice {
id: string
user_id: string
company_id: string
supplier_id: string
arrival_number: number
@@ -442,6 +499,7 @@ export interface SupplierInvoicePayment {
export interface InvoicePayment {
id: string
user_id: string
company_id: string
invoice_id: string
payment_date: string
@@ -461,6 +519,7 @@ export interface InvoicePayment {
export interface Invoice {
id: string
user_id: string
company_id: string
customer_id: string
// Invoice number (auto-generated)
@@ -799,6 +858,7 @@ export type MappingRuleType =
export interface BASAccount {
id: string
user_id: string
company_id: string
account_number: string
account_name: string
account_class: number
@@ -821,6 +881,7 @@ export interface BASAccount {
export interface FiscalPeriod {
id: string
user_id: string
company_id: string
name: string
period_start: string
period_end: string
@@ -840,6 +901,7 @@ export interface FiscalPeriod {
export interface JournalEntry {
id: string
user_id: string
company_id: string
fiscal_period_id: string
voucher_number: number
voucher_series: string
@@ -882,6 +944,7 @@ export interface JournalEntryLine {
export interface MappingRule {
id: string
user_id: string | null
company_id: string | null
rule_name: string
rule_type: MappingRuleType
priority: number
@@ -954,6 +1017,7 @@ export interface LinePatternEntry {
export interface CategorizationTemplate {
id: string
user_id: string
company_id: string
counterparty_name: string
counterparty_aliases: string[]
debit_account: string
@@ -975,6 +1039,7 @@ export interface CategorizationTemplate {
export interface AccountBalance {
id: string
user_id: string
company_id: string
fiscal_period_id: string
account_number: string
account_id: string | null
@@ -1077,6 +1142,7 @@ export type PendingOperationStatus = 'pending' | 'committed' | 'rejected'
export interface PendingOperation {
id: string
user_id: string
company_id: string
operation_type: PendingOperationType
status: PendingOperationStatus
title: string
@@ -1182,6 +1248,7 @@ export type DeadlinePriority = 'critical' | 'important' | 'normal'
export interface Deadline {
id: string
user_id: string
company_id: string
title: string
due_date: string
due_time: string | null
@@ -1278,6 +1345,7 @@ export type NotificationType =
export interface NotificationLog {
id: string
user_id: string
company_id: string | null
notification_type: NotificationType
reference_id: string
days_before: number
@@ -1293,6 +1361,7 @@ export interface NotificationLog {
export interface CalendarFeed {
id: string
user_id: string
company_id: string
feed_token: string
is_active: boolean
include_tax_deadlines: boolean
@@ -1344,6 +1413,7 @@ export type SIEImportStatus = 'pending' | 'mapped' | 'completed' | 'failed'
export interface SIEImport {
id: string
user_id: string
company_id: string
filename: string
file_hash: string
org_number: string | null
@@ -1367,6 +1437,7 @@ export interface SIEImport {
export interface SIEAccountMapping {
id: string
user_id: string
company_id: string
source_account: string
source_name: string | null
target_account: string
@@ -1389,6 +1460,7 @@ export type DocumentClassificationType = 'supplier_invoice' | 'receipt' | 'gover
export interface InvoiceInboxItem {
id: string
user_id: string
company_id: string
status: InboxItemStatus
source: InboxItemSource
email_from: string | null
@@ -1436,6 +1508,7 @@ export type ReceiptStatus = 'pending' | 'processing' | 'extracted' | 'confirmed'
export interface Receipt {
id: string
user_id: string
company_id: string
// Image storage
image_url: string
@@ -1763,6 +1836,7 @@ export interface CreditNote extends Invoice {
export interface ExtensionDataRecord {
id: string
user_id: string
company_id: string
extension_id: string
key: string
value: Record<string, unknown>
@@ -1820,6 +1894,7 @@ export type DocumentUploadSource =
export interface DocumentAttachment {
id: string
user_id: string
company_id: string
storage_path: string
file_name: string
file_size_bytes: number | null
@@ -1872,6 +1947,7 @@ export type AuditAction =
export interface AuditLogEntry {
id: string
user_id: string
company_id: string | null
action: AuditAction
table_name: string | null
record_id: string | null
@@ -1889,6 +1965,7 @@ export interface AuditLogEntry {
export interface CostCenter {
id: string
user_id: string
company_id: string
code: string
name: string
is_active: boolean
@@ -1899,6 +1976,7 @@ export interface CostCenter {
export interface Project {
id: string
user_id: string
company_id: string
code: string
name: string
is_active: boolean
@@ -1998,6 +2076,7 @@ export interface InvoiceReminder {
id: string
invoice_id: string
user_id: string
company_id: string
reminder_level: 1 | 2 | 3
sent_at: string
email_to: string