95 lines
2.5 KiB
TypeScript
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,
|
|
});
|
|
}
|