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:
co-authored by
greptile-apps[bot]
parent
6c79f21679
commit
211033410c
@@ -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 }
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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 }
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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 }
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user