feat: add foreign currency support, refactor bookkeeping engine, and improve invoice inbox document classification

- Add currency-utils module for SEK conversion with exchange rates
- Refactor createJournalEntry to use draft+commit flow preventing voucher number gaps (BFL 5 kap. 7§)
- Add foreign currency support to invoice entries with per-line SEK conversion
- Centralize category-to-account mapping into single source of truth
- Refactor invoice inbox to use shared document analyzer with document type classification (receipt, supplier invoice, government letter)
- Update mapping engine, supplier invoice entries, and transaction entries
- Fix report component rendering issues
- Add new validation schemas and tests

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Jakob Wennberg
2026-02-27 09:50:06 +01:00
co-authored by Claude Opus 4.6
parent 547fd053ec
commit 6f4573f380
33 changed files with 1365 additions and 497 deletions
@@ -126,7 +126,8 @@ describe('POST /api/invoices/[id]/mark-paid', () => {
expect.anything(),
'user-1',
expect.objectContaining({ id: 'inv-1' }),
expect.any(String)
expect.any(String),
undefined
)
})
+20 -2
View File
@@ -4,6 +4,7 @@ import {
createInvoicePaymentJournalEntry,
createInvoiceCashEntry,
} from '@/lib/bookkeeping/invoice-entries'
import { MarkInvoicePaidSchema } from '@/lib/api/schemas'
import type { EntityType, Invoice } from '@/types'
/**
@@ -49,8 +50,24 @@ export async function POST(
)
}
// Parse optional body (backward compatible — body may be empty)
let exchangeRateDifference: number | undefined
let bodyPaymentDate: string | undefined
try {
const text = await request.text()
if (text) {
const parsed = MarkInvoicePaidSchema.safeParse(JSON.parse(text))
if (parsed.success) {
exchangeRateDifference = parsed.data.exchange_rate_difference
bodyPaymentDate = parsed.data.payment_date
}
}
} catch {
// No body or invalid JSON — use defaults
}
const now = new Date().toISOString()
const paymentDate = now.split('T')[0]
const paymentDate = bodyPaymentDate || now.split('T')[0]
// Update status to paid
const { error: updateError } = await supabase
@@ -89,7 +106,8 @@ export async function POST(
supabase,
user.id,
invoice as Invoice,
paymentDate
paymentDate,
exchangeRateDifference
)
journalEntryId = journalEntry?.id ?? null
} else {