* feat(skatteverket): rewrite AGI flow against real Skatteverket RAML The previous AGI client posted JSON to URL paths that don't exist on Skatteverket's gateway and used invented field names. POST /underlag actually accepts application/xml, and the lock/kvittenser operations live on the separate hanteraredovisningsperiod API. Verified against dev_docs/arbetsgivardeklaration-inlamning(1.7.7) and arbetsgivardeklaration-hantera-redovisningsperiod(1.2.8) RAMLs. - Replace fictional types with real schemas (kontrollresultat, granskningsunderlag, kvittenser, error envelope) - Rewrite agi-client into 9 functions matching the documented flow: /underlag (XML) -> kontrollresultat -> spara -> skapaGranskningsunderlag -> kvittenser, plus las/lasUpp on the hantera API - Drop agi-mappers entirely; lib/salary/agi/xml-generator.ts already produces schema-valid XML, so the extension just feeds agi_declarations.xml_content to POST /underlag - Extend skvRequest with a contentType option so AGI can post XML - AGIPanel state machine: underlag_submitted -> awaiting_signing -> signed, with kontrollresultat polling and normalized findings - Add the agd OAuth scope (confirmed from SKV's Tjanstebeskrivning Arbetsgivardeklaration inlamning v1.7, section 4.1.2.2) - Add Skatteverket connect step to NewUserChecklist alongside the existing SIE/old-system import and bank steps; track hasSkatteverketConnected in OnboardingProgress - Update orchestrator route + tests to point at the new /agi/submit endpoint - Declare new optional base-URL env vars in the manifest Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(skatteverket): address PR review findings on AGI flow - Surface INCORRECT_DATA felrapport link in AGIPanel skapaGranskningsunderlag returns 409 with a felrapport URL when SKV rejects the underlag. The link was persisted as `signeringslank` with status `underlag_rejected`, but the render condition only fired for `awaiting_signing`, leaving the link unreachable. Add a distinct destructive-styled block so the user can open the felrapport in Mina Sidor. - /agi/underlag DELETE clears local submission state Add optional `period` query param. When supplied, clear `agi_submission_{period}` directly. When not, fall back to scanning recent agi_submission_* keys for the matching inlamningId. Without this, an aborted underlag left a stale `underlag_submitted` entry in extension_data and the UI couldn't progress. - Re-add salary-run status guard inside loadAGIXml The orchestrator at app/api/salary/runs/[id]/agi/submit/route.ts has this check, but the extension endpoint is also reachable directly from AGIPanel and must enforce it itself. Per BFL 5 kap and SFL 26 kap, AGI must reflect finalised payroll data; submitting from a draft/cancelled run would emit incorrect figures. - Move agi_declarations.status='exported' from /agi/submit to /agi/spara Setting status on underlag-ingest was wrong because a DONE_REJECTED kontrollresultat would leave the row falsely marked as exported. The transition now happens only after the spara call commits the underlag to Eget utrymme. /agi/spara accepts salaryRunId in the body for the fast path and falls back to scanning agi_submission_* state otherwise. - Move salary_runs.agi_submitted_at stamp to kvittenser observation The orchestrator was stamping at underlag-ingest, but no later code updated the column on signing. Removed the orchestrator stamp; the /agi/kvittenser handler now stamps salary_runs.agi_submitted_at to kvittens.signeradTid (mirroring SKV's own timestamp) when it pins the receipt to the matching agi_declarations row. - Tighten misleading JSDoc in agi-client.ts taBortSparadInlamning is on the inlämning API, not hantera; the old layout grouped it under a "hantera API" heading and tripped an automated reviewer. Restructured into separate "period management" and "cleanup" blocks. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(skatteverket): address Swedish compliance review on AGI flow Follow-up to review on https://github.com/erp-mafia/gnubok/pull/391. - Migration adds 'pending_signature' to agi_declarations.status Reusing 'exported' for the spara→kvittens interval misstated the filing outcome — Eget utrymme is a staging area, not a filing — which conflicts with BFNAR 2013:2 kap 8 / BFL 5 kap 5§ behandlingshistorik faithfulness. /agi/spara now sets 'pending_signature'; /agi/kvittenser later promotes to 'submitted' when a uuidKvittens is observed. - AGIPanel auto-polls /agi/kvittenser at 30s, 2 min and 5 min after the signing link is created Previously the kvittens (and therefore salary_runs.agi_submitted_at) was only stamped if the user manually returned to the panel and clicked "Hämta kvittens". Without that follow-up the audit trail showed a NULL submitted-at for an AGI that had actually been filed. Background polls capture the kvittens for the common case where the user signs in Mina Sidor and never returns to gnubok. Cleanup on unmount via useRef + useEffect. - Distinct MISSING_SCOPE error code on 403 invalid_scope Existing tokens lack the new 'agd' scope and surface as a generic ACCESS_DENIED today. The compliance reviewer pointed out that operators may interpret this as a data error and submit a corrected AGI with altered figures. New SkatteverketAuthError code maps SKV's invalid_scope body to a clear "reconnect via Inställningar → Skatteverket" message; routes to 401 (token-level remediation). - Refine deadline copy in AGIPanel The standard AGI deadline is the 12th regardless of company size; the 17th only applies in January and August for employers with turnover ≤ 40 MSEK. Surface that nuance instead of saying just "12:e". Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(skatteverket): server-side kvittens reconciliation cron Round 2 of compliance review on https://github.com/erp-mafia/gnubok/pull/391. - /api/extensions/skatteverket/agi/kvittenser/cron Walks every agi_declarations row in 'pending_signature' status, fetches kvittenser via the matching token, and on a hit promotes the row to 'submitted' + stamps salary_runs.agi_submitted_at. Authoritative source for the audit trail per BFNAR 2013:2 kap 8 / BFL 5 kap 5§ — the AGIPanel client-side timers from the previous round remain as the fast-path UX, but no longer carry the audit-trail responsibility on their own. Per-row errors are skipped, not abort-the-run. 50s budget. Scheduled every 2 hours in vercel.json. - AGIStatus union now includes 'pending_signature' Without this update, downstream code reading the union would have rejected the new status as unknown. The migration extending the DB CHECK constraint shipped in the previous commit; this brings the type layer into sync. - Stale comment update in AGIPanel.tsx Referred to status='exported' from before the rename. Now reads 'pending_signature', matching the actual handler behavior. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(skatteverket): close audit-trail gaps from compliance round 3 - Cron now writes submitted_by from the token-owning auth.users row Previously left NULL with a "system actor" comment. The token row was created when the operator authenticated with BankID, and the kvittens' signeradAv refers to the same person — so writing the user_id from skatteverket_tokens captures actor traceability without inventing a system identity. Closes the BFL 5 kap 6§ / BFNAR 2013:2 kap 8 gap on cron-reconciled rows. - /agi/spara monotonicity guard Adds .in('status', ['generated', 'exported']) to the row update so a delayed /agi/spara call after the cron (or interactive /agi/kvittenser) has already promoted the row to 'submitted'/'accepted' won't silently regress it back to 'pending_signature'. behandlingshistorik must advance only. - DONE_REJECTED / DONE_FAILED → status='rejected' /agi/kontrollresultat handler now flips the matching agi_declarations row to 'rejected' on a terminal SKV failure, using the same cached-submission-state lookup pattern /agi/spara already uses. Without this the row sat at 'generated' indefinitely even though SKV considered the underlag failed. Same monotonicity guard prevents regressing a successfully-filed row. - Deadline criterion: lönesumma, not omsättning AGIPanel pendingText. SFL 26 kap's relaxed-deadline criterion (17:e in Jan/Aug) is the employer's total taxable wages, not turnover. Internal reference (.claude/skills/swedish-payroll/references/agi-filing.md) used the colloquial "turnover"; statutory wording is "lönesumma". Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(skatteverket): close round-4 audit-trail and UX gaps - agi_submitted_at: NULL when signeradTid absent Both /agi/kvittenser handler and the kvittens cron previously fell back to new Date().toISOString() if SKV's kvittens lacked signeradTid. Substituting wall-clock now() falsifies the filing moment in behandlingshistorik (BFNAR 2013:2 kap 8 / BFL 5 kap 6§). Now leaves the column NULL and logs a warning. Status flip to 'submitted' still happens — the audit gap was timing only. - Proactive missing-agd-scope banner SkatteverketConnectPanel and AGIPanel now warn when the stored token lacks the agd scope. Tokens issued before the agd rollout would otherwise 403 with invalid_scope at submission time, often too close to the AGI deadline. SkatteverketConnectPanel mirrors the existing "skattekonto saknas" pattern; AGIPanel surfaces a banner in the connected state and links to /settings/skatteverket. - Granskningsunderlag isError keys on tillstand only Previous check mixed HTTP 409 with the INCORRECT_DATA tillstand string. A future SKV addition like RECEIVING returned with HTTP 200 would have slipped through as awaiting_signing. Now keys solely on tillstand: only LOCKED_FOR_SIGNING / UNLOCKED are treated as signable; everything else (INCORRECT_DATA, RECEIVING, CALCULATING, SIGNING) routes to underlag_rejected. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(skatteverket): close round-5 audit-trail and recovery gaps - agi_submitted_at: stamp with reconciliation time when signeradTid absent Round 4 left the column NULL on missing signeradTid to avoid falsifying the signing moment. Round 5 pointed out that NULL hides that the filing *occurred* — also a behandlingshistorik integrity violation. Resolution: presence of uuidKvittens proves SKV signed and accepted the AGI, so we stamp with signeradTid || now() and warn-log when fallback is used. Both /agi/kvittenser handler and the kvittens cron. - Persist signeradAv + full kvittens in agi_declarations.response_data submitted_by is the auth.users UUID we have on hand (the polling / reconciling user). The legally load-bearing signer identity is kvittens.signeradAv (a personnummer) — which the token user_id does NOT necessarily match (e.g. bookkeeper vs deklarationsombud). The existing response_data jsonb column now holds the full kvittens record, preserving signeradAv for the audit trail (BFL 5 kap 6§ / BFNAR 2013:2 kap 8) without a schema change. Cron path also marks reconciledBy='cron'. - /agi/spara monotonicity: allow recovery from 'rejected' Previously .in('status', ['generated', 'exported']) excluded rejected rows, so a successful re-submission after a prior rejection couldn't promote the row to pending_signature — it silently stayed rejected. The xml-route reuses the same agi_declarations row when re-generating XML, so this is the realistic recovery path. Added 'rejected' to the allowed-from list. 'submitted'/'accepted' still blocked (no regression from filed states). - Fix misleading agi-client.ts comment Claimed users could "fix the errors in Mina Sidor" after a DONE_REJECTED save. Mina Sidor doesn't expose in-place editing; the correct recovery is to regenerate XML and resubmit. Updated the agiSparaUnderlag JSDoc to describe the actual flow. - Deadline copy: "vars sammanlagda lönesumma understiger 40 MSEK" Reads more cleanly than "≤ 40 MSEK" and matches the phrasing the compliance reviewer suggested. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(skatteverket): tighten /agi/spara guard and clarify deadline copy (round 6) - Drop 'exported' from /agi/spara allowed-from states Audit confirmed no code path writes status='exported' today; the value is preserved in the schema (and union) for the legacy manual-download path that no longer has a writer. Allowing the spara handler to flip an 'exported' row to 'pending_signature' would conflate two distinct filing attempts on a single row, weakening the chain of custody (BFL 5 kap 6§). Tightened to .in(['generated', 'rejected']) — same recovery path for re-submission after rejection, no path for the dormant state. - Deadline copy: explicit "per år" qualifier The 40 MSEK threshold is annual lönesumma, not per-payment. Adding "per år" closes the (admittedly thin) misread the compliance reviewer flagged. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2658 lines
66 KiB
TypeScript
2658 lines
66 KiB
TypeScript
// 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'
|
|
| 'income_products'
|
|
| 'income_other'
|
|
| 'expense_equipment'
|
|
| 'expense_software'
|
|
| 'expense_travel'
|
|
| 'expense_office'
|
|
| 'expense_marketing'
|
|
| 'expense_professional_services'
|
|
| 'expense_education'
|
|
| 'expense_representation'
|
|
| 'expense_consumables'
|
|
| 'expense_vehicle'
|
|
| 'expense_telecom'
|
|
| 'expense_bank_fees'
|
|
| 'expense_card_fees'
|
|
| 'expense_currency_exchange'
|
|
| 'expense_other'
|
|
| 'private'
|
|
| 'uncategorized'
|
|
|
|
// Customer types for VAT handling
|
|
export type CustomerType =
|
|
| 'individual' // Swedish private person
|
|
| 'swedish_business' // Swedish company
|
|
| 'eu_business' // EU company (needs VAT validation)
|
|
| 'non_eu_business' // Non-EU company
|
|
|
|
// Invoice status
|
|
export type InvoiceStatus = 'draft' | 'sent' | 'paid' | 'partially_paid' | 'overdue' | 'cancelled' | 'credited'
|
|
|
|
// Invoice document type
|
|
export type InvoiceDocumentType = 'invoice' | 'proforma' | 'delivery_note'
|
|
|
|
// Supplier types
|
|
export type SupplierType = 'swedish_business' | 'eu_business' | 'non_eu_business'
|
|
|
|
// Supplier invoice status
|
|
// 'reversed' marks a credit note whose journal entry was storno-reversed via
|
|
// "Ångra kreditering". The row is preserved (BFL 7 kap) rather than hard-deleted.
|
|
export type SupplierInvoiceStatus = 'registered' | 'approved' | 'paid' | 'partially_paid' | 'overdue' | 'disputed' | 'credited' | 'reversed'
|
|
|
|
// VAT treatment
|
|
export type VatTreatment =
|
|
| 'standard_25' // 25% Swedish VAT
|
|
| 'reduced_12' // 12% reduced rate
|
|
| 'reduced_6' // 6% reduced rate
|
|
| 'reverse_charge' // EU reverse charge (0%)
|
|
| 'export' // Non-EU export (0%)
|
|
| 'exempt' // VAT exempt
|
|
|
|
// Accounting method (bokföringsmetod)
|
|
export type AccountingMethod = 'accrual' | 'cash'
|
|
|
|
// Moms reporting period
|
|
export type MomsPeriod = 'monthly' | 'quarterly' | 'yearly'
|
|
|
|
// Reconciliation method
|
|
export type ReconciliationMethod = 'auto_exact' | 'auto_date_range' | 'auto_reference' | 'auto_fuzzy' | 'manual'
|
|
|
|
// Processing history (behandlingshistorik) — event-driven audit trail per BFNAR 2013:2 kap 8
|
|
|
|
export type ProcessingHistoryActorType = 'user' | 'system' | 'llm' | 'cron' | 'api_key'
|
|
|
|
export interface ProcessingHistoryActor {
|
|
type: ProcessingHistoryActorType
|
|
id: string
|
|
label?: string
|
|
}
|
|
|
|
export type ProcessingHistoryAggregateType =
|
|
| 'Document'
|
|
| 'BankTransaction'
|
|
| 'MatchProposal'
|
|
| 'Verifikation'
|
|
| 'CounterpartyTemplate'
|
|
| 'Period'
|
|
| 'Migration'
|
|
| 'System'
|
|
|
|
export interface ProcessingHistoryEvent {
|
|
event_id: string
|
|
seq: number
|
|
company_id: string
|
|
correlation_id: string
|
|
causation_id: string | null
|
|
aggregate_type: ProcessingHistoryAggregateType
|
|
aggregate_id: string
|
|
event_type: string // open type — validated at runtime against processing_event_types registry
|
|
payload: Record<string, unknown>
|
|
payload_schema_version: number
|
|
actor: ProcessingHistoryActor
|
|
rubric_version: string | null
|
|
occurred_at: string
|
|
appended_at: string
|
|
}
|
|
|
|
// Bank connection status
|
|
export type BankConnectionStatus = 'pending' | 'active' | 'expired' | 'revoked' | 'error'
|
|
|
|
// Currency types
|
|
export type Currency = 'SEK' | 'EUR' | 'USD' | 'GBP' | 'NOK' | 'DKK'
|
|
|
|
// Profile (extends auth.users)
|
|
export interface Profile {
|
|
id: string
|
|
email: string
|
|
full_name: string | null
|
|
avatar_url: string | null
|
|
created_at: string
|
|
updated_at: string
|
|
}
|
|
|
|
// Company Settings
|
|
export interface CompanySettings {
|
|
id: string
|
|
user_id: string
|
|
company_id: string
|
|
|
|
// Entity info
|
|
entity_type: EntityType
|
|
company_name: string | null
|
|
trade_name: string | null
|
|
org_number: string | null
|
|
|
|
// Address
|
|
address_line1: string | null
|
|
address_line2: string | null
|
|
postal_code: string | null
|
|
city: string | null
|
|
country: string
|
|
|
|
// Contact
|
|
phone: string | null
|
|
email: string | null
|
|
website: string | null
|
|
|
|
// Tax registration
|
|
pays_salaries: boolean
|
|
f_skatt: boolean
|
|
vat_registered: boolean
|
|
vat_number: string | null
|
|
moms_period: MomsPeriod | null
|
|
|
|
// Fiscal year
|
|
fiscal_year_start_month: number // 1-12
|
|
// Transient first-year fields (used during onboarding, not persisted in DB)
|
|
is_first_fiscal_year?: boolean
|
|
first_year_start?: string
|
|
first_year_end?: string
|
|
|
|
// Preliminary tax
|
|
preliminary_tax_monthly: number | null
|
|
|
|
// Bank details for invoices
|
|
bank_name: string | null
|
|
clearing_number: string | null
|
|
account_number: string | null
|
|
bankgiro: string | null
|
|
plusgiro: string | null
|
|
iban: string | null
|
|
bic: string | null
|
|
|
|
// Accounting method
|
|
accounting_method: AccountingMethod
|
|
|
|
// Invoice settings
|
|
invoice_prefix: string | null
|
|
next_invoice_number: number
|
|
next_delivery_note_number: number
|
|
invoice_default_days: number
|
|
invoice_default_notes: string | null
|
|
|
|
// Bookkeeping lock
|
|
bookkeeping_locked_through: string | null
|
|
auto_lock_period_days: number | null
|
|
|
|
// Voucher series
|
|
default_voucher_series: string
|
|
|
|
// Invoice PDF settings
|
|
ore_rounding: boolean
|
|
invoice_show_ocr: boolean
|
|
invoice_show_bankgiro: boolean
|
|
invoice_show_plusgiro: boolean
|
|
invoice_late_fee_text: string | null
|
|
invoice_credit_terms_text: string | null
|
|
|
|
// Logo
|
|
logo_url: string | null
|
|
|
|
// Onboarding
|
|
onboarding_step: number
|
|
onboarding_complete: boolean
|
|
|
|
// Sector
|
|
sector_slug: string | null
|
|
|
|
// Sandbox
|
|
is_sandbox: boolean
|
|
|
|
// Agent auto-commit. When enabled, low-risk pending_operations staged by
|
|
// trusted agents (api_key, mcp_oauth) skip human review. High-risk ops
|
|
// (period close, year-end, send_invoice, etc.) always require approval.
|
|
agent_auto_commit_enabled: boolean
|
|
agent_auto_commit_max_amount: number | null
|
|
|
|
// Timestamps
|
|
created_at: string
|
|
updated_at: string
|
|
}
|
|
|
|
// Bank Connection
|
|
export interface BankConnection {
|
|
id: string
|
|
user_id: string
|
|
company_id: string
|
|
|
|
bank_name: string
|
|
provider: string
|
|
|
|
// Enable Banking specific
|
|
session_id: string | null
|
|
authorization_id: string | null
|
|
|
|
// Account info
|
|
accounts_data: BankAccount[]
|
|
|
|
// Status
|
|
status: BankConnectionStatus
|
|
|
|
// Consent
|
|
consent_expires: string | null
|
|
last_synced_at: string | null
|
|
error_message: string | null
|
|
|
|
created_at: string
|
|
updated_at: string
|
|
}
|
|
|
|
export interface BankAccount {
|
|
uid: string // Enable Banking account UID
|
|
iban: string | null
|
|
name: string | null
|
|
currency: Currency
|
|
balance: number | null
|
|
balance_updated_at?: string | null
|
|
}
|
|
|
|
// Import source identifiers
|
|
export type ImportSource =
|
|
| 'enable_banking'
|
|
| 'csv_nordea'
|
|
| 'csv_seb'
|
|
| 'csv_swedbank'
|
|
| 'csv_handelsbanken'
|
|
| 'csv_generic'
|
|
| 'camt053'
|
|
| 'manual'
|
|
|
|
// Transaction
|
|
export interface Transaction {
|
|
id: string
|
|
user_id: string
|
|
company_id: string
|
|
|
|
// Source
|
|
bank_connection_id: string | null
|
|
external_id: string | null // For deduplication
|
|
|
|
// Details
|
|
date: string
|
|
description: string
|
|
amount: number // Positive = income, negative = expense
|
|
currency: Currency
|
|
|
|
// For non-SEK transactions
|
|
amount_sek: number | null
|
|
exchange_rate: number | null
|
|
exchange_rate_date: string | null
|
|
|
|
// Categorization
|
|
category: TransactionCategory
|
|
is_business: boolean | null // null = uncategorized
|
|
|
|
// Linked invoice (for matching)
|
|
invoice_id: string | null
|
|
|
|
// Linked supplier invoice (for matching)
|
|
supplier_invoice_id: string | null
|
|
|
|
// Potential invoice match (suggested, not confirmed)
|
|
potential_invoice_id: string | null
|
|
|
|
// Potential supplier invoice match (suggested, not confirmed)
|
|
potential_supplier_invoice_id: string | null
|
|
|
|
// Bookkeeping
|
|
journal_entry_id: string | null
|
|
mcc_code: number | null
|
|
merchant_name: string | null
|
|
|
|
// Receipt link
|
|
receipt_id: string | null
|
|
|
|
// Reconciliation
|
|
reconciliation_method: ReconciliationMethod | null
|
|
|
|
// Import tracking
|
|
import_source: string | null
|
|
reference: string | null // OCR number, Bankgiro reference
|
|
|
|
// Notes
|
|
notes: string | null
|
|
|
|
created_at: string
|
|
updated_at: string
|
|
}
|
|
|
|
// Bank File Import (tracking table for file-based imports)
|
|
export type BankFileImportStatus = 'pending' | 'processing' | 'completed' | 'failed'
|
|
|
|
export interface BankFileImport {
|
|
id: string
|
|
user_id: string
|
|
company_id: string
|
|
filename: string
|
|
file_hash: string
|
|
file_format: string
|
|
transaction_count: number
|
|
imported_count: number
|
|
duplicate_count: number
|
|
matched_count: number
|
|
date_from: string | null
|
|
date_to: string | null
|
|
status: BankFileImportStatus
|
|
error_message: string | null
|
|
created_at: string
|
|
updated_at: string
|
|
}
|
|
|
|
// Customer
|
|
export interface Customer {
|
|
id: string
|
|
user_id: string
|
|
company_id: string
|
|
|
|
// Basic info
|
|
name: string
|
|
customer_type: CustomerType
|
|
|
|
// Contact
|
|
email: string | null
|
|
phone: string | null
|
|
|
|
// Address
|
|
address_line1: string | null
|
|
address_line2: string | null
|
|
postal_code: string | null
|
|
city: string | null
|
|
country: string
|
|
|
|
// Tax info
|
|
org_number: string | null
|
|
vat_number: string | null
|
|
vat_number_validated: boolean
|
|
vat_number_validated_at: string | null
|
|
|
|
// Payment
|
|
default_payment_terms: number // Days
|
|
|
|
// Notes
|
|
notes: string | null
|
|
|
|
created_at: string
|
|
updated_at: string
|
|
}
|
|
|
|
// Supplier
|
|
export interface Supplier {
|
|
id: string
|
|
user_id: string
|
|
company_id: string
|
|
|
|
name: string
|
|
supplier_type: SupplierType
|
|
|
|
email: string | null
|
|
phone: string | null
|
|
|
|
address_line1: string | null
|
|
address_line2: string | null
|
|
postal_code: string | null
|
|
city: string | null
|
|
country: string
|
|
|
|
org_number: string | null
|
|
vat_number: string | null
|
|
|
|
bankgiro: string | null
|
|
plusgiro: string | null
|
|
bank_account: string | null
|
|
iban: string | null
|
|
bic: string | null
|
|
|
|
default_expense_account: string | null
|
|
default_payment_terms: number
|
|
default_currency: string
|
|
|
|
notes: string | null
|
|
|
|
created_at: string
|
|
updated_at: string
|
|
}
|
|
|
|
// Supplier Invoice
|
|
export interface SupplierInvoice {
|
|
id: string
|
|
user_id: string
|
|
company_id: string
|
|
supplier_id: string
|
|
|
|
arrival_number: number
|
|
supplier_invoice_number: string
|
|
|
|
invoice_date: string
|
|
due_date: string
|
|
received_date: string
|
|
delivery_date: string | null
|
|
|
|
status: SupplierInvoiceStatus
|
|
|
|
currency: string
|
|
exchange_rate: number | null
|
|
exchange_rate_date: string | null
|
|
|
|
subtotal: number
|
|
subtotal_sek: number | null
|
|
vat_amount: number
|
|
vat_amount_sek: number | null
|
|
total: number
|
|
total_sek: number | null
|
|
|
|
vat_treatment: VatTreatment
|
|
reverse_charge: boolean
|
|
|
|
payment_reference: string | null
|
|
paid_at: string | null
|
|
paid_amount: number
|
|
remaining_amount: number
|
|
|
|
is_credit_note: boolean
|
|
credited_invoice_id: string | null
|
|
|
|
registration_journal_entry_id: string | null
|
|
payment_journal_entry_id: string | null
|
|
|
|
transaction_id: string | null
|
|
document_id: string | null
|
|
|
|
notes: string | null
|
|
|
|
created_at: string
|
|
updated_at: string
|
|
|
|
// Relations (populated when fetched)
|
|
supplier?: Supplier
|
|
items?: SupplierInvoiceItem[]
|
|
payments?: SupplierInvoicePayment[]
|
|
}
|
|
|
|
// Supplier Invoice Item
|
|
export interface SupplierInvoiceItem {
|
|
id: string
|
|
supplier_invoice_id: string
|
|
|
|
sort_order: number
|
|
description: string
|
|
quantity: number
|
|
unit: string
|
|
unit_price: number
|
|
line_total: number
|
|
|
|
account_number: string
|
|
vat_code: string | null
|
|
vat_rate: number
|
|
vat_amount: number
|
|
|
|
created_at: string
|
|
}
|
|
|
|
// Supplier Invoice Payment (partial payments)
|
|
export interface SupplierInvoicePayment {
|
|
id: string
|
|
supplier_invoice_id: string
|
|
|
|
payment_date: string
|
|
amount: number
|
|
currency: string
|
|
exchange_rate: number | null
|
|
exchange_rate_difference: number
|
|
|
|
journal_entry_id: string | null
|
|
transaction_id: string | null
|
|
notes: string | null
|
|
|
|
created_at: string
|
|
}
|
|
|
|
// Invoice Payment (partial payments)
|
|
export interface InvoicePayment {
|
|
id: string
|
|
user_id: string
|
|
company_id: string
|
|
invoice_id: string
|
|
|
|
payment_date: string
|
|
amount: number
|
|
currency: string
|
|
exchange_rate: number | null
|
|
exchange_rate_difference: number
|
|
|
|
journal_entry_id: string | null
|
|
transaction_id: string | null
|
|
notes: string | null
|
|
|
|
created_at: string
|
|
}
|
|
|
|
// Invoice
|
|
export interface Invoice {
|
|
id: string
|
|
user_id: string
|
|
company_id: string
|
|
customer_id: string
|
|
|
|
// Invoice number (auto-generated at first send; null while draft)
|
|
invoice_number: string | null
|
|
|
|
// Dates
|
|
invoice_date: string
|
|
due_date: string
|
|
delivery_date: string | null
|
|
|
|
// Status
|
|
status: InvoiceStatus
|
|
|
|
// Currency
|
|
currency: Currency
|
|
|
|
// Exchange rate (if non-SEK)
|
|
exchange_rate: number | null
|
|
exchange_rate_date: string | null
|
|
|
|
// Amounts
|
|
subtotal: number
|
|
subtotal_sek: number | null
|
|
|
|
vat_amount: number
|
|
vat_amount_sek: number | null
|
|
|
|
total: number
|
|
total_sek: number | null
|
|
|
|
// VAT
|
|
vat_treatment: VatTreatment
|
|
vat_rate: number
|
|
moms_ruta: string | null // For Swedish VAT reporting (05, 39, 40, etc.)
|
|
|
|
// Reference
|
|
your_reference: string | null
|
|
our_reference: string | null
|
|
|
|
// Notes
|
|
notes: string | null
|
|
|
|
// Reverse charge text (auto-added for EU B2B)
|
|
reverse_charge_text: string | null
|
|
|
|
// Credit note reference
|
|
credited_invoice_id: string | null
|
|
|
|
// Document type (invoice, proforma, delivery_note)
|
|
document_type: InvoiceDocumentType
|
|
|
|
// Conversion tracking (proforma -> invoice)
|
|
converted_from_id: string | null
|
|
|
|
// Payment tracking
|
|
paid_at: string | null
|
|
paid_amount: number | null
|
|
remaining_amount: number
|
|
|
|
created_at: string
|
|
updated_at: string
|
|
|
|
// Relations (populated when fetched)
|
|
customer?: Customer
|
|
items?: InvoiceItem[]
|
|
payments?: InvoicePayment[]
|
|
}
|
|
|
|
// Invoice Item
|
|
export interface InvoiceItem {
|
|
id: string
|
|
invoice_id: string
|
|
|
|
// Order
|
|
sort_order: number
|
|
|
|
// Description
|
|
description: string
|
|
|
|
// Quantity
|
|
quantity: number
|
|
unit: string // 'st', 'tim', 'dag', etc.
|
|
|
|
// Price
|
|
unit_price: number
|
|
|
|
// Calculated
|
|
line_total: number
|
|
|
|
// Per-line VAT
|
|
vat_rate: number
|
|
vat_amount: number
|
|
|
|
created_at: string
|
|
}
|
|
|
|
// Tax Rates (reference table)
|
|
export interface TaxRate {
|
|
id: string
|
|
|
|
// Type
|
|
rate_type: 'egenavgifter' | 'bolagsskatt' | 'arbetsgivaravgifter' | 'vat' | 'municipal'
|
|
|
|
// Rate
|
|
rate: number
|
|
|
|
// Validity
|
|
valid_from: string
|
|
valid_to: string | null
|
|
|
|
// Description
|
|
description: string
|
|
}
|
|
|
|
// Form types for creating/updating
|
|
|
|
export interface CreateCustomerInput {
|
|
name: string
|
|
customer_type: CustomerType
|
|
email?: string
|
|
phone?: string
|
|
address_line1?: string
|
|
address_line2?: string
|
|
postal_code?: string
|
|
city?: string
|
|
country?: string
|
|
org_number?: string
|
|
vat_number?: string
|
|
default_payment_terms?: number
|
|
notes?: string
|
|
}
|
|
|
|
export interface CreateSupplierInput {
|
|
name: string
|
|
supplier_type: SupplierType
|
|
email?: string
|
|
phone?: string
|
|
address_line1?: string
|
|
address_line2?: string
|
|
postal_code?: string
|
|
city?: string
|
|
country?: string
|
|
org_number?: string
|
|
vat_number?: string
|
|
bankgiro?: string
|
|
plusgiro?: string
|
|
bank_account?: string
|
|
iban?: string
|
|
bic?: string
|
|
default_expense_account?: string
|
|
default_payment_terms?: number
|
|
default_currency?: string
|
|
notes?: string
|
|
}
|
|
|
|
export interface CreateSupplierInvoiceInput {
|
|
supplier_id: string
|
|
supplier_invoice_number: string
|
|
invoice_date: string
|
|
due_date: string
|
|
delivery_date?: string
|
|
currency?: string
|
|
exchange_rate?: number
|
|
vat_treatment?: VatTreatment
|
|
reverse_charge?: boolean
|
|
payment_reference?: string
|
|
notes?: string
|
|
items: CreateSupplierInvoiceItemInput[]
|
|
}
|
|
|
|
export interface CreateSupplierInvoiceItemInput {
|
|
description: string
|
|
amount: number
|
|
account_number: string
|
|
vat_rate?: number
|
|
vat_code?: string
|
|
// Legacy fields (backward compat, ignored when amount is set)
|
|
quantity?: number
|
|
unit?: string
|
|
unit_price?: number
|
|
}
|
|
|
|
export interface CreateInvoiceInput {
|
|
customer_id: string
|
|
invoice_date: string
|
|
due_date: string
|
|
currency: Currency
|
|
document_type?: InvoiceDocumentType
|
|
your_reference?: string
|
|
our_reference?: string
|
|
notes?: string
|
|
items: CreateInvoiceItemInput[]
|
|
}
|
|
|
|
export interface CreateInvoiceItemInput {
|
|
description: string
|
|
quantity: number
|
|
unit: string
|
|
unit_price: number
|
|
vat_rate?: number
|
|
}
|
|
|
|
export interface CreateTransactionInput {
|
|
date: string
|
|
description: string
|
|
amount: number
|
|
currency: Currency
|
|
category?: TransactionCategory
|
|
is_business?: boolean
|
|
notes?: string
|
|
}
|
|
|
|
// API Response types
|
|
export interface ApiResponse<T> {
|
|
data?: T
|
|
error?: string
|
|
}
|
|
|
|
export interface PaginatedResponse<T> {
|
|
data: T[]
|
|
count: number
|
|
page: number
|
|
pageSize: number
|
|
totalPages: number
|
|
}
|
|
|
|
// VAT validation response
|
|
export interface VatValidationResult {
|
|
valid: boolean
|
|
name?: string
|
|
address?: string
|
|
country_code?: string
|
|
vat_number?: string
|
|
error?: string
|
|
}
|
|
|
|
// Exchange rate response
|
|
export interface ExchangeRate {
|
|
currency: Currency
|
|
rate: number
|
|
date: string
|
|
}
|
|
|
|
// Dashboard summary types
|
|
export interface DashboardSummary {
|
|
// Income
|
|
total_income_ytd: number
|
|
total_income_mtd: number
|
|
|
|
// Expenses
|
|
total_expenses_ytd: number
|
|
total_expenses_mtd: number
|
|
|
|
// Net
|
|
net_income_ytd: number
|
|
net_income_mtd: number
|
|
|
|
// Tax estimates
|
|
estimated_tax: TaxEstimate
|
|
|
|
// Alerts
|
|
uncategorized_count: number
|
|
unpaid_invoices_count: number
|
|
unpaid_invoices_total: number
|
|
overdue_invoices_count: number
|
|
|
|
// Bank
|
|
bank_balance: number | null
|
|
available_balance: number | null // After tax reservations
|
|
}
|
|
|
|
export interface TaxEstimate {
|
|
// For EF
|
|
egenavgifter?: number
|
|
income_tax?: number // Municipal tax (kommunalskatt)
|
|
state_tax?: number // State tax (statlig skatt) - 20% on high incomes
|
|
grundavdrag?: number // Basic deduction applied
|
|
|
|
// For AB
|
|
bolagsskatt?: number
|
|
|
|
// Common
|
|
moms_to_pay: number
|
|
total_tax_liability: number
|
|
|
|
// Comparison with preliminary
|
|
preliminary_paid_ytd: number
|
|
difference: number // Positive = underpaying
|
|
|
|
}
|
|
|
|
// ============================================================
|
|
// BAS Kontoplan & Bookkeeping Types
|
|
// ============================================================
|
|
|
|
// Risk levels for mapping rules
|
|
export type RiskLevel = 'NONE' | 'LOW' | 'MEDIUM' | 'HIGH' | 'VERY_HIGH'
|
|
|
|
// Account types
|
|
export type AccountType = 'asset' | 'equity' | 'liability' | 'revenue' | 'expense' | 'untaxed_reserves'
|
|
export type NormalBalance = 'debit' | 'credit'
|
|
export type PlanType = 'k1' | 'full_bas'
|
|
|
|
// Journal entry source
|
|
export type JournalEntrySourceType =
|
|
| 'manual'
|
|
| 'bank_transaction'
|
|
| 'invoice_created'
|
|
| 'invoice_paid'
|
|
| 'invoice_cash_payment'
|
|
| 'credit_note'
|
|
| 'salary_payment'
|
|
| 'opening_balance'
|
|
| 'year_end'
|
|
| 'storno'
|
|
| 'correction'
|
|
| 'import'
|
|
| 'system'
|
|
| 'supplier_invoice_registered'
|
|
| 'supplier_invoice_paid'
|
|
| 'supplier_invoice_cash_payment'
|
|
| 'supplier_credit_note'
|
|
| 'currency_revaluation'
|
|
|
|
// Journal entry status
|
|
export type JournalEntryStatus = 'draft' | 'posted' | 'reversed' | 'cancelled'
|
|
|
|
// Mapping rule type
|
|
export type MappingRuleType =
|
|
| 'mcc_code'
|
|
| 'merchant_name'
|
|
| 'description_pattern'
|
|
| 'amount_threshold'
|
|
| 'combined'
|
|
|
|
// BAS Account
|
|
export interface BASAccount {
|
|
id: string
|
|
user_id: string
|
|
company_id: string
|
|
account_number: string
|
|
account_name: string
|
|
account_class: number
|
|
account_group: string
|
|
account_type: AccountType
|
|
normal_balance: NormalBalance
|
|
plan_type: PlanType
|
|
is_active: boolean
|
|
is_system_account: boolean
|
|
default_vat_code: string | null
|
|
description: string | null
|
|
sru_code: string | null
|
|
k2_excluded: boolean
|
|
sort_order: number
|
|
created_at: string
|
|
updated_at: string
|
|
}
|
|
|
|
// Fiscal Period (Räkenskapsår)
|
|
export interface FiscalPeriod {
|
|
id: string
|
|
user_id: string
|
|
company_id: string
|
|
name: string
|
|
period_start: string
|
|
period_end: string
|
|
is_closed: boolean
|
|
closed_at: string | null
|
|
locked_at: string | null
|
|
retention_expires_at: string | null
|
|
opening_balances_set: boolean
|
|
closing_entry_id: string | null
|
|
opening_balance_entry_id: string | null
|
|
previous_period_id: string | null
|
|
created_at: string
|
|
updated_at: string
|
|
}
|
|
|
|
// Journal Entry (Verifikation)
|
|
export interface JournalEntry {
|
|
id: string
|
|
user_id: string
|
|
company_id: string
|
|
fiscal_period_id: string
|
|
voucher_number: number
|
|
voucher_series: string
|
|
entry_date: string
|
|
description: string
|
|
source_type: JournalEntrySourceType
|
|
source_id: string | null
|
|
status: JournalEntryStatus
|
|
committed_at: string | null
|
|
reversed_by_id: string | null
|
|
reverses_id: string | null
|
|
correction_of_id: string | null
|
|
attachment_urls: string[] | null
|
|
notes: string | null
|
|
commit_method: string | null
|
|
rubric_version: string | null
|
|
source_voucher_series: string | null
|
|
source_voucher_number: number | null
|
|
created_at: string
|
|
updated_at: string
|
|
// Relations
|
|
lines?: JournalEntryLine[]
|
|
// Set by list_fiscal_period_entries_with_related when the entry was
|
|
// returned as a follow-up from a different fiscal period than the one
|
|
// being viewed. Absent from plain PostgREST responses.
|
|
out_of_period?: boolean
|
|
}
|
|
|
|
// Journal Entry Line
|
|
export interface JournalEntryLine {
|
|
id: string
|
|
journal_entry_id: string
|
|
account_number: string
|
|
account_id: string | null
|
|
debit_amount: number
|
|
credit_amount: number
|
|
currency: string
|
|
amount_in_currency: number | null
|
|
exchange_rate: number | null
|
|
line_description: string | null
|
|
tax_code: string | null
|
|
cost_center: string | null
|
|
project: string | null
|
|
sort_order: number
|
|
created_at: string
|
|
}
|
|
|
|
// Mapping Rule
|
|
export interface MappingRule {
|
|
id: string
|
|
user_id: string | null
|
|
company_id: string | null
|
|
rule_name: string
|
|
rule_type: MappingRuleType
|
|
priority: number
|
|
// Matching
|
|
mcc_codes: number[] | null
|
|
merchant_pattern: string | null
|
|
description_pattern: string | null
|
|
amount_min: number | null
|
|
amount_max: number | null
|
|
// Targets
|
|
debit_account: string | null
|
|
credit_account: string | null
|
|
vat_treatment: string | null
|
|
vat_debit_account: string | null
|
|
vat_credit_account: string | null
|
|
// Risk
|
|
risk_level: RiskLevel
|
|
default_private: boolean
|
|
requires_review: boolean
|
|
confidence_score: number
|
|
// Capitalization
|
|
capitalization_threshold: number | null
|
|
capitalized_debit_account: string | null
|
|
// Source tracking
|
|
source: 'auto' | 'user_description' | 'system'
|
|
user_description: string | null
|
|
template_id: string | null
|
|
// Meta
|
|
is_active: boolean
|
|
created_at: string
|
|
updated_at: string
|
|
}
|
|
|
|
// Mapping engine result
|
|
export interface MappingResult {
|
|
rule: MappingRule | null
|
|
template_id?: string
|
|
debit_account: string
|
|
credit_account: string
|
|
risk_level: RiskLevel
|
|
confidence: number
|
|
requires_review: boolean
|
|
default_private: boolean
|
|
vat_lines: VatJournalLine[]
|
|
all_lines_complete?: boolean // when true, vat_lines contains ALL non-settlement lines
|
|
description: string
|
|
}
|
|
|
|
// VAT journal line (auto-generated)
|
|
export interface VatJournalLine {
|
|
account_number: string
|
|
debit_amount: number
|
|
credit_amount: number
|
|
description: string
|
|
}
|
|
|
|
// Categorization template source
|
|
export type CategorizationTemplateSource = 'sie_import' | 'user_approved' | 'sni_default' | 'auto_learned' | 'ai_corrected'
|
|
|
|
// Multi-line booking pattern entry
|
|
export interface LinePatternEntry {
|
|
account: string
|
|
type: 'business' | 'vat' | 'tax'
|
|
side: 'debit' | 'credit'
|
|
ratio?: number // proportion of NON-VAT amount (business + tax ratios sum to ~1.0)
|
|
vat_rate?: number // applied to FULL amount via rate/(1+rate) (vat type only)
|
|
}
|
|
|
|
// Per-tenant counterparty-based categorization template
|
|
export interface CategorizationTemplate {
|
|
id: string
|
|
user_id: string
|
|
company_id: string
|
|
counterparty_name: string
|
|
counterparty_aliases: string[]
|
|
debit_account: string
|
|
credit_account: string
|
|
vat_treatment: VatTreatment | null
|
|
vat_account: string | null
|
|
category: TransactionCategory | null
|
|
line_pattern: LinePatternEntry[] | null
|
|
occurrence_count: number
|
|
confidence: number
|
|
last_seen_date: string | null
|
|
source: CategorizationTemplateSource
|
|
is_active: boolean
|
|
created_at: string
|
|
updated_at: string
|
|
}
|
|
|
|
// Booking template library categories
|
|
export type BookingTemplateCategory =
|
|
| 'eu_trade'
|
|
| 'tax_account'
|
|
| 'private_transfer'
|
|
| 'salary'
|
|
| 'representation'
|
|
| 'year_end'
|
|
| 'vat'
|
|
| 'financial'
|
|
| 'other'
|
|
|
|
// Booking template library line
|
|
export interface BookingTemplateLibraryLine {
|
|
account: string
|
|
label: string
|
|
side: 'debit' | 'credit'
|
|
type: 'business' | 'vat' | 'settlement'
|
|
ratio?: number
|
|
vat_rate?: number
|
|
}
|
|
|
|
// Booking template library entry (system, team, or company-scoped)
|
|
export interface BookingTemplateLibrary {
|
|
id: string
|
|
company_id: string | null
|
|
team_id: string | null
|
|
created_by: string | null
|
|
name: string
|
|
description: string
|
|
category: BookingTemplateCategory
|
|
entity_type: 'all' | EntityType
|
|
lines: BookingTemplateLibraryLine[]
|
|
is_system: boolean
|
|
is_active: boolean
|
|
created_at: string
|
|
updated_at: string
|
|
}
|
|
|
|
// Account Balance (cached)
|
|
export interface AccountBalance {
|
|
id: string
|
|
user_id: string
|
|
company_id: string
|
|
fiscal_period_id: string
|
|
account_number: string
|
|
account_id: string | null
|
|
opening_debit: number
|
|
opening_credit: number
|
|
period_debit: number
|
|
period_credit: number
|
|
closing_debit: number
|
|
closing_credit: number
|
|
created_at: string
|
|
updated_at: string
|
|
}
|
|
|
|
// Report types
|
|
export interface TrialBalanceRow {
|
|
account_number: string
|
|
account_name: string
|
|
account_class: number
|
|
opening_debit: number
|
|
opening_credit: number
|
|
period_debit: number
|
|
period_credit: number
|
|
closing_debit: number
|
|
closing_credit: number
|
|
}
|
|
|
|
export interface IncomeStatementSection {
|
|
title: string
|
|
rows: { account_number: string; account_name: string; amount: number }[]
|
|
subtotal: number
|
|
}
|
|
|
|
export interface IncomeStatementReport {
|
|
revenue_sections: IncomeStatementSection[]
|
|
total_revenue: number
|
|
expense_sections: IncomeStatementSection[]
|
|
total_expenses: number
|
|
financial_sections: IncomeStatementSection[]
|
|
total_financial: number
|
|
net_result: number
|
|
period: { start: string; end: string }
|
|
}
|
|
|
|
export interface BalanceSheetSection {
|
|
title: string
|
|
rows: { account_number: string; account_name: string; amount: number }[]
|
|
subtotal: number
|
|
}
|
|
|
|
export interface BalanceSheetReport {
|
|
asset_sections: BalanceSheetSection[]
|
|
total_assets: number
|
|
equity_liability_sections: BalanceSheetSection[]
|
|
total_equity_liabilities: number
|
|
period: { start: string; end: string }
|
|
}
|
|
|
|
export interface ResultatrapportRow {
|
|
account_number: string
|
|
account_name: string
|
|
current_period: number
|
|
prior_period: number
|
|
}
|
|
|
|
export interface ResultatrapportGroup {
|
|
class: number
|
|
class_label: string
|
|
rows: ResultatrapportRow[]
|
|
subtotal_current: number
|
|
subtotal_prior: number
|
|
}
|
|
|
|
export interface ResultatrapportReport {
|
|
groups: ResultatrapportGroup[]
|
|
net_result_current: number
|
|
net_result_prior: number
|
|
period: { start: string; end: string }
|
|
prior_period: { start: string; end: string } | null
|
|
}
|
|
|
|
export interface BalansrapportRow {
|
|
account_number: string
|
|
account_name: string
|
|
ib: number
|
|
ub: number
|
|
period_change: number
|
|
}
|
|
|
|
export interface BalansrapportGroup {
|
|
class: number
|
|
class_label: string
|
|
rows: BalansrapportRow[]
|
|
subtotal_ib: number
|
|
subtotal_ub: number
|
|
}
|
|
|
|
export interface BalansrapportReport {
|
|
groups: BalansrapportGroup[]
|
|
total_assets_ub: number
|
|
total_equity_liabilities_ub: number
|
|
beraknat_resultat: number
|
|
is_balanced: boolean
|
|
period: { start: string; end: string }
|
|
}
|
|
|
|
export interface SIEExportOptions {
|
|
fiscal_period_id: string
|
|
company_name: string
|
|
trade_name?: string | null
|
|
org_number: string | null
|
|
program_name?: string
|
|
}
|
|
|
|
// Input types for creating entries
|
|
export interface CreateJournalEntryInput {
|
|
fiscal_period_id: string
|
|
entry_date: string
|
|
description: string
|
|
source_type: JournalEntrySourceType
|
|
source_id?: string
|
|
voucher_series?: string
|
|
notes?: string
|
|
lines: CreateJournalEntryLineInput[]
|
|
}
|
|
|
|
export interface CreateJournalEntryLineInput {
|
|
account_number: string
|
|
debit_amount: number
|
|
credit_amount: number
|
|
line_description?: string
|
|
currency?: string
|
|
amount_in_currency?: number
|
|
exchange_rate?: number
|
|
tax_code?: string
|
|
cost_center?: string
|
|
project?: string
|
|
}
|
|
|
|
export interface CreateFiscalPeriodInput {
|
|
name: string
|
|
period_start: string
|
|
period_end: string
|
|
}
|
|
|
|
// ── Pending Operations ────────────────────────────────────────
|
|
|
|
export type PendingOperationType =
|
|
| 'categorize_transaction'
|
|
| 'create_customer'
|
|
| 'create_invoice'
|
|
| 'mark_invoice_paid'
|
|
| 'send_invoice'
|
|
| 'mark_invoice_sent'
|
|
| 'match_transaction_invoice'
|
|
// Stream 1 Phase 1: bookkeeping period operations
|
|
| 'close_period'
|
|
| 'lock_period'
|
|
| 'unlock_period'
|
|
| 'set_opening_balances'
|
|
| 'run_year_end'
|
|
| 'run_currency_revaluation'
|
|
// Stream 1 Phase 1: SIE import (export is read-only)
|
|
| 'import_sie'
|
|
// Stream 1 Phase 1: voucher gap explanations
|
|
| 'explain_voucher_gap'
|
|
// Stream 1 Phase 1: transaction reversal
|
|
| 'uncategorize_transaction'
|
|
// Stream 1 Phase 1: supplier invoice lifecycle
|
|
| 'approve_supplier_invoice'
|
|
| 'credit_supplier_invoice'
|
|
// Stream 1 Phase 1: invoice operations beyond simple create/send
|
|
| 'credit_invoice'
|
|
| 'convert_invoice'
|
|
export type PendingOperationStatus = 'pending' | 'committing' | 'committed' | 'rejected'
|
|
|
|
export type PendingOperationActorType = 'user' | 'api_key' | 'mcp_oauth' | 'cron'
|
|
export type PendingOperationRiskLevel = 'low' | 'medium' | 'high'
|
|
|
|
export interface PendingOperation {
|
|
id: string
|
|
user_id: string
|
|
company_id: string
|
|
operation_type: PendingOperationType
|
|
status: PendingOperationStatus
|
|
title: string
|
|
params: Record<string, unknown>
|
|
preview_data: Record<string, unknown>
|
|
result_data: Record<string, unknown> | null
|
|
// Stream 2 Phase 1: actor model
|
|
actor_type: PendingOperationActorType
|
|
actor_id: string | null
|
|
actor_label: string | null
|
|
risk_level: PendingOperationRiskLevel
|
|
// Stream 2 Phase 2: auto-commit tracking
|
|
auto_commit_eligible: boolean
|
|
auto_committed_at: string | null
|
|
created_at: string
|
|
resolved_at: string | null
|
|
updated_at: string
|
|
}
|
|
|
|
// Onboarding progress for new user checklist
|
|
export interface OnboardingProgress {
|
|
hasCustomers: boolean
|
|
hasInvoices: boolean
|
|
hasBankConnected: boolean
|
|
hasSIEImport: boolean
|
|
/** True when the active user has a stored Skatteverket OAuth token. */
|
|
hasSkatteverketConnected: boolean
|
|
}
|
|
|
|
// Onboarding step data
|
|
export interface OnboardingStepData {
|
|
step1?: {
|
|
entity_type: EntityType
|
|
}
|
|
step2?: {
|
|
company_name: string
|
|
org_number?: string
|
|
address_line1?: string
|
|
postal_code?: string
|
|
city?: string
|
|
}
|
|
step3?: {
|
|
f_skatt: boolean
|
|
fiscal_year_start_month: number
|
|
is_first_fiscal_year?: boolean
|
|
first_year_start?: string
|
|
first_year_end?: string
|
|
vat_registered: boolean
|
|
vat_number?: string
|
|
moms_period?: MomsPeriod
|
|
}
|
|
step4?: {
|
|
preliminary_tax_monthly?: number
|
|
}
|
|
step5?: {
|
|
bank_name?: string
|
|
clearing_number?: string
|
|
account_number?: string
|
|
iban?: string
|
|
bic?: string
|
|
}
|
|
step6?: {
|
|
bank_connected: boolean
|
|
bank_connection_id?: string
|
|
}
|
|
}
|
|
|
|
// ============================================================
|
|
// Calendar & Deadline Types
|
|
// ============================================================
|
|
|
|
// Calendar view mode
|
|
export type CalendarViewMode = 'month' | 'week' | 'day'
|
|
|
|
// Payment calendar day (for invoice due date tracking)
|
|
export interface PaymentCalendarDay {
|
|
date: string
|
|
invoices: Invoice[]
|
|
totalExpected: number
|
|
overdueCount: number
|
|
}
|
|
|
|
// Tax deadline types (Swedish Skatteverket)
|
|
export type TaxDeadlineType =
|
|
| 'moms_monthly'
|
|
| 'moms_quarterly'
|
|
| 'moms_yearly'
|
|
| 'f_skatt'
|
|
| 'arbetsgivardeklaration'
|
|
| 'inkomstdeklaration_ef'
|
|
| 'inkomstdeklaration_ab'
|
|
| 'arsredovisning'
|
|
| 'periodisk_sammanstallning'
|
|
| 'bokslut'
|
|
|
|
// Deadline status workflow
|
|
export type DeadlineStatus =
|
|
| 'upcoming' // More than 14 days away
|
|
| 'action_needed' // Within 14 days, needs attention
|
|
| 'in_progress' // User is working on it
|
|
| 'submitted' // Submitted to Skatteverket
|
|
| 'confirmed' // Confirmed/acknowledged
|
|
| 'overdue' // Past due date without submission
|
|
|
|
// Deadline source
|
|
export type DeadlineSource = 'system' | 'user'
|
|
|
|
// Deadline types
|
|
export type DeadlineType = 'delivery' | 'invoicing' | 'report' | 'tax' | 'other'
|
|
export type DeadlinePriority = 'critical' | 'important' | 'normal'
|
|
|
|
// Deadline record
|
|
export interface Deadline {
|
|
id: string
|
|
user_id: string
|
|
company_id: string
|
|
title: string
|
|
due_date: string
|
|
due_time: string | null
|
|
deadline_type: DeadlineType
|
|
priority: DeadlinePriority
|
|
is_completed: boolean
|
|
completed_at: string | null
|
|
customer_id: string | null
|
|
is_auto_generated: boolean
|
|
notes: string | null
|
|
created_at: string
|
|
updated_at: string
|
|
|
|
// Tax deadline fields
|
|
tax_deadline_type: TaxDeadlineType | null
|
|
tax_period: string | null
|
|
source: DeadlineSource
|
|
reminder_offsets: number[] | null
|
|
status: DeadlineStatus
|
|
status_changed_at: string
|
|
linked_report_type: string | null
|
|
linked_report_period: Record<string, unknown> | null
|
|
|
|
// Relations
|
|
customer?: Customer
|
|
}
|
|
|
|
// Input for creating a deadline
|
|
export interface CreateDeadlineInput {
|
|
title: string
|
|
due_date: string
|
|
due_time?: string
|
|
deadline_type: DeadlineType
|
|
priority?: DeadlinePriority
|
|
customer_id?: string
|
|
notes?: string
|
|
// Tax deadline fields
|
|
tax_deadline_type?: TaxDeadlineType
|
|
tax_period?: string
|
|
source?: DeadlineSource
|
|
linked_report_type?: string
|
|
linked_report_period?: Record<string, unknown>
|
|
}
|
|
|
|
// ============================================================
|
|
// Push Notification Types
|
|
// ============================================================
|
|
|
|
// Push subscription for Web Push API
|
|
export interface PushSubscription {
|
|
id: string
|
|
user_id: string
|
|
endpoint: string
|
|
p256dh: string
|
|
auth: string
|
|
user_agent: string | null
|
|
is_active: boolean
|
|
last_used_at: string | null
|
|
created_at: string
|
|
}
|
|
|
|
// Notification settings per user
|
|
export interface NotificationSettings {
|
|
id: string
|
|
user_id: string
|
|
tax_deadlines_enabled: boolean
|
|
invoice_reminders_enabled: boolean
|
|
quiet_start: string // time format "HH:MM"
|
|
quiet_end: string // time format "HH:MM"
|
|
email_enabled: boolean
|
|
push_enabled: boolean
|
|
period_locked_enabled: boolean
|
|
period_year_closed_enabled: boolean
|
|
invoice_sent_enabled: boolean
|
|
receipt_extracted_enabled: boolean
|
|
receipt_matched_enabled: boolean
|
|
created_at: string
|
|
updated_at: string
|
|
}
|
|
|
|
// Notification type for logging
|
|
export type NotificationType =
|
|
| 'tax_deadline'
|
|
| 'invoice_due'
|
|
| 'invoice_overdue'
|
|
| 'period_locked'
|
|
| 'period_year_closed'
|
|
| 'receipt_extracted'
|
|
| 'receipt_matched'
|
|
| 'invoice_sent'
|
|
| 'missing_underlag'
|
|
|
|
// Notification log entry
|
|
export interface NotificationLog {
|
|
id: string
|
|
user_id: string
|
|
company_id: string | null
|
|
notification_type: NotificationType
|
|
reference_id: string
|
|
days_before: number
|
|
sent_at: string
|
|
delivery_status: 'sent' | 'delivered' | 'failed'
|
|
}
|
|
|
|
// ============================================================
|
|
// Calendar Feed Types (ICS)
|
|
// ============================================================
|
|
|
|
// Calendar feed for Apple Calendar / Google Calendar sync
|
|
export interface CalendarFeed {
|
|
id: string
|
|
user_id: string
|
|
company_id: string
|
|
feed_token: string
|
|
is_active: boolean
|
|
include_tax_deadlines: boolean
|
|
include_invoices: boolean
|
|
last_accessed_at: string | null
|
|
access_count: number
|
|
created_at: string
|
|
updated_at: string
|
|
}
|
|
|
|
// Input for creating/updating calendar feed
|
|
export interface UpdateCalendarFeedInput {
|
|
include_tax_deadlines?: boolean
|
|
include_invoices?: boolean
|
|
}
|
|
|
|
// Swedish labels for deadline status
|
|
export const DEADLINE_STATUS_LABELS: Record<DeadlineStatus, string> = {
|
|
upcoming: 'Kommande',
|
|
action_needed: 'Åtgärd krävs',
|
|
in_progress: 'Pågår',
|
|
submitted: 'Inskickad',
|
|
confirmed: 'Bekräftad',
|
|
overdue: 'Försenad'
|
|
}
|
|
|
|
// Swedish labels for tax deadline types
|
|
export const TAX_DEADLINE_TYPE_LABELS: Record<TaxDeadlineType, string> = {
|
|
moms_monthly: 'Momsdeklaration (månad)',
|
|
moms_quarterly: 'Momsdeklaration (kvartal)',
|
|
moms_yearly: 'Momsdeklaration (år)',
|
|
f_skatt: 'F-skatt',
|
|
arbetsgivardeklaration: 'Arbetsgivardeklaration',
|
|
inkomstdeklaration_ef: 'Inkomstdeklaration EF',
|
|
inkomstdeklaration_ab: 'Inkomstdeklaration AB',
|
|
arsredovisning: 'Årsredovisning',
|
|
periodisk_sammanstallning: 'Periodisk sammanställning',
|
|
bokslut: 'Bokslut'
|
|
}
|
|
|
|
// ============================================================
|
|
// SIE Import Types
|
|
// ============================================================
|
|
|
|
// SIE import status
|
|
export type SIEImportStatus = 'pending' | 'mapped' | 'completed' | 'failed'
|
|
|
|
// SIE import record
|
|
export interface SIEImport {
|
|
id: string
|
|
user_id: string
|
|
company_id: string
|
|
filename: string
|
|
file_hash: string
|
|
org_number: string | null
|
|
company_name: string | null
|
|
sie_type: number
|
|
fiscal_year_start: string | null
|
|
fiscal_year_end: string | null
|
|
accounts_count: number
|
|
transactions_count: number
|
|
opening_balance_total: number | null
|
|
status: SIEImportStatus
|
|
error_message: string | null
|
|
fiscal_period_id: string | null
|
|
opening_balance_entry_id: string | null
|
|
imported_at: string | null
|
|
created_at: string
|
|
updated_at: string
|
|
}
|
|
|
|
// SIE account mapping record
|
|
export interface SIEAccountMapping {
|
|
id: string
|
|
user_id: string
|
|
company_id: string
|
|
source_account: string
|
|
source_name: string | null
|
|
target_account: string
|
|
confidence: number
|
|
match_type: 'exact' | 'name' | 'class' | 'manual'
|
|
created_at: string
|
|
updated_at: string
|
|
}
|
|
|
|
// ============================================================
|
|
// Invoice Inbox Types
|
|
// ============================================================
|
|
|
|
export type InboxItemStatus = 'received' | 'error'
|
|
export type InboxItemSource = 'email' | 'upload'
|
|
|
|
export type CompanyInboxStatus = 'active' | 'deprecated' | 'blocked'
|
|
|
|
export interface CompanyInbox {
|
|
id: string
|
|
company_id: string
|
|
local_part: string
|
|
status: CompanyInboxStatus
|
|
slug_seed: string
|
|
created_at: string
|
|
updated_at: string
|
|
deprecated_at: string | null
|
|
}
|
|
|
|
export interface InvoiceInboxItem {
|
|
id: string
|
|
user_id: string
|
|
company_id: string
|
|
status: InboxItemStatus
|
|
source: InboxItemSource
|
|
email_from: string | null
|
|
email_subject: string | null
|
|
email_received_at: string | null
|
|
email_body_text: string | null
|
|
resend_email_id: string | null
|
|
resend_attachment_id: string | null
|
|
document_id: string | null
|
|
extracted_data: Record<string, unknown> | null
|
|
matched_supplier_id: string | null
|
|
created_supplier_invoice_id: string | null
|
|
error_message: string | null
|
|
raw_email_payload: Record<string, unknown> | null
|
|
|
|
// Audit chain (processing_history correlation)
|
|
correlation_id: string | null
|
|
|
|
created_at: string
|
|
updated_at: string
|
|
|
|
// Relations (populated when fetched)
|
|
document?: DocumentAttachment
|
|
supplier?: Supplier
|
|
supplier_invoice?: SupplierInvoice
|
|
}
|
|
|
|
// ============================================================
|
|
// Receipt Types
|
|
// ============================================================
|
|
|
|
// Receipt extraction status
|
|
export type ReceiptStatus = 'pending' | 'processing' | 'extracted' | 'confirmed' | 'error'
|
|
|
|
// Receipt record
|
|
export interface Receipt {
|
|
id: string
|
|
user_id: string
|
|
company_id: string
|
|
|
|
// Image storage
|
|
image_url: string
|
|
image_thumbnail_url: string | null
|
|
|
|
// Extraction status
|
|
status: ReceiptStatus
|
|
extraction_confidence: number | null
|
|
|
|
// Extracted header data
|
|
merchant_name: string | null
|
|
merchant_org_number: string | null
|
|
merchant_vat_number: string | null
|
|
receipt_date: string | null
|
|
receipt_time: string | null
|
|
total_amount: number | null
|
|
currency: string
|
|
vat_amount: number | null
|
|
|
|
// Special flags
|
|
is_restaurant: boolean
|
|
is_systembolaget: boolean
|
|
is_foreign_merchant: boolean
|
|
|
|
// Restaurant representation data
|
|
representation_persons: number | null
|
|
representation_purpose: string | null
|
|
representation_business_connection: string | null
|
|
|
|
// Source tracking (for email-originated receipts)
|
|
source: 'upload' | 'camera' | 'email'
|
|
email_from: string | null
|
|
|
|
// Transaction matching
|
|
matched_transaction_id: string | null
|
|
match_confidence: number | null
|
|
|
|
// Raw extraction data
|
|
raw_extraction: ReceiptExtractionResult | null
|
|
|
|
created_at: string
|
|
updated_at: string
|
|
|
|
// Relations (populated when fetched)
|
|
line_items?: ReceiptLineItem[]
|
|
matched_transaction?: Transaction
|
|
}
|
|
|
|
// Receipt line item record
|
|
export interface ReceiptLineItem {
|
|
id: string
|
|
receipt_id: string
|
|
|
|
// Extracted data
|
|
description: string
|
|
quantity: number
|
|
unit_price: number | null
|
|
line_total: number
|
|
vat_rate: number | null
|
|
vat_amount: number | null
|
|
|
|
// Classification
|
|
is_business: boolean | null
|
|
category: TransactionCategory | null
|
|
bas_account: string | null
|
|
|
|
// Confidence
|
|
extraction_confidence: number | null
|
|
suggested_category: string | null
|
|
|
|
sort_order: number
|
|
created_at: string
|
|
}
|
|
|
|
// AI extraction result from Claude Vision
|
|
export interface ReceiptExtractionResult {
|
|
merchant: {
|
|
name: string | null
|
|
orgNumber: string | null
|
|
vatNumber: string | null
|
|
isForeign: boolean
|
|
}
|
|
receipt: {
|
|
date: string | null
|
|
time: string | null
|
|
currency: string
|
|
}
|
|
lineItems: ExtractedLineItem[]
|
|
totals: {
|
|
subtotal: number | null
|
|
vatAmount: number | null
|
|
total: number | null
|
|
}
|
|
flags: {
|
|
isRestaurant: boolean
|
|
isSystembolaget: boolean
|
|
isForeignMerchant: boolean
|
|
}
|
|
confidence: number
|
|
suggestedTemplateId?: string
|
|
}
|
|
|
|
// Extracted line item from AI
|
|
export interface ExtractedLineItem {
|
|
description: string
|
|
quantity: number
|
|
unitPrice: number | null
|
|
lineTotal: number
|
|
vatRate: number | null
|
|
suggestedCategory: string | null
|
|
suggestedTemplateId?: string
|
|
confidence?: number
|
|
}
|
|
|
|
// Match candidate for receipt-to-transaction matching
|
|
export interface ReceiptMatchCandidate {
|
|
transaction: Transaction
|
|
confidence: number
|
|
matchReasons: string[]
|
|
dateVariance: number
|
|
amountVariance: number
|
|
}
|
|
|
|
// Input for creating a receipt
|
|
export interface CreateReceiptInput {
|
|
image_url: string
|
|
image_thumbnail_url?: string
|
|
}
|
|
|
|
// Input for confirming receipt line items
|
|
export interface ConfirmReceiptInput {
|
|
line_items: ConfirmLineItemInput[]
|
|
matched_transaction_id?: string
|
|
representation_persons?: number
|
|
representation_purpose?: string
|
|
}
|
|
|
|
export interface ConfirmLineItemInput {
|
|
id: string
|
|
is_business: boolean
|
|
category?: TransactionCategory
|
|
bas_account?: string
|
|
}
|
|
|
|
// Receipt queue summary
|
|
export interface ReceiptQueueSummary {
|
|
unmatched_receipts_count: number
|
|
unmatched_transactions_count: number
|
|
pending_review_count: number
|
|
streak_count: number
|
|
}
|
|
|
|
// Camera quality feedback
|
|
export interface CameraQualityFeedback {
|
|
lightingOk: boolean
|
|
distanceOk: boolean
|
|
focusOk: boolean
|
|
readyToCapture: boolean
|
|
message?: string
|
|
}
|
|
|
|
// Swedish labels for receipt status
|
|
export const RECEIPT_STATUS_LABELS: Record<ReceiptStatus, string> = {
|
|
pending: 'Väntar',
|
|
processing: 'Analyserar',
|
|
extracted: 'Extraherat',
|
|
confirmed: 'Bekräftat',
|
|
error: 'Fel'
|
|
}
|
|
|
|
// ============================================================
|
|
// VAT Declaration Types (Momsdeklaration)
|
|
// ============================================================
|
|
|
|
// VAT period type
|
|
export type VatPeriodType = 'monthly' | 'quarterly' | 'yearly'
|
|
|
|
// VAT declaration rutor (boxes) according to SKV 4700
|
|
// Complete set of all 30 boxes in the momsdeklaration form.
|
|
export interface VatDeclarationRutor {
|
|
// Momspliktig försäljning (taxable sales basis, all rates combined)
|
|
ruta05: number // Momspliktig försäljning (excl. ruta 06, 07, 08)
|
|
ruta06: number // Momspliktiga uttag (always 0 for most users)
|
|
ruta07: number // Vinstmarginalbeskattning (always 0 for most users)
|
|
ruta08: number // Hyresinkomster frivillig beskattning (always 0 for most users)
|
|
|
|
// Utgående moms (Output VAT per rate)
|
|
ruta10: number // Utgående moms 25%
|
|
ruta11: number // Utgående moms 12%
|
|
ruta12: number // Utgående moms 6%
|
|
|
|
// Inköp vid omvänd skattskyldighet (reverse charge purchase bases)
|
|
ruta20: number // Inköp av varor från annat EU-land
|
|
ruta21: number // Inköp av tjänster från annat EU-land
|
|
ruta22: number // Inköp av tjänster från land utanför EU
|
|
ruta23: number // Inköp av varor i Sverige (construction reverse charge goods)
|
|
ruta24: number // Övriga inköp av tjänster i Sverige (domestic reverse charge)
|
|
|
|
// Utgående moms omvänd skattskyldighet (self-assessed output VAT on reverse charge)
|
|
ruta30: number // Utgående moms 25% omvänd skattskyldighet
|
|
ruta31: number // Utgående moms 12% omvänd skattskyldighet
|
|
ruta32: number // Utgående moms 6% omvänd skattskyldighet
|
|
|
|
// EU och export försäljning
|
|
ruta35: number // Varuförsäljning till annat EU-land
|
|
ruta36: number // Varuförsäljning utanför EU (export)
|
|
ruta37: number // Mellanmans inköp vid trepartshandel
|
|
ruta38: number // Mellanmans försäljning vid trepartshandel
|
|
ruta39: number // Försäljning av tjänster till annat EU-land (reverse charge)
|
|
ruta40: number // Övrig försäljning av tjänster utomlands
|
|
ruta41: number // Försäljning med omvänd skattskyldighet (Sverige)
|
|
ruta42: number // Övrig momsfri försäljning m.m.
|
|
|
|
// Ingående moms (Input VAT)
|
|
ruta48: number // Ingående moms att dra av
|
|
|
|
// Moms att betala eller få tillbaka
|
|
ruta49: number // Moms att betala (positive) eller återfå (negative)
|
|
|
|
// Import (via Tullverket)
|
|
ruta50: number // Beskattningsunderlag vid import
|
|
ruta60: number // Utgående moms 25% import
|
|
ruta61: number // Utgående moms 12% import
|
|
ruta62: number // Utgående moms 6% import
|
|
}
|
|
|
|
// VAT declaration response
|
|
export interface VatDeclaration {
|
|
period: {
|
|
type: VatPeriodType
|
|
year: number
|
|
period: number // 1-12 for monthly, 1-4 for quarterly, 1 for yearly
|
|
start: string // YYYY-MM-DD
|
|
end: string // YYYY-MM-DD
|
|
}
|
|
rutor: VatDeclarationRutor
|
|
// Supporting data
|
|
invoiceCount: number
|
|
transactionCount: number
|
|
// Breakdown by source
|
|
breakdown: {
|
|
invoices: {
|
|
ruta05: number
|
|
ruta06: number
|
|
ruta07: number
|
|
ruta10: number
|
|
ruta11: number
|
|
ruta12: number
|
|
ruta39: number
|
|
ruta40: number
|
|
// Per-rate base amounts for UI display
|
|
base25: number
|
|
base12: number
|
|
base6: number
|
|
}
|
|
transactions: {
|
|
ruta48: number // Ingående moms from categorized expenses
|
|
}
|
|
receipts: {
|
|
ruta48: number // Ingående moms from receipts
|
|
}
|
|
reverseCharge: {
|
|
ruta20: number
|
|
ruta21: number
|
|
ruta22: number
|
|
ruta23: number
|
|
ruta24: number
|
|
ruta30: number
|
|
ruta31: number
|
|
ruta32: number
|
|
}
|
|
}
|
|
}
|
|
|
|
// VAT declaration request parameters
|
|
export interface VatDeclarationRequest {
|
|
periodType: VatPeriodType
|
|
year: number
|
|
period: number
|
|
}
|
|
|
|
// Labels for VAT rutor
|
|
export const VAT_RUTA_LABELS: Record<keyof VatDeclarationRutor, string> = {
|
|
ruta05: 'Momspliktig försäljning',
|
|
ruta06: 'Momspliktiga uttag',
|
|
ruta07: 'Vinstmarginalbeskattning',
|
|
ruta08: 'Hyresinkomster (frivillig beskattning)',
|
|
ruta10: 'Utgående moms 25%',
|
|
ruta11: 'Utgående moms 12%',
|
|
ruta12: 'Utgående moms 6%',
|
|
ruta20: 'Inköp av varor från annat EU-land',
|
|
ruta21: 'Inköp av tjänster från annat EU-land',
|
|
ruta22: 'Inköp av tjänster från land utanför EU',
|
|
ruta23: 'Inköp av varor i Sverige',
|
|
ruta24: 'Övriga inköp av tjänster i Sverige',
|
|
ruta30: 'Utgående moms 25% (omvänd skattskyldighet)',
|
|
ruta31: 'Utgående moms 12% (omvänd skattskyldighet)',
|
|
ruta32: 'Utgående moms 6% (omvänd skattskyldighet)',
|
|
ruta35: 'Varuförsäljning till annat EU-land',
|
|
ruta36: 'Varuförsäljning utanför EU (export)',
|
|
ruta37: 'Mellanmans inköp vid trepartshandel',
|
|
ruta38: 'Mellanmans försäljning vid trepartshandel',
|
|
ruta39: 'Försäljning av tjänster till EU-land',
|
|
ruta40: 'Övrig försäljning av tjänster utomlands',
|
|
ruta41: 'Försäljning med omvänd skattskyldighet (Sverige)',
|
|
ruta42: 'Övrig momsfri försäljning m.m.',
|
|
ruta48: 'Ingående moms att dra av',
|
|
ruta49: 'Moms att betala/återfå',
|
|
ruta50: 'Beskattningsunderlag vid import',
|
|
ruta60: 'Utgående moms 25% import',
|
|
ruta61: 'Utgående moms 12% import',
|
|
ruta62: 'Utgående moms 6% import',
|
|
}
|
|
|
|
// ============================================================
|
|
// Event Payload Placeholder Types
|
|
// ============================================================
|
|
|
|
/** Credit note is an invoice with a credited_invoice_id */
|
|
export interface CreditNote extends Invoice {
|
|
credited_invoice_id: string
|
|
}
|
|
|
|
/** Generic key-value store record for extensions */
|
|
export interface ExtensionDataRecord {
|
|
id: string
|
|
user_id: string
|
|
company_id: string
|
|
extension_id: string
|
|
key: string
|
|
value: Record<string, unknown>
|
|
created_at: string
|
|
updated_at: string
|
|
}
|
|
|
|
// ============================================================
|
|
// Tax Code Types
|
|
// ============================================================
|
|
|
|
// Tax code identifiers (standard Swedish codes)
|
|
export type TaxCodeId =
|
|
| 'MP1' | 'MP2' | 'MP3' // Output VAT 25%, 12%, 6%
|
|
| 'MPI' | 'MPI12' | 'MPI6' // Input VAT 25%, 12%, 6%
|
|
| 'IV' // Intra-EU acquisition
|
|
| 'EUS' // EU sale (reverse charge)
|
|
| 'IP' // Import
|
|
| 'EXP' // Export outside EU
|
|
| 'OSS' // One Stop Shop
|
|
| 'NONE' // VAT exempt
|
|
|
|
export interface TaxCode {
|
|
id: string
|
|
user_id: string | null
|
|
code: string
|
|
description: string
|
|
rate: number
|
|
moms_basis_boxes: string[]
|
|
moms_tax_boxes: string[]
|
|
moms_input_boxes: string[]
|
|
is_output_vat: boolean
|
|
is_reverse_charge: boolean
|
|
is_eu: boolean
|
|
is_export: boolean
|
|
is_oss: boolean
|
|
is_system: boolean
|
|
created_at: string
|
|
updated_at: string
|
|
}
|
|
|
|
// ============================================================
|
|
// Document Archive Types
|
|
// ============================================================
|
|
|
|
export type DocumentUploadSource =
|
|
| 'camera'
|
|
| 'file_upload'
|
|
| 'email'
|
|
| 'e_invoice'
|
|
| 'scan'
|
|
| 'api'
|
|
| 'system'
|
|
|
|
export interface DocumentAttachment {
|
|
id: string
|
|
user_id: string
|
|
company_id: string
|
|
storage_path: string
|
|
file_name: string
|
|
file_size_bytes: number | null
|
|
mime_type: string | null
|
|
sha256_hash: string
|
|
version: number
|
|
original_id: string | null
|
|
superseded_by_id: string | null
|
|
is_current_version: boolean
|
|
uploaded_by: string | null
|
|
upload_source: DocumentUploadSource | null
|
|
digitization_date: string | null
|
|
journal_entry_id: string | null
|
|
journal_entry_line_id: string | null
|
|
prev_version_hash: string | null
|
|
last_integrity_check_at: string | null
|
|
created_at: string
|
|
updated_at: string
|
|
}
|
|
|
|
export interface CreateDocumentAttachmentInput {
|
|
storage_path: string
|
|
file_name: string
|
|
file_size_bytes?: number
|
|
mime_type?: string
|
|
sha256_hash: string
|
|
upload_source?: DocumentUploadSource
|
|
journal_entry_id?: string
|
|
journal_entry_line_id?: string
|
|
}
|
|
|
|
// ============================================================
|
|
// Audit Log Types
|
|
// ============================================================
|
|
|
|
export type AuditAction =
|
|
| 'INSERT'
|
|
| 'UPDATE'
|
|
| 'DELETE'
|
|
| 'COMMIT'
|
|
| 'REVERSE'
|
|
| 'CORRECT'
|
|
| 'LOCK_PERIOD'
|
|
| 'CLOSE_PERIOD'
|
|
| 'DOCUMENT_DELETE_BLOCKED'
|
|
| 'RETENTION_BLOCK'
|
|
| 'SECURITY_EVENT'
|
|
| 'INTEGRITY_FAILURE'
|
|
|
|
export interface AuditLogEntry {
|
|
id: string
|
|
user_id: string
|
|
company_id: string | null
|
|
action: AuditAction
|
|
table_name: string | null
|
|
record_id: string | null
|
|
actor_id: string | null
|
|
actor_type: 'user' | 'api_key' | 'mcp_oauth' | 'cron' | 'system' | null
|
|
actor_label: string | null
|
|
old_state: Record<string, unknown> | null
|
|
new_state: Record<string, unknown> | null
|
|
description: string | null
|
|
created_at: string
|
|
}
|
|
|
|
// ============================================================
|
|
// Dimension Types (Kostnadsställen & Projekt)
|
|
// ============================================================
|
|
|
|
export interface CostCenter {
|
|
id: string
|
|
user_id: string
|
|
company_id: string
|
|
code: string
|
|
name: string
|
|
is_active: boolean
|
|
created_at: string
|
|
updated_at: string
|
|
}
|
|
|
|
export interface Project {
|
|
id: string
|
|
user_id: string
|
|
company_id: string
|
|
code: string
|
|
name: string
|
|
is_active: boolean
|
|
start_date: string | null
|
|
end_date: string | null
|
|
created_at: string
|
|
updated_at: string
|
|
}
|
|
|
|
// ============================================================
|
|
// Voucher Gap Detection
|
|
// ============================================================
|
|
|
|
export interface VoucherGap {
|
|
gap_start: number
|
|
gap_end: number
|
|
series: string
|
|
}
|
|
|
|
export interface VoucherGapExplanation {
|
|
id: string
|
|
company_id: string
|
|
user_id: string
|
|
fiscal_period_id: string
|
|
voucher_series: string
|
|
gap_start: number
|
|
gap_end: number
|
|
explanation: string
|
|
created_at: string
|
|
updated_at: string
|
|
}
|
|
|
|
export interface SequenceMismatch {
|
|
series: string
|
|
sequenceCounter: number
|
|
actualMax: number
|
|
}
|
|
|
|
// ============================================================
|
|
// Year-End Closing Types (Årsbokslut)
|
|
// ============================================================
|
|
|
|
export interface YearEndValidation {
|
|
ready: boolean
|
|
errors: string[]
|
|
warnings: string[]
|
|
draftCount: number
|
|
voucherGaps: VoucherGap[]
|
|
unexplainedGaps: VoucherGap[]
|
|
sequenceMismatches: SequenceMismatch[]
|
|
trialBalanceBalanced: boolean
|
|
}
|
|
|
|
export interface YearEndPreview {
|
|
netResult: number
|
|
closingAccount: string
|
|
closingAccountName: string
|
|
closingLines: CreateJournalEntryLineInput[]
|
|
resultAccountSummary: { account_number: string; account_name: string; amount: number }[]
|
|
currencyRevaluation: CurrencyRevaluationPreview | null
|
|
}
|
|
|
|
export interface YearEndResult {
|
|
closingEntry: JournalEntry
|
|
nextPeriod: FiscalPeriod
|
|
openingBalanceEntry: JournalEntry
|
|
revaluationEntry: JournalEntry | null
|
|
}
|
|
|
|
// ============================================================
|
|
// IB/UB Continuity Check Types (Avstämning ingående/utgående balans)
|
|
// ============================================================
|
|
|
|
export interface ContinuityDiscrepancy {
|
|
account_number: string
|
|
account_name: string
|
|
previous_ub_net: number
|
|
current_ib_net: number
|
|
difference: number
|
|
}
|
|
|
|
export interface ContinuityCheckResult {
|
|
valid: boolean
|
|
period_name: string
|
|
previous_period_name: string | null
|
|
discrepancies: ContinuityDiscrepancy[]
|
|
checked_accounts: number
|
|
}
|
|
|
|
// ============================================================
|
|
// Currency Revaluation Types (Omvärdering utländsk valuta)
|
|
// ============================================================
|
|
|
|
export interface RevaluationItem {
|
|
type: 'receivable' | 'payable'
|
|
source_id: string
|
|
reference: string
|
|
currency: Currency
|
|
amount_in_currency: number
|
|
original_rate: number
|
|
closing_rate: number
|
|
original_sek: number
|
|
closing_sek: number
|
|
difference_sek: number
|
|
}
|
|
|
|
export interface CurrencyRevaluationPreview {
|
|
items: RevaluationItem[]
|
|
lines: CreateJournalEntryLineInput[]
|
|
closingRates: Record<string, number>
|
|
totalGain: number
|
|
totalLoss: number
|
|
netEffect: number
|
|
}
|
|
|
|
export interface CurrencyRevaluationResult {
|
|
entry: JournalEntry
|
|
preview: CurrencyRevaluationPreview
|
|
}
|
|
|
|
export interface PeriodStatus {
|
|
is_locked: boolean
|
|
is_closed: boolean
|
|
has_closing_entry: boolean
|
|
has_opening_balances: boolean
|
|
draft_count: number
|
|
next_period_exists: boolean
|
|
}
|
|
|
|
// ============================================================
|
|
// Invoice Reminder Types (Betalningspåminnelser)
|
|
// ============================================================
|
|
|
|
// Response type from customer action
|
|
export type ReminderResponseType = 'marked_paid' | 'disputed'
|
|
|
|
// Invoice reminder record
|
|
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
|
|
response_type: ReminderResponseType | null
|
|
response_at: string | null
|
|
action_token: string
|
|
action_token_used: boolean
|
|
created_at: string
|
|
}
|
|
|
|
// Swedish labels for reminder levels
|
|
export const REMINDER_LEVEL_LABELS: Record<1 | 2 | 3, string> = {
|
|
1: 'Vänlig påminnelse',
|
|
2: 'Andra påminnelsen',
|
|
3: 'Slutlig påminnelse'
|
|
}
|
|
|
|
// Reminder level descriptions
|
|
export const REMINDER_LEVEL_DESCRIPTIONS: Record<1 | 2 | 3, string> = {
|
|
1: '15 dagar efter förfallodatum',
|
|
2: '30 dagar efter förfallodatum',
|
|
3: '45 dagar efter förfallodatum'
|
|
}
|
|
|
|
// ============================================================
|
|
// Transaction Ingestion Types (re-exported for extension use)
|
|
// ============================================================
|
|
|
|
/** Normalized transaction input for the generic ingestion pipeline */
|
|
export interface RawTransaction {
|
|
date: string
|
|
description: string
|
|
amount: number
|
|
currency: string
|
|
external_id: string
|
|
mcc_code?: number | null
|
|
merchant_name?: string | null
|
|
reference?: string | null
|
|
bank_connection_id?: string | null
|
|
import_source?: string
|
|
}
|
|
|
|
/** Options for the transaction ingestion pipeline */
|
|
export interface IngestOptions {
|
|
/** Skip auto-categorization (mapping engine + journal entry creation).
|
|
* Reconciliation and invoice matching still run.
|
|
* Used when SIE-imported entries overlap the sync date range
|
|
* to prevent double-booking. */
|
|
skipAutoCategorization?: boolean
|
|
/** Override the default settlement account (1930) for bank transactions.
|
|
* Used when importing to a secondary bank account (e.g., 1931). */
|
|
settlementAccount?: string
|
|
/** Only INSERT transactions + dedup. Skip reconciliation, invoice matching,
|
|
* supplier matching, and auto-categorization. For viewer imports. */
|
|
rawInsertOnly?: boolean
|
|
}
|
|
|
|
/** Result of the transaction ingestion pipeline */
|
|
export interface IngestResult {
|
|
imported: number
|
|
duplicates: number
|
|
reconciled: number
|
|
auto_categorized: number
|
|
auto_matched_invoices: number
|
|
errors: number
|
|
transaction_ids: string[]
|
|
}
|
|
|
|
// ── Invoice extraction (used by invoice-inbox extension and core utils) ──
|
|
|
|
export interface InvoiceExtractionResult {
|
|
supplier: {
|
|
name: string | null
|
|
orgNumber: string | null
|
|
vatNumber: string | null
|
|
address: string | null
|
|
bankgiro: string | null
|
|
plusgiro: string | null
|
|
}
|
|
invoice: {
|
|
invoiceNumber: string | null
|
|
invoiceDate: string | null
|
|
dueDate: string | null
|
|
paymentReference: string | null
|
|
currency: string
|
|
}
|
|
lineItems: ExtractedInvoiceLineItem[]
|
|
totals: {
|
|
subtotal: number | null
|
|
vatAmount: number | null
|
|
total: number | null
|
|
}
|
|
vatBreakdown: VatBreakdownItem[]
|
|
confidence: number
|
|
suggestedTemplateId?: string
|
|
}
|
|
|
|
export interface ExtractedInvoiceLineItem {
|
|
description: string
|
|
quantity: number
|
|
unitPrice: number | null
|
|
lineTotal: number
|
|
vatRate: number | null
|
|
accountSuggestion: string | null
|
|
suggestedTemplateId?: string
|
|
}
|
|
|
|
export interface VatBreakdownItem {
|
|
rate: number
|
|
base: number
|
|
amount: number
|
|
}
|
|
|
|
// KPI Report
|
|
export interface KPIReport {
|
|
netResult: number // SEK
|
|
cashPosition: number // SEK (sum of 19xx account balances)
|
|
outstandingReceivables: number // SEK
|
|
overdueReceivables: number // SEK
|
|
vatLiability: number // SEK, ruta 49 (positive = owe, negative = refund)
|
|
totalRevenue: number // SEK
|
|
totalExpenses: number // SEK
|
|
grossMargin: number | null // percentage, null if no revenue
|
|
expenseRatio: number | null // percentage, null if no revenue
|
|
avgPaymentDays: number | null // days, null if fewer than 5 paid invoices
|
|
periodComplete: boolean // whether selected period is closed/complete
|
|
months: { label: string; income: number; expenses: number; net: number }[]
|
|
period: { start: string; end: string }
|
|
}
|
|
|
|
export interface KPIPreferences {
|
|
visibleKpis: string[]
|
|
kpiOrder: string[]
|
|
accountOverrides: Record<string, string[]>
|
|
}
|
|
|
|
// ============================================================
|
|
// Salary Module Types (Lönehantering)
|
|
// ============================================================
|
|
|
|
export type EmploymentType = 'employee' | 'company_owner' | 'board_member'
|
|
export type SalaryType = 'monthly' | 'hourly'
|
|
export type FSkattStatus = 'a_skatt' | 'f_skatt' | 'fa_skatt' | 'not_verified'
|
|
export type VacationRule = 'procentregeln' | 'sammaloneregeln'
|
|
export type SalaryRunStatus = 'draft' | 'review' | 'approved' | 'paid' | 'booked' | 'corrected'
|
|
export type AGIStatus =
|
|
| 'generated' // XML built from a salary run; nothing sent to SKV yet
|
|
| 'pending_signature' // underlag accepted into Eget utrymme; awaiting BankID
|
|
| 'exported' // legacy: manual XML download path
|
|
| 'submitted' // kvittens received; AGI is filed
|
|
| 'accepted' // reserved (SKV does not currently expose this)
|
|
| 'rejected' // reserved (kontrollresultat DONE_REJECTED could land here)
|
|
|
|
export type SalaryLineItemType =
|
|
| 'monthly_salary' | 'hourly_salary' | 'overtime' | 'bonus' | 'commission'
|
|
| 'gross_deduction_pension' | 'gross_deduction_other'
|
|
| 'benefit_car' | 'benefit_housing' | 'benefit_meals' | 'benefit_wellness' | 'benefit_other'
|
|
| 'sick_karens' | 'sick_day2_14' | 'sick_day15_plus'
|
|
| 'vab' | 'parental_leave' | 'vacation'
|
|
| 'traktamente_taxfree' | 'traktamente_taxable'
|
|
| 'mileage_taxfree' | 'mileage_taxable'
|
|
| 'net_deduction_advance' | 'net_deduction_union' | 'net_deduction_benefit_payment'
|
|
| 'net_deduction_other'
|
|
| 'correction' | 'other'
|
|
|
|
export interface Employee {
|
|
id: string
|
|
company_id: string
|
|
user_id: string
|
|
first_name: string
|
|
last_name: string
|
|
personnummer: string
|
|
personnummer_last4: string
|
|
employment_type: EmploymentType
|
|
employment_start: string
|
|
employment_end: string | null
|
|
employment_degree: number
|
|
salary_type: SalaryType
|
|
monthly_salary: number | null
|
|
hourly_rate: number | null
|
|
tax_table_number: number | null
|
|
tax_column: number
|
|
tax_municipality: string | null
|
|
jamkning_percentage: number | null
|
|
jamkning_valid_from: string | null
|
|
jamkning_valid_to: string | null
|
|
is_sidoinkomst: boolean
|
|
f_skatt_status: FSkattStatus
|
|
f_skatt_verified_at: string | null
|
|
clearing_number: string | null
|
|
bank_account_number: string | null
|
|
vacation_rule: VacationRule
|
|
vacation_days_per_year: number
|
|
vacation_days_saved: number
|
|
semestertillagg_rate: number
|
|
email: string | null
|
|
phone: string | null
|
|
address_line1: string | null
|
|
postal_code: string | null
|
|
city: string | null
|
|
specification_number: number | null
|
|
vaxa_stod_eligible: boolean
|
|
vaxa_stod_start: string | null
|
|
vaxa_stod_end: string | null
|
|
is_active: boolean
|
|
created_at: string
|
|
updated_at: string
|
|
}
|
|
|
|
export interface SalaryRun {
|
|
id: string
|
|
company_id: string
|
|
user_id: string
|
|
period_year: number
|
|
period_month: number
|
|
payment_date: string
|
|
status: SalaryRunStatus
|
|
voucher_series: string
|
|
total_gross: number
|
|
total_tax: number
|
|
total_net: number
|
|
total_avgifter: number
|
|
total_vacation_accrual: number
|
|
total_employer_cost: number
|
|
salary_entry_id: string | null
|
|
avgifter_entry_id: string | null
|
|
vacation_entry_id: string | null
|
|
agi_generated_at: string | null
|
|
agi_submitted_at: string | null
|
|
calculation_params: Record<string, unknown> | null
|
|
approved_by: string | null
|
|
approved_at: string | null
|
|
paid_at: string | null
|
|
booked_at: string | null
|
|
booked_by: string | null
|
|
notes: string | null
|
|
is_correction: boolean
|
|
corrects_run_id: string | null
|
|
created_at: string
|
|
updated_at: string
|
|
// Relations
|
|
employees?: SalaryRunEmployee[]
|
|
}
|
|
|
|
export interface SalaryRunEmployee {
|
|
id: string
|
|
salary_run_id: string
|
|
employee_id: string
|
|
company_id: string
|
|
employment_degree: number
|
|
monthly_salary: number
|
|
salary_type: string
|
|
hours_worked: number | null
|
|
gross_salary: number
|
|
gross_deductions: number
|
|
benefit_values: number
|
|
taxable_income: number
|
|
tax_withheld: number
|
|
net_deductions: number
|
|
net_salary: number
|
|
avgifter_rate: number
|
|
avgifter_amount: number
|
|
avgifter_basis: number
|
|
vacation_accrual: number
|
|
vacation_accrual_avgifter: number
|
|
tax_table_number: number | null
|
|
tax_column: number | null
|
|
tax_table_year: number | null
|
|
sick_days: number
|
|
vab_days: number
|
|
parental_days: number
|
|
vacation_days_taken: number
|
|
calculation_breakdown: Record<string, unknown> | null
|
|
ytd_gross: number
|
|
ytd_tax: number
|
|
ytd_net: number
|
|
created_at: string
|
|
updated_at: string
|
|
// Relations
|
|
employee?: Employee
|
|
line_items?: SalaryLineItem[]
|
|
}
|
|
|
|
export interface SalaryLineItem {
|
|
id: string
|
|
salary_run_employee_id: string
|
|
company_id: string
|
|
item_type: SalaryLineItemType
|
|
description: string
|
|
quantity: number | null
|
|
unit_price: number | null
|
|
amount: number
|
|
is_taxable: boolean
|
|
is_avgift_basis: boolean
|
|
is_vacation_basis: boolean
|
|
is_gross_deduction: boolean
|
|
is_net_deduction: boolean
|
|
account_number: string | null
|
|
sort_order: number
|
|
created_at: string
|
|
updated_at: string
|
|
}
|
|
|
|
export interface AGIDeclaration {
|
|
id: string
|
|
company_id: string
|
|
user_id: string
|
|
salary_run_id: string | null
|
|
period_year: number
|
|
period_month: number
|
|
xml_content: string
|
|
status: AGIStatus
|
|
individuppgifter: Record<string, unknown>[]
|
|
total_gross: number
|
|
total_tax: number
|
|
total_avgifter_basis: number
|
|
total_avgifter: number
|
|
employee_count: number
|
|
kvittensnummer: string | null
|
|
submitted_at: string | null
|
|
submitted_by: string | null
|
|
response_data: Record<string, unknown> | null
|
|
is_correction: boolean
|
|
corrects_agi_id: string | null
|
|
created_at: string
|
|
updated_at: string
|
|
}
|
|
|