feat: arcim migration wizard UX, import fixes, Sentry setup (#22)

* feat: import system improvements, INK2 fix, and Swedish text corrections

- SIE parser: Windows-1252 and CP437 encoding detection and decoding
- Bank file parser: add Nordea Business (Företag) CSV format
- Bank file parser: improve format detection for SEB, Länsförsäkringar, generic CSV
- INK2 engine: calculate årets resultat (7222) from income statement for open fiscal years
- Dashboard: parallel Supabase queries, simplified dashboard page
- Fix Swedish characters (å, ä, ö) in BAS data descriptions, validation messages, AI consent disclosures
- Import wizard UI improvements across all steps
- Migration: add 'bas_range' match type to sie_account_mappings constraint
- Extensive new tests for SIE parser encoding and bank file parser

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat: arcim migration wizard UX fixes, Sentry setup, and extension scaffolding

Arcim migration wizard improvements:
- Progress bar now excludes non-interactive steps (migrating/result)
- Fix OAuth text to match target="_blank" behavior (new tab, not redirect)
- Display month names instead of "Månad X" in preview
- Fix Swedish typo "förifylla" in no-company-info message
- Replace native checkboxes with shadcn Switch in options step
- Add ConfirmationDialog before starting migration
- Show progress percentage during migration
- Add "Nästa steg" guidance and navigation links in result step
- Add "Försök igen" button in error state (returns to options)
- Add Bokio company ID help text (GUID from URL)
- Add Fortnox integration add-on hint on connection failure

Also includes: SIE import system improvements, INK2 fixes, Swedish text
corrections, Sentry error tracking setup, and arcim-migration extension
scaffolding.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: address PR review feedback

- Fix OAuth error recovery blank page (restore provider from URL params)
- Pass real userId to MigrationWizard instead of empty string
- Remove ~50 debug console.log statements from sie-import.ts
- Fix comment referencing account 3740 → 3741

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Jakob Wennberg
2026-03-13 16:24:54 +01:00
committed by GitHub
co-authored by Claude Opus 4.6
parent 82ad630448
commit 2ad8731dc9
44 changed files with 5108 additions and 122 deletions
+24 -1
View File
@@ -40,7 +40,12 @@ export async function GET(
/**
* DELETE /api/import/sie/[id]
* Delete an import record (does not delete created journal entries)
* Delete an import record.
*
* Only failed or pending imports can be deleted. Completed imports have created
* journal entries that are part of räkenskapsinformation — deleting the metadata
* without reversing entries would leave orphaned bookkeeping data, and deleting
* both is prohibited under BFL 7 kap (7-year retention).
*/
export async function DELETE(
request: Request,
@@ -57,6 +62,24 @@ export async function DELETE(
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
}
// Check current status before deleting
const { data: importRecord } = await supabase
.from('sie_imports')
.select('status')
.eq('id', id)
.eq('user_id', user.id)
.single()
if (!importRecord) {
return NextResponse.json({ error: 'Import not found' }, { status: 404 })
}
if (importRecord.status === 'completed') {
return NextResponse.json({
error: 'Slutförd import kan inte raderas. Importerade verifikationer ingår i räkenskapsinformationen (BFL 7 kap).',
}, { status: 403 })
}
const { error } = await supabase
.from('sie_imports')
.delete()
+13 -2
View File
@@ -7,7 +7,7 @@ import {
decodeBuffer,
calculateFileHash,
} from '@/lib/import/sie-parser'
import { suggestMappings, getMappingStats } from '@/lib/import/account-mapper'
import { suggestMappings, getMappingStats, isSystemAccount } from '@/lib/import/account-mapper'
import { generateImportPreview, checkDuplicateImport } from '@/lib/import/sie-import'
import { BAS_REFERENCE } from '@/lib/bookkeeping/bas-data'
import type { SIEAccountMappingRecord } from '@/lib/import/types'
@@ -78,6 +78,15 @@ export async function POST(request: Request) {
}, { status: 400 })
}
// Separate source-system internal accounts (e.g. Fortnox 0099) from
// real bookkeeping accounts. System accounts have no BAS equivalent and
// should not appear in the mapping step.
const excludedSystemAccounts = parsed.accounts
.filter((a) => isSystemAccount(a.number))
.map((a) => ({ number: a.number, name: a.name }))
const bookkeepingAccounts = parsed.accounts
.filter((a) => !isSystemAccount(a.number))
// Fetch stored mappings from database
const { data: storedMappings } = await supabase
.from('sie_account_mappings')
@@ -88,13 +97,15 @@ export async function POST(request: Request) {
// the user's active chart (~40 accounts). Accounts that match will be
// auto-activated during the execute step.
const mappings = suggestMappings(
parsed.accounts,
bookkeepingAccounts,
BAS_REFERENCE,
(storedMappings as SIEAccountMappingRecord[]) || undefined
)
// Generate preview
const preview = generateImportPreview(parsed, mappings)
preview.excludedSystemAccounts = excludedSystemAccounts
preview.accountCount = bookkeepingAccounts.length
// Calculate file hash for storage
const fileHash = await calculateFileHash(content)