import { NextResponse } from 'next/server' import { parseSIEFile, validateSIEFile, detectEncoding, decodeBuffer, calculateFileHash, } from '@/lib/import/sie-parser' import { suggestMappings, getMappingStats, isSystemAccount } from '@/lib/import/account-mapper' import { generateImportPreview, checkDuplicateImport, checkDuplicatePeriodImport } from '@/lib/import/sie-import' import { BAS_REFERENCE } from '@/lib/bookkeeping/bas-data' import { withRouteContext } from '@/lib/api/with-route-context' import { errorResponseFromCode } from '@/lib/errors/get-structured-error' import type { SIEAccountMappingRecord } from '@/lib/import/types' import { getErrorMessage as getUserErrorMessage } from '@/lib/errors/get-error-message' /** * POST /api/import/sie/parse * Parse an uploaded SIE file and return preview data. */ export const POST = withRouteContext( 'sie_import.parse', async (request, ctx) => { const { supabase, companyId, log, requestId } = ctx const formData = await request.formData() const file = formData.get('file') as File | null if (!file) { return errorResponseFromCode('SIE_PARSE_NO_FILE', log, { requestId }) } const filename = file.name.toLowerCase() if (!filename.endsWith('.sie') && !filename.endsWith('.se')) { return errorResponseFromCode('SIE_PARSE_INVALID_TYPE', log, { requestId, details: { filename: file.name }, }) } const MAX_FILE_SIZE = 50 * 1024 * 1024 if (file.size > MAX_FILE_SIZE) { return errorResponseFromCode('SIE_PARSE_FILE_TOO_LARGE', log, { requestId, details: { sizeMb: +(file.size / 1024 / 1024).toFixed(1) }, }) } if (file.size === 0) { return errorResponseFromCode('SIE_PARSE_EMPTY', log, { requestId }) } const opLog = log.child({ filename: file.name, sizeBytes: file.size }) try { const arrayBuffer = await file.arrayBuffer() const encoding = detectEncoding(arrayBuffer) const content = decodeBuffer(arrayBuffer, encoding) const duplicate = await checkDuplicateImport(supabase, companyId!, content) if (duplicate) { return errorResponseFromCode('SIE_DUPLICATE_FILE', opLog, { requestId, details: { importId: duplicate.id, importedAt: duplicate.imported_at, }, }) } const parsed = parseSIEFile(content) if (parsed.stats.fiscalYearStart && parsed.stats.fiscalYearEnd) { const periodDuplicate = await checkDuplicatePeriodImport( supabase, companyId!, parsed.stats.fiscalYearStart, parsed.stats.fiscalYearEnd, ) if (periodDuplicate) { return errorResponseFromCode('SIE_DUPLICATE_PERIOD', opLog, { requestId, details: { importId: periodDuplicate.id, fiscalYearStart: periodDuplicate.fiscal_year_start, fiscalYearEnd: periodDuplicate.fiscal_year_end, importedAt: periodDuplicate.imported_at, }, }) } } const validation = validateSIEFile(parsed) if (!validation.valid) { return errorResponseFromCode('SIE_PARSE_VALIDATION_FAILED', opLog, { requestId, details: { errors: validation.errors, warnings: validation.warnings }, }) } 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)) const { data: storedMappings } = await supabase .from('sie_account_mappings') .select('*') .eq('company_id', companyId) const mappings = suggestMappings( bookkeepingAccounts, BAS_REFERENCE, (storedMappings as SIEAccountMappingRecord[]) || undefined, ) const preview = generateImportPreview(parsed, mappings) preview.excludedSystemAccounts = excludedSystemAccounts preview.accountCount = bookkeepingAccounts.length const fileHash = await calculateFileHash(content) return NextResponse.json({ success: true, encoding, fileHash, parsed: { header: parsed.header, accounts: parsed.accounts, stats: parsed.stats, issues: parsed.issues, }, mappings, mappingStats: getMappingStats(mappings), preview, validation: { valid: validation.valid, errors: validation.errors, warnings: validation.warnings, }, }) } catch (err) { opLog.error('sie parse failed', err as Error) return errorResponseFromCode('SIE_PARSE_FAILED', opLog, { requestId, details: { reason: err instanceof Error ? getUserErrorMessage(err) : 'unknown' }, }) } }, )