import { NextRequest, NextResponse } from "next/server" import prisma from "@/lib/prisma" import Papa from "papaparse" import { matchBankRow } from "@/lib/matching" import { resolveOrgId } from "@/lib/org" export async function POST(request: NextRequest) { try { if (!prisma) { return NextResponse.json({ error: "Database not configured" }, { status: 503 }) } const orgId = await resolveOrgId(request.headers.get("x-org-id")) if (!orgId) { return NextResponse.json({ error: "Organization not found" }, { status: 404 }) } const formData = await request.formData() const file = formData.get("file") as File const mappingJson = formData.get("mapping") as string if (!file) { return NextResponse.json({ error: "No file uploaded" }, { status: 400 }) } let mapping: Record = {} try { mapping = mappingJson ? JSON.parse(mappingJson) : {} } catch { return NextResponse.json({ error: "Invalid column mapping JSON" }, { status: 400 }) } const csvText = await file.text() const parsed = Papa.parse(csvText, { header: true, skipEmptyLines: true }) if (parsed.errors.length > 0 && parsed.data.length === 0) { return NextResponse.json({ error: "CSV parse error", details: parsed.errors }, { status: 400 }) } // Get all unmatched pledges for this org const openPledges = await prisma.pledge.findMany({ where: { organizationId: orgId, status: { in: ["new", "initiated", "overdue"] }, }, select: { id: true, reference: true, amountPence: true }, }) const pledgeMap = new Map( openPledges.map((p: { id: string; reference: string; amountPence: number }) => [p.reference, { id: p.id, amountPence: p.amountPence }]) ) // Convert rows and match const rows = (parsed.data as Record[]).map((raw) => ({ date: raw[mapping.dateCol || "Date"] || "", description: raw[mapping.descriptionCol || "Description"] || "", amount: parseFloat(raw[mapping.creditCol || mapping.amountCol || "Amount"] || "0"), reference: raw[mapping.referenceCol || "Reference"] || "", raw, })) const results = rows .filter((r) => r.amount > 0) // only credits .map((r) => matchBankRow(r, pledgeMap)) // Create import record const importRecord = await prisma.import.create({ data: { organizationId: orgId, kind: "bank_statement", fileName: file.name, rowCount: rows.length, matchedCount: results.filter((r) => r.confidence === "exact").length, unmatchedCount: results.filter((r) => r.confidence === "none").length, mappingConfig: mapping, status: "completed", stats: { totalRows: rows.length, credits: rows.filter((r) => r.amount > 0).length, exactMatches: results.filter((r) => r.confidence === "exact").length, partialMatches: results.filter((r) => r.confidence === "partial").length, unmatched: results.filter((r) => r.confidence === "none").length, }, }, }) // Auto-confirm exact matches const confirmed: string[] = [] for (const result of results) { if (result.confidence === "exact" && result.pledgeId) { await prisma.$transaction([ prisma.pledge.update({ where: { id: result.pledgeId }, data: { status: "paid", paidAt: new Date() }, }), prisma.payment.create({ data: { pledgeId: result.pledgeId, provider: "bank", amountPence: Math.round(result.matchedAmount * 100), status: "confirmed", matchedBy: "auto", receivedAt: new Date(result.bankRow.date) || new Date(), importId: importRecord.id, }, }), // Skip remaining reminders prisma.reminder.updateMany({ where: { pledgeId: result.pledgeId, status: "pending" }, data: { status: "skipped" }, }), ]) confirmed.push(result.pledgeId) } } return NextResponse.json({ importId: importRecord.id, summary: { totalRows: rows.length, credits: rows.filter((r) => r.amount > 0).length, exactMatches: results.filter((r) => r.confidence === "exact").length, partialMatches: results.filter((r) => r.confidence === "partial").length, unmatched: results.filter((r) => r.confidence === "none").length, autoConfirmed: confirmed.length, }, matches: results.map((r) => ({ ...r, autoConfirmed: r.pledgeId ? confirmed.includes(r.pledgeId) : false, })), }) } catch (error) { console.error("Bank import error:", error) return NextResponse.json({ error: "Internal error" }, { status: 500 }) } }