Broad update across dashboard pages, components, extensions, and lib code. Includes ESLint config additions, onboarding flow redesign, settings page refactor, help page content expansion, dead code removal, and test mock fixes. Adds dev docs and public assets. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
131 lines
4.2 KiB
TypeScript
131 lines
4.2 KiB
TypeScript
import { createClient } from '@/lib/supabase/server'
|
|
import { NextResponse } from 'next/server'
|
|
import { eventBus } from '@/lib/events'
|
|
import { ensureInitialized } from '@/lib/init'
|
|
import { ingestTransactions, type RawTransaction } from '@/lib/transactions/ingest'
|
|
import { generateExternalId } from '@/lib/import/bank-file/parser'
|
|
import type { ParsedBankTransaction, BankFileFormatId } from '@/lib/import/bank-file/types'
|
|
import type { Transaction } from '@/types'
|
|
|
|
ensureInitialized()
|
|
|
|
interface ExecuteRequest {
|
|
transactions: ParsedBankTransaction[]
|
|
format: BankFileFormatId
|
|
filename: string
|
|
file_hash: string
|
|
skip_duplicates: boolean
|
|
auto_categorize: boolean
|
|
}
|
|
|
|
/**
|
|
* POST /api/import/bank-file/execute
|
|
*
|
|
* Executes the import of confirmed bank transactions.
|
|
* Records import in bank_file_imports, calls ingestTransactions(),
|
|
* emits transaction.synced event.
|
|
*/
|
|
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 body: ExecuteRequest = await request.json()
|
|
const { transactions, format, filename, file_hash, skip_duplicates: _skip_duplicates = true, auto_categorize: _auto_categorize = true } = body
|
|
|
|
if (!transactions || transactions.length === 0) {
|
|
return NextResponse.json({ error: 'No transactions to import' }, { status: 400 })
|
|
}
|
|
|
|
try {
|
|
// Create import record
|
|
const { data: importRecord, error: importError } = await supabase
|
|
.from('bank_file_imports')
|
|
.upsert({
|
|
user_id: user.id,
|
|
filename,
|
|
file_hash,
|
|
file_format: format,
|
|
transaction_count: transactions.length,
|
|
status: 'processing',
|
|
date_from: transactions.map(t => t.date).sort()[0] || null,
|
|
date_to: transactions.map(t => t.date).sort().reverse()[0] || null,
|
|
}, {
|
|
onConflict: 'user_id,file_hash',
|
|
})
|
|
.select()
|
|
.single()
|
|
|
|
if (importError) {
|
|
console.error('Failed to create import record:', importError)
|
|
return NextResponse.json({ error: 'Failed to create import record' }, { status: 500 })
|
|
}
|
|
|
|
// Convert parsed transactions to RawTransaction format
|
|
const rawTransactions: RawTransaction[] = transactions.map((tx, index) => ({
|
|
date: tx.date,
|
|
description: tx.description,
|
|
amount: tx.amount,
|
|
currency: tx.currency || 'SEK',
|
|
external_id: generateExternalId(tx, format, index),
|
|
reference: tx.reference || null,
|
|
import_source: format === 'camt053' ? 'camt053' : `csv_${format}`,
|
|
}))
|
|
|
|
// Run ingestion pipeline
|
|
const ingestResult = await ingestTransactions(supabase, user.id, rawTransactions)
|
|
|
|
// Update import record with results
|
|
await supabase
|
|
.from('bank_file_imports')
|
|
.update({
|
|
imported_count: ingestResult.imported,
|
|
duplicate_count: ingestResult.duplicates,
|
|
matched_count: ingestResult.auto_matched_invoices,
|
|
status: ingestResult.errors > 0 && ingestResult.imported === 0 ? 'failed' : 'completed',
|
|
error_message: ingestResult.errors > 0
|
|
? `${ingestResult.errors} transactions failed to import`
|
|
: null,
|
|
})
|
|
.eq('id', importRecord.id)
|
|
|
|
// Emit event with newly imported transactions
|
|
if (ingestResult.imported > 0 && ingestResult.transaction_ids.length > 0) {
|
|
try {
|
|
const { data: importedTransactions } = await supabase
|
|
.from('transactions')
|
|
.select('*')
|
|
.in('id', ingestResult.transaction_ids)
|
|
|
|
if (importedTransactions && importedTransactions.length > 0) {
|
|
await eventBus.emit({
|
|
type: 'transaction.synced',
|
|
payload: {
|
|
transactions: importedTransactions as Transaction[],
|
|
userId: user.id,
|
|
},
|
|
})
|
|
}
|
|
} catch {
|
|
// Non-critical event emission
|
|
}
|
|
}
|
|
|
|
return NextResponse.json({
|
|
data: {
|
|
import_id: importRecord.id,
|
|
...ingestResult,
|
|
},
|
|
})
|
|
} catch (error) {
|
|
console.error('Bank file execute error:', error)
|
|
return NextResponse.json(
|
|
{ error: error instanceof Error ? error.message : 'Import failed' },
|
|
{ status: 500 }
|
|
)
|
|
}
|
|
}
|