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
+38
View File
@@ -134,6 +134,8 @@ describe('ingestTransactions', () => {
// Booked transaction map query (no booked transactions)
enqueue({ data: [], error: null })
// Supplier invoices fetch (no unpaid invoices)
enqueue({ data: [], error: null })
// Dedup check returns null (no existing row)
enqueue({ data: null, error: null })
// Insert returns the new transaction
@@ -158,6 +160,8 @@ describe('ingestTransactions', () => {
// Booked transaction map query
enqueue({ data: [], error: null })
// Supplier invoices fetch
enqueue({ data: [], error: null })
// Dedup check returns an existing record
enqueue({ data: { id: 'existing-tx-1' }, error: null })
@@ -177,6 +181,8 @@ describe('ingestTransactions', () => {
// Booked transaction map query
enqueue({ data: [], error: null })
// Supplier invoices fetch
enqueue({ data: [], error: null })
// Dedup check: no duplicate
enqueue({ data: null, error: null })
// Insert fails
@@ -203,6 +209,8 @@ describe('ingestTransactions', () => {
// Booked transaction map query
enqueue({ data: [], error: null })
// Supplier invoices fetch
enqueue({ data: [], error: null })
// Dedup: no duplicate
enqueue({ data: null, error: null })
// Insert returns the new transaction
@@ -243,6 +251,8 @@ describe('ingestTransactions', () => {
// Booked transaction map query
enqueue({ data: [], error: null })
// Supplier invoices fetch
enqueue({ data: [], error: null })
// Dedup: no duplicate
enqueue({ data: null, error: null })
// Insert
@@ -272,6 +282,8 @@ describe('ingestTransactions', () => {
// Booked transaction map query
enqueue({ data: [], error: null })
// Supplier invoices fetch
enqueue({ data: [], error: null })
// Dedup: no duplicate
enqueue({ data: null, error: null })
// Insert
@@ -308,6 +320,8 @@ describe('ingestTransactions', () => {
// Booked transaction map query
enqueue({ data: [], error: null })
// Supplier invoices fetch
enqueue({ data: [], error: null })
// Dedup: no duplicate
enqueue({ data: null, error: null })
// Insert
@@ -337,6 +351,8 @@ describe('ingestTransactions', () => {
// Booked transaction map query
enqueue({ data: [], error: null })
// Supplier invoices fetch
enqueue({ data: [], error: null })
// Dedup: no duplicate
enqueue({ data: null, error: null })
// Insert
@@ -365,6 +381,8 @@ describe('ingestTransactions', () => {
// Booked transaction map query
enqueue({ data: [], error: null })
// Supplier invoices fetch
enqueue({ data: [], error: null })
// Transaction 1: dedup (no match), insert OK
enqueue({ data: null, error: null })
enqueue({ data: inserted1, error: null })
@@ -402,6 +420,8 @@ describe('ingestTransactions', () => {
// Booked transaction map query
enqueue({ data: [], error: null })
// Supplier invoices fetch
enqueue({ data: [], error: null })
// Transaction rawNew: dedup (no match), insert OK
enqueue({ data: null, error: null })
enqueue({ data: insertedNew, error: null })
@@ -474,6 +494,8 @@ describe('ingestTransactions', () => {
// Booked transaction map query
enqueue({ data: [], error: null })
// Supplier invoices fetch
enqueue({ data: [], error: null })
enqueue({ data: null, error: null })
enqueue({ data: inserted, error: null })
@@ -498,6 +520,8 @@ describe('ingestTransactions', () => {
// Booked transaction map query
enqueue({ data: [], error: null })
// Supplier invoices fetch
enqueue({ data: [], error: null })
enqueue({ data: null, error: null })
enqueue({ data: inserted, error: null })
@@ -548,6 +572,8 @@ describe('ingestTransactions', () => {
// Booked transaction map query
enqueue({ data: [], error: null })
// Supplier invoices fetch
enqueue({ data: [], error: null })
// Dedup: no duplicate
enqueue({ data: null, error: null })
// Insert
@@ -590,6 +616,8 @@ describe('ingestTransactions', () => {
// Booked transaction map query
enqueue({ data: [], error: null })
// Supplier invoices fetch
enqueue({ data: [], error: null })
// Dedup: no duplicate
enqueue({ data: null, error: null })
// Insert
@@ -617,6 +645,8 @@ describe('ingestTransactions', () => {
// Booked transaction map query
enqueue({ data: [], error: null })
// Supplier invoices fetch
enqueue({ data: [], error: null })
// Dedup: no duplicate
enqueue({ data: null, error: null })
// Insert
@@ -647,6 +677,8 @@ describe('ingestTransactions', () => {
data: [{ date: '2024-06-15', amount: -250 }],
error: null,
})
// Supplier invoices fetch
enqueue({ data: [], error: null })
// external_id dedup: no match (different source)
enqueue({ data: null, error: null })
@@ -670,6 +702,8 @@ describe('ingestTransactions', () => {
data: [{ date: '2024-06-15', amount: -250 }],
error: null,
})
// Supplier invoices fetch
enqueue({ data: [], error: null })
// external_id dedup: no match
enqueue({ data: null, error: null })
// Insert
@@ -702,6 +736,8 @@ describe('ingestTransactions', () => {
],
error: null,
})
// Supplier invoices fetch
enqueue({ data: [], error: null })
// raw1: external_id dedup (no match) -> content dedup matches (bookedCount=2 -> 1)
enqueue({ data: null, error: null })
@@ -726,6 +762,8 @@ describe('ingestTransactions', () => {
// Booked map query throws (caught by try/catch in buildBookedTransactionMap)
enqueue({ error: { message: 'Query failed' } })
// Supplier invoices fetch
enqueue({ data: [], error: null })
// external_id dedup: no match
enqueue({ data: null, error: null })
// Insert