Fix/import data (#200)

* fix: enhance import data handling and consent management across components

* feat: Enhance SIE import functionality with validation and error handling improvements

- Added validation errors and warnings state management in SIEImportWizard.
- Improved error handling for duplicate, validation, and parsing errors during SIE file import.
- Enhanced user feedback with actionable guidance for common import errors.
- Updated SIEUploadStep to display validation errors and warnings.
- Improved error messages in API routes for better clarity and user experience.
- Added file size and type validation in the SIE parse route.
- Enhanced parsing logic to provide more detailed error messages for unbalanced vouchers and missing amounts.
- Created a new storage bucket for SIE file archival in Supabase with appropriate policies for user access.
- Updated tests to reflect changes in error messages and validation logic.

* fix: Improve type assertion for response in getPage method

* Update extensions/general/arcim-migration/lib/migration-orchestrator.ts

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>

* Update supabase/migrations/20260408130000_sie_files_storage_bucket.sql

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>

* fix: Add company ID verification for consent handling in accept and disconnect endpoints

---------

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
This commit is contained in:
Mattsson
2026-04-08 18:17:00 +02:00
committed by GitHub
co-authored by greptile-apps[bot]
parent 6c79f21679
commit 211033410c
20 changed files with 529 additions and 126 deletions
+3 -3
View File
@@ -71,7 +71,7 @@ export async function POST(request: Request) {
const accounts: SIEAccount[] = body.accounts
if (!accounts || !Array.isArray(accounts) || accounts.length === 0) {
return NextResponse.json({ error: 'No accounts provided' }, { status: 400 })
return NextResponse.json({ error: 'Inga konton att skapa.' }, { status: 400 })
}
// Prepare accounts for upsert (idempotent — safe to retry)
@@ -117,7 +117,7 @@ export async function POST(request: Request) {
if (error) {
console.error('Error upserting accounts batch:', error)
return NextResponse.json({
error: `Failed to create accounts: ${error.message}`,
error: `Kunde inte skapa konton (batch ${Math.floor(i / batchSize) + 1}): ${error.message}. ${totalCreated} konton skapades innan felet.`,
created: totalCreated,
}, { status: 500 })
}
@@ -134,7 +134,7 @@ export async function POST(request: Request) {
} catch (error) {
console.error('Create accounts error:', error)
return NextResponse.json(
{ error: error instanceof Error ? error.message : 'Failed to create accounts' },
{ error: `Kunde inte skapa konton: ${error instanceof Error ? error.message : 'Okänt fel'}. Försök igen.` },
{ status: 500 }
)
}
+10 -5
View File
@@ -37,7 +37,7 @@ export async function POST(request: Request) {
const optionsJson = formData.get('options') as string | null
if (!file) {
return NextResponse.json({ error: 'No file provided' }, { status: 400 })
return NextResponse.json({ error: 'Ingen fil bifogad. Gå tillbaka och ladda upp filen igen.' }, { status: 400 })
}
// Parse options
@@ -87,9 +87,11 @@ export async function POST(request: Request) {
// Validate all accounts are mapped
const unmapped = mappings.filter((m) => !m.targetAccount)
if (unmapped.length > 0) {
const accountList = unmapped.slice(0, 5).map((m) => `${m.sourceAccount} (${m.sourceName})`).join(', ')
const remaining = unmapped.length > 5 ? ` och ${unmapped.length - 5} till` : ''
return NextResponse.json({
error: 'validation',
message: `${unmapped.length} account(s) are not mapped`,
message: `${unmapped.length} konto(n) saknar mappning: ${accountList}${remaining}. Gå tillbaka till kontomappningssteget och koppla alla konton.`,
unmappedAccounts: unmapped.map((m) => ({
account: m.sourceAccount,
name: m.sourceName,
@@ -183,7 +185,7 @@ export async function POST(request: Request) {
if (activateError) {
return NextResponse.json({
error: `Failed to activate accounts: ${activateError.message}`,
error: `Kunde inte aktivera konton i kontoplanen: ${activateError.message}. Kontrollera att kontona inte redan finns med andra inställningar.`,
}, { status: 500 })
}
}
@@ -208,7 +210,7 @@ export async function POST(request: Request) {
if (!result.success) {
return NextResponse.json({
error: 'import',
message: 'Import completed with errors',
message: 'Importen slutfördes med fel. Se detaljerna nedan för att förstå vad som gick snett.',
result,
}, { status: 400 })
}
@@ -219,8 +221,11 @@ export async function POST(request: Request) {
})
} catch (error) {
console.error('SIE import error:', error)
const detail = error instanceof Error ? error.message : ''
return NextResponse.json(
{ error: error instanceof Error ? error.message : 'Failed to import SIE file' },
{
error: `Importen avbröts oväntat. Ingen data har sparats.${detail ? ` (${detail})` : ''} Försök igen — om felet kvarstår, kontakta support.`,
},
{ status: 500 }
)
}
+25 -4
View File
@@ -36,14 +36,31 @@ export async function POST(request: Request) {
const file = formData.get('file') as File | null
if (!file) {
return NextResponse.json({ error: 'No file provided' }, { status: 400 })
return NextResponse.json({ error: 'parse', message: 'Ingen fil bifogad i förfrågan.' }, { status: 400 })
}
// Validate file type
const filename = file.name.toLowerCase()
if (!filename.endsWith('.sie') && !filename.endsWith('.se')) {
return NextResponse.json(
{ error: 'Invalid file type. Please upload a .sie file' },
{ error: 'parse', message: 'Filtypen stöds inte. Ladda upp en fil med ändelsen .sie eller .se.' },
{ status: 400 }
)
}
// Validate file size (max 50 MB)
const MAX_FILE_SIZE = 50 * 1024 * 1024
if (file.size > MAX_FILE_SIZE) {
return NextResponse.json(
{ error: 'parse', message: `Filen är för stor (${(file.size / 1024 / 1024).toFixed(1)} MB). Maxstorlek är 50 MB.` },
{ status: 400 }
)
}
// Validate file is not empty
if (file.size === 0) {
return NextResponse.json(
{ error: 'parse', message: 'Filen är tom (0 bytes). Kontrollera att exporten från bokföringsprogrammet genomfördes korrekt.' },
{ status: 400 }
)
}
@@ -92,7 +109,7 @@ export async function POST(request: Request) {
if (!validation.valid) {
return NextResponse.json({
error: 'validation',
message: 'SIE file has validation errors',
message: 'SIE-filen innehåller valideringsfel som måste åtgärdas innan import.',
errors: validation.errors,
warnings: validation.warnings,
}, { status: 400 })
@@ -151,8 +168,12 @@ export async function POST(request: Request) {
})
} catch (error) {
console.error('SIE parse error:', error)
const detail = error instanceof Error ? error.message : ''
return NextResponse.json(
{ error: error instanceof Error ? error.message : 'Failed to parse SIE file' },
{
error: 'parse',
message: `Kunde inte tolka SIE-filen. Filen kan vara skadad eller i ett format som inte stöds.${detail ? ` (${detail})` : ''}`,
},
{ status: 500 }
)
}