Files
accounted/app/api/import/opening-balance/parse/route.ts
T
Mattsson a3fea6fb7c feat: add opening balance import functionality (#238)
- Implemented OpeningBalanceResultStep component to display results of the import process, including success messages and error handling.
- Created OpeningBalanceUploadStep component for file upload with drag-and-drop support, including validation for accepted file types.
- Developed column detection logic in column-detector.ts to identify account number, name, debit, credit, and balance columns based on headers and data.
- Added parser functionality in parser.ts to handle parsing of opening balance files, including validation and BAS account matching.
- Created tests for column detection and parsing logic to ensure accuracy and reliability.
- Defined types for detected columns and parsed rows in types.ts to improve type safety and clarity in the codebase.
2026-04-14 15:35:50 +02:00

75 lines
2.2 KiB
TypeScript

import { createClient } from '@/lib/supabase/server'
import { NextResponse } from 'next/server'
import { parseOpeningBalanceFile } from '@/lib/import/opening-balance/parser'
import type { DetectedColumns } from '@/lib/import/opening-balance/types'
const ALLOWED_EXTENSIONS = ['.xlsx', '.xls', '.csv', '.ods']
const MAX_FILE_SIZE = 10 * 1024 * 1024 // 10MB
/**
* POST /api/import/opening-balance/parse
*
* Accepts an Excel/CSV file via FormData, auto-detects columns,
* returns parsed opening balance rows with BAS matching.
*/
export async function POST(request: Request) {
const supabase = await createClient()
const { data: { user } } = await supabase.auth.getUser()
if (!user) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
}
const formData = await request.formData()
const file = formData.get('file') as File | null
const columnOverridesRaw = formData.get('column_overrides') as string | null
if (!file) {
return NextResponse.json({ error: 'Ingen fil bifogad' }, { status: 400 })
}
// Validate file size
if (file.size > MAX_FILE_SIZE) {
return NextResponse.json(
{ error: 'Filen är för stor (max 10MB)' },
{ status: 400 },
)
}
// Validate file extension
const ext = '.' + file.name.split('.').pop()?.toLowerCase()
if (!ALLOWED_EXTENSIONS.includes(ext)) {
return NextResponse.json(
{ error: `Filformatet stöds inte. Tillåtna format: ${ALLOWED_EXTENSIONS.join(', ')}` },
{ status: 400 },
)
}
try {
const buffer = await file.arrayBuffer()
// Parse optional column overrides
let columnOverrides: DetectedColumns | undefined
if (columnOverridesRaw) {
try {
columnOverrides = JSON.parse(columnOverridesRaw)
} catch {
return NextResponse.json(
{ error: 'Ogiltigt kolumnmappningsformat' },
{ status: 400 },
)
}
}
const result = parseOpeningBalanceFile(buffer, file.name, columnOverrides)
return NextResponse.json({ data: result })
} catch (error) {
console.error('Opening balance parse error:', error)
return NextResponse.json(
{ error: error instanceof Error ? error.message : 'Kunde inte tolka filen' },
{ status: 500 },
)
}
}