feat: unified document inbox, full BAS 2026, and document-transaction matching

- Expand BAS reference from ~180 to ~1,276 accounts (full BAS Kontoplan 2026)
  with K2 exclusion flags, per-class data files, and computed SRU codes
- Evolve invoice inbox into unified document inbox handling invoices, receipts,
  and government letters with AI-powered classification (Claude Haiku Vision)
- Add multi-pass document-to-transaction matching engine with greedy assignment
  for both supplier invoices (reference/amount/date/name) and receipts
  (weighted amount/merchant/date scoring)
- Add supplier invoice matching in transaction ingest pipeline
- Inject booking template suggestions into AI extraction prompts
- Surface matched documents in swipe categorization UI with one-tap booking
- Auto-activate missing BAS accounts during SIE import against full reference
- Add K2 filter toggle in Chart of Accounts manager
- Add receipt confirmation route with BFNAR representation fields
- Add database migrations for K2 support and document matching columns
- Remove obsolete extension migration scripts

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Jakob Wennberg
2026-02-25 16:59:02 +01:00
co-authored by Claude Opus 4.6
parent 6956a757f3
commit 39e407644d
68 changed files with 19234 additions and 2412 deletions
@@ -46,10 +46,19 @@ export interface CategorizationContext {
recentHistory: { description: string; category: string }[]
}
export interface DocumentEnrichment {
type: 'receipt' | 'supplier_invoice'
merchantName?: string
lineItems?: Array<{ description: string; amount: number; category?: string; accountSuggestion?: string }>
vatBreakdown?: Array<{ rate: number; amount: number }>
isReverseCharge?: boolean
}
export interface EnrichedCategorizationContext extends CategorizationContext {
candidateTemplates: BookingTemplate[]
userAccountUsage: AccountUsageEntry[]
merchantHistory: MerchantHistoryEntry[]
documentData?: DocumentEnrichment
}
export interface CategorizationSuggestion {
@@ -223,6 +232,11 @@ export class AnthropicCategorizationProvider implements CategorizationProvider {
.join('\n')}`
: ''
// Build document enrichment context (from linked receipt or supplier invoice)
const documentContext = enriched?.documentData
? buildDocumentContext(enriched.documentData)
: ''
const systemPrompt = `Du är expert på svensk bokföring och kategorisering av banktransaktioner enligt BAS-kontoplanen.
Din uppgift är att kategorisera varje transaktion till rätt mall-ID (templateId) och BAS-konto.
@@ -243,7 +257,7 @@ MOMSHANTERING:
- Intäkter: Normalt 25% moms (utgående moms, MP1)
${NON_DEDUCTIBLE_RULES}
${documentContext}
REGLER:
1. Negativa belopp = utgifter, positiva = intäkter
2. VIKTIGT: Dessa transaktioner kommer från företagets bankkonto/kort. Anta ALLTID att de är affärsrelaterade. Klassificera ALDRIG som "private" — det beslutet tar användaren själv.
@@ -382,6 +396,42 @@ ${transactionList}`
}
}
function buildDocumentContext(doc: DocumentEnrichment): string {
const parts: string[] = []
const typeLabel = doc.type === 'receipt' ? 'KVITTO' : 'LEVERANTÖRSFAKTURA'
parts.push(`LÄNKAT DOKUMENT (${typeLabel}):`)
if (doc.merchantName) {
parts.push(`Handlare/leverantör: ${doc.merchantName}`)
}
if (doc.lineItems && doc.lineItems.length > 0) {
parts.push('Rader:')
for (const item of doc.lineItems) {
let line = `- ${item.description}: ${item.amount} kr`
if (item.accountSuggestion) line += ` (föreslaget konto: ${item.accountSuggestion})`
if (item.category) line += ` [${item.category}]`
parts.push(line)
}
}
if (doc.vatBreakdown && doc.vatBreakdown.length > 0) {
parts.push('Momsfördelning:')
for (const vat of doc.vatBreakdown) {
parts.push(`- ${vat.rate}%: ${vat.amount} kr`)
}
}
if (doc.isReverseCharge) {
parts.push(`VIKTIGT: Omvänd skattskyldighet (reverse charge). Använd dubbelkontering:
- Debitera 2645 (beräknad ingående moms) OCH kreditera 2614 (utgående moms, omvänd skattskyldighet)
- Mallen "purchase_eu_service_reverse_charge" ska användas om tillgänglig`)
}
return parts.join('\n') + '\n'
}
function isEnrichedContext(
ctx: CategorizationContext | EnrichedCategorizationContext
): ctx is EnrichedCategorizationContext {