5725c25bf1
* 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>
216 lines
6.6 KiB
TypeScript
216 lines
6.6 KiB
TypeScript
import type { SupabaseClient } from '@supabase/supabase-js'
|
|
import type { CoreEvent, CoreEventType } from '@/lib/events/types'
|
|
import type { EntityType, RawTransaction, IngestResult, IngestOptions } from '@/types'
|
|
|
|
// ============================================================
|
|
// Extension Marketplace Types
|
|
// ============================================================
|
|
|
|
/** Extension category for marketplace grouping */
|
|
export type ExtensionCategory = 'accounting' | 'reports' | 'import' | 'operations'
|
|
|
|
/** Sector slugs for extension organization */
|
|
export type SectorSlug = 'general'
|
|
|
|
/** How an extension gets its data */
|
|
export type ExtensionDataPattern = 'core' | 'manual' | 'both'
|
|
|
|
/** Dashboard quick action declared by an extension */
|
|
export interface QuickActionDefinition {
|
|
label: string
|
|
description: string
|
|
icon: string
|
|
href?: string
|
|
event?: string
|
|
order?: number
|
|
}
|
|
|
|
/** Extension metadata for the marketplace and workspace routing */
|
|
export interface ExtensionDefinition {
|
|
slug: string
|
|
name: string
|
|
sector: SectorSlug
|
|
category: ExtensionCategory
|
|
description: string
|
|
longDescription: string
|
|
icon: string
|
|
entityTypes?: EntityType[]
|
|
dataPattern: ExtensionDataPattern
|
|
readsCoreTables?: string[]
|
|
hasOwnData?: boolean
|
|
quickAction?: QuickActionDefinition
|
|
/** Notice shown when enabling — e.g. external subscription requirement */
|
|
subscriptionNotice?: string
|
|
}
|
|
|
|
/** Sector definition with its extensions */
|
|
export interface Sector {
|
|
slug: SectorSlug
|
|
name: string
|
|
icon: string
|
|
description: string
|
|
extensions: ExtensionDefinition[]
|
|
}
|
|
|
|
// ============================================================
|
|
// Extension Interface & Supporting Types
|
|
// ============================================================
|
|
|
|
/** A route exposed by an extension (page route) */
|
|
export interface RouteDefinition {
|
|
path: string
|
|
label: string
|
|
}
|
|
|
|
/**
|
|
* An API route exposed by an extension.
|
|
*
|
|
* Auth/context modes (mutually exclusive — combining throws at dispatch time):
|
|
* - default: requires auth AND a resolved company; ctx is passed to the handler
|
|
* - `skipAuth: true`: no auth, no ctx (e.g. OAuth callbacks)
|
|
* - `skipCompanyContext: true`: auth required, no ctx (pre-onboarding routes)
|
|
*/
|
|
export interface ApiRouteDefinition {
|
|
method: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH'
|
|
path: string
|
|
/** Skip auth check for this route (e.g. OAuth callbacks from external providers) */
|
|
skipAuth?: boolean
|
|
/**
|
|
* Require auth but NOT a resolved company context. Use for routes that
|
|
* legitimately run during onboarding (before the user has a company) —
|
|
* e.g. TIC /lookup used by Step2CompanyDetails to fetch company info
|
|
* while the user types their org number. Handler is called without a
|
|
* ctx argument; handlers that opt in must tolerate a missing context.
|
|
*
|
|
* Must NOT be combined with `skipAuth: true` — the dispatcher treats
|
|
* that as a misconfiguration and returns 500.
|
|
*/
|
|
skipCompanyContext?: boolean
|
|
handler: (request: Request, ctx?: ExtensionContext) => Promise<Response>
|
|
}
|
|
|
|
/** Sidebar navigation item added by an extension */
|
|
export interface SidebarItem {
|
|
label: string
|
|
icon?: string
|
|
path: string
|
|
order?: number
|
|
}
|
|
|
|
/** Report type added by an extension */
|
|
export interface ReportDefinition {
|
|
id: string
|
|
name: string
|
|
description: string
|
|
}
|
|
|
|
/** Settings panel exposed by an extension */
|
|
export interface SettingsPanelDefinition {
|
|
label: string
|
|
path: string
|
|
}
|
|
|
|
/** Tax code definition added by an extension */
|
|
export interface TaxCodeDefinition {
|
|
code: string
|
|
rate: number
|
|
description: string
|
|
}
|
|
|
|
/** Dimension type definition added by an extension */
|
|
export interface DimensionDefinition {
|
|
id: string
|
|
name: string
|
|
description: string
|
|
}
|
|
|
|
/** Mapping rule type added by an extension */
|
|
export interface MappingRuleTypeDefinition {
|
|
id: string
|
|
name: string
|
|
description: string
|
|
}
|
|
|
|
/** Event handler registration for an extension */
|
|
export interface ExtensionEventHandler {
|
|
eventType: CoreEventType
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
handler: (payload: any, ctx?: ExtensionContext) => Promise<void> | void
|
|
}
|
|
|
|
/** Logger interface for extensions */
|
|
export interface ExtensionLogger {
|
|
info(message: string, ...args: unknown[]): void
|
|
warn(message: string, ...args: unknown[]): void
|
|
error(message: string, ...args: unknown[]): void
|
|
}
|
|
|
|
/** Settings accessor for extension-scoped key-value data */
|
|
export interface ExtensionSettings {
|
|
get<T>(key?: string): Promise<T | null>
|
|
set<T>(key: string, value: T): Promise<void>
|
|
}
|
|
|
|
/** Storage accessor wrapping Supabase storage */
|
|
export interface ExtensionStorage {
|
|
download(bucket: string, path: string): Promise<{ data: Blob | null; error?: string }>
|
|
upload(bucket: string, path: string, data: ArrayBuffer, options?: { contentType?: string }): Promise<{ path: string; error?: string }>
|
|
getPublicUrl(bucket: string, path: string): string
|
|
}
|
|
|
|
/** Core services exposed to extensions */
|
|
export interface ExtensionServices {
|
|
ingestTransactions(supabase: SupabaseClient, companyId: string, userId: string, raw: RawTransaction[], options?: IngestOptions): Promise<IngestResult>
|
|
}
|
|
|
|
/** Context passed to extension lifecycle hooks and event handlers */
|
|
export interface ExtensionContext {
|
|
userId: string
|
|
companyId: string
|
|
extensionId: string
|
|
/**
|
|
* Stable id for the inbound HTTP request — `req_<uuid>`.
|
|
* Included in the response envelope and in the `X-Request-Id` header so
|
|
* support staff can grep stdout logs by it.
|
|
*/
|
|
requestId?: string
|
|
supabase: SupabaseClient
|
|
emit(event: CoreEvent): Promise<void>
|
|
settings: ExtensionSettings
|
|
storage: ExtensionStorage
|
|
log: ExtensionLogger
|
|
services: ExtensionServices
|
|
}
|
|
|
|
/**
|
|
* Extension interface — the contract for all add-ons.
|
|
*
|
|
* Extensions declare what they provide (routes, event handlers, sidebar items, etc.)
|
|
* and the registry wires them into the system.
|
|
*/
|
|
export interface Extension {
|
|
id: string
|
|
name: string
|
|
version: string
|
|
sector?: SectorSlug
|
|
|
|
// Surfaces
|
|
routes?: RouteDefinition[]
|
|
apiRoutes?: ApiRouteDefinition[]
|
|
sidebarItems?: SidebarItem[]
|
|
eventHandlers?: ExtensionEventHandler[]
|
|
mappingRuleTypes?: MappingRuleTypeDefinition[]
|
|
reportTypes?: ReportDefinition[]
|
|
settingsPanel?: SettingsPanelDefinition
|
|
taxCodes?: TaxCodeDefinition[]
|
|
dimensionTypes?: DimensionDefinition[]
|
|
|
|
/** Named services this extension provides to core via registry lookup */
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
services?: Record<string, (...args: any[]) => Promise<any>>
|
|
|
|
// Lifecycle hooks
|
|
onInstall?(ctx: ExtensionContext): Promise<void>
|
|
onUninstall?(ctx: ExtensionContext): Promise<void>
|
|
}
|