Files
finance-app/src/app/api/import/csv/route.ts
T
siddharthd 4a49add277 feat: CSV import and batch reconciliation UI
- Add reconciled_with_id column to transactions (links manual → statement tx)
- CSV import wizard: 4-step modal (upload → map columns → review → done)
  - Handles any bank format via column mapping with localStorage presets
  - Single signed or separate debit/credit column modes
  - Editable preview table before committing
  - Auto-tags all imported rows with 'csv-import'
- Batch reconcile page: shows all unreconciled manual transactions with
  potential statement matches (date ±3 days, amount ±1%) pre-fetched
  - Select matches across multiple rows, apply all at once
  - Copies overrides/tags/splits from manual → statement tx atomically
  - Manual tx marked reconciled (linked), hidden from main transactions view
  - Transactions with no matches shown separately
- Import CSV button on transactions page
- Reconcile nav item in sidebar
2026-04-13 06:23:08 +10:00

32 lines
1.0 KiB
TypeScript

import { NextRequest, NextResponse } from "next/server";
import { getCurrentUser } from "@/lib/auth";
import { ensureTag, batchInsertCSVTransactions } from "@/lib/queries";
export async function POST(req: NextRequest) {
const user = await getCurrentUser(req);
if (!user) return NextResponse.json({ error: "Unauthorized" }, { status: 403 });
const body = await req.json() as {
bank_name: string;
transactions: {
date: string;
description: string;
amount: number;
transaction_type: string;
merchant_name?: string;
foreign_currency_amount?: number;
foreign_currency_code?: string;
category?: string;
}[];
};
if (!Array.isArray(body.transactions) || body.transactions.length === 0) {
return NextResponse.json({ error: "No transactions provided" }, { status: 400 });
}
const tagId = await ensureTag("csv-import", "#8b5cf6");
const inserted = await batchInsertCSVTransactions(user.id, body.transactions, tagId);
return NextResponse.json({ inserted }, { status: 201 });
}