Files
personal-admin-2026/apps/backend/src/modules/expenses/handlers/importPeriodicExpenses.ts

95 lines
2.5 KiB
TypeScript

import type { Context } from "hono";
import { prisma } from "../../../lib/prisma";
import { logger } from "../../../lib/logger";
function parseArgentineAmount(raw: string): number {
const cleaned = raw.replace("$", "").replace(/,/g, "").trim();
return Number(cleaned);
}
function parsePeriods(raw: string): number[] {
const inner = raw.replace(/^\{|\}$/g, "").trim();
if (!inner) return [];
return inner.split(",").map(Number);
}
export async function importPeriodicExpensesHandler(c: Context) {
const body = await c.req.parseBody();
const file = body["file"];
if (!file || !(file instanceof File)) {
return c.json({ error: "No se envió ningún archivo" }, 400);
}
const text = await file.text();
const lines = text.trim().split("\n");
if (lines.length < 2) {
return c.json({ error: "El archivo está vacío o solo tiene encabezados" }, 400);
}
let imported = 0;
let skippedNoPeriods = 0;
let skippedDuplicate = 0;
const errors: string[] = [];
for (let i = 1; i < lines.length; i++) {
const line = lines[i].trim();
if (!line) continue;
const parts = line.split("|");
if (parts.length < 5) {
errors.push(`Línea ${i + 1}: formato inválido`);
continue;
}
const description = parts[1].trim();
const defaultDueDay = Number(parts[2].trim());
const rawAmount = parts[3].trim();
const rawPeriods = parts[4].trim();
const periods = parsePeriods(rawPeriods);
if (periods.length === 0) {
skippedNoPeriods++;
continue;
}
try {
const existing = await prisma.periodicExpense.findFirst({
where: { description: { equals: description, mode: "insensitive" } },
});
if (existing) {
skippedDuplicate++;
continue;
}
const defaultAmount = parseArgentineAmount(rawAmount);
await prisma.periodicExpense.create({
data: {
description,
defaultDueDay: isNaN(defaultDueDay) ? 1 : defaultDueDay,
defaultAmount: isNaN(defaultAmount) ? 0 : defaultAmount,
periods,
},
});
imported++;
} catch (err) {
const message = err instanceof Error ? err.message : String(err);
errors.push(`Línea ${i + 1} ("${description}"): ${message}`);
logger.error({ err, description }, "Error importing periodic expense");
}
}
return c.json({
imported,
skipped: skippedNoPeriods + skippedDuplicate,
skippedNoPeriods,
skippedDuplicate,
errors: errors.length > 0 ? errors : undefined,
});
}