High priority features: - APU CRUD with materials, labor, and equipment breakdown - Labor catalog with FSR (Factor de Salario Real) calculation - Equipment catalog with hourly cost calculation - Link APU to budget line items (partidas) - Explosion de insumos (consolidated materials list) Additional features: - Duplicate APU functionality - Excel export for explosion de insumos - Search and filters in APU list - Price validation alerts for outdated prices - PDF report export for APU New components: - APUForm, APUList, APUDetail - ManoObraForm, EquipoForm - ConfiguracionAPUForm - VincularAPUDialog - PartidasManager - ExplosionInsumos - APUPDF New UI components: - Alert component - Tooltip component Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
105 lines
3.0 KiB
TypeScript
105 lines
3.0 KiB
TypeScript
import { NextResponse } from "next/server";
|
|
import { auth } from "@/lib/auth";
|
|
import { prisma } from "@/lib/prisma";
|
|
|
|
export async function POST(
|
|
request: Request,
|
|
{ params }: { params: Promise<{ id: string }> }
|
|
) {
|
|
try {
|
|
const session = await auth();
|
|
if (!session?.user?.empresaId) {
|
|
return NextResponse.json({ error: "No autorizado" }, { status: 401 });
|
|
}
|
|
|
|
const { id } = await params;
|
|
|
|
// Get the original APU with all insumos
|
|
const original = await prisma.analisisPrecioUnitario.findFirst({
|
|
where: {
|
|
id,
|
|
empresaId: session.user.empresaId,
|
|
},
|
|
include: {
|
|
insumos: true,
|
|
},
|
|
});
|
|
|
|
if (!original) {
|
|
return NextResponse.json({ error: "APU no encontrado" }, { status: 404 });
|
|
}
|
|
|
|
// Generate a unique code for the copy
|
|
let newCodigo = `${original.codigo}-COPIA`;
|
|
let counter = 1;
|
|
|
|
while (true) {
|
|
const existing = await prisma.analisisPrecioUnitario.findFirst({
|
|
where: {
|
|
codigo: newCodigo,
|
|
empresaId: session.user.empresaId,
|
|
},
|
|
});
|
|
|
|
if (!existing) break;
|
|
|
|
counter++;
|
|
newCodigo = `${original.codigo}-COPIA${counter}`;
|
|
}
|
|
|
|
// Create the duplicate APU with all its insumos
|
|
const duplicate = await prisma.analisisPrecioUnitario.create({
|
|
data: {
|
|
codigo: newCodigo,
|
|
descripcion: `${original.descripcion} (Copia)`,
|
|
unidad: original.unidad,
|
|
rendimientoDiario: original.rendimientoDiario,
|
|
costoMateriales: original.costoMateriales,
|
|
costoManoObra: original.costoManoObra,
|
|
costoEquipo: original.costoEquipo,
|
|
costoHerramienta: original.costoHerramienta,
|
|
costoDirecto: original.costoDirecto,
|
|
porcentajeIndirectos: original.porcentajeIndirectos,
|
|
costoIndirectos: original.costoIndirectos,
|
|
porcentajeUtilidad: original.porcentajeUtilidad,
|
|
costoUtilidad: original.costoUtilidad,
|
|
precioUnitario: original.precioUnitario,
|
|
empresaId: session.user.empresaId,
|
|
insumos: {
|
|
create: original.insumos.map((insumo) => ({
|
|
tipo: insumo.tipo,
|
|
descripcion: insumo.descripcion,
|
|
unidad: insumo.unidad,
|
|
cantidad: insumo.cantidad,
|
|
desperdicio: insumo.desperdicio,
|
|
cantidadConDesperdicio: insumo.cantidadConDesperdicio,
|
|
rendimiento: insumo.rendimiento,
|
|
precioUnitario: insumo.precioUnitario,
|
|
importe: insumo.importe,
|
|
materialId: insumo.materialId,
|
|
categoriaManoObraId: insumo.categoriaManoObraId,
|
|
equipoId: insumo.equipoId,
|
|
})),
|
|
},
|
|
},
|
|
include: {
|
|
insumos: {
|
|
include: {
|
|
material: true,
|
|
categoriaManoObra: true,
|
|
equipo: true,
|
|
},
|
|
},
|
|
},
|
|
});
|
|
|
|
return NextResponse.json(duplicate);
|
|
} catch (error) {
|
|
console.error("Error duplicating APU:", error);
|
|
return NextResponse.json(
|
|
{ error: "Error al duplicar el APU" },
|
|
{ status: 500 }
|
|
);
|
|
}
|
|
}
|