Files
andris-carservice/src/app/api/tasks/route.ts
T
2026-07-20 21:17:12 +01:00

195 lines
5.3 KiB
TypeScript

import { prisma } from "@/lib/prisma";
import { errorResponse, getJsonBody, jsonResponse } from "@/lib/api-helpers";
async function recalcOrderTotal(orderId: number) {
const tasks = await prisma.task.findMany({
where: { orderId },
include: { parts: true },
});
const total = tasks.reduce((sum, task) => {
const partsCost = task.parts.reduce(
(p, tp) => p + (tp.price || 0) * tp.qty,
0,
);
const laborCost = task.laborHours * task.hourlyRate;
const price = task.price > 0 ? task.price : partsCost + laborCost;
return sum + price;
}, 0);
await prisma.order.update({ where: { id: orderId }, data: { total } });
}
export async function POST(req: Request) {
const body = await getJsonBody(req);
if (!body) return errorResponse("Invalid JSON");
const {
orderId,
title,
notes,
laborHours,
hourlyRate,
price,
partIds = [],
templateParts = [],
} = body;
if (!orderId || !title)
return errorResponse("orderId and title are required");
const order = await prisma.order.findUnique({
where: { id: Number(orderId) },
include: { car: true },
});
if (!order) return errorResponse("Order not found", 404);
const last = await prisma.task.findFirst({
where: { orderId: Number(orderId) },
orderBy: { orderIndex: "desc" },
});
// If the title matches a quick task template, pull its defaults.
const quickTask = await prisma.quickTask.findFirst({
where: { title },
include: { parts: true },
});
if (quickTask) {
await prisma.quickTask.update({
where: { id: quickTask.id },
data: { useCount: { increment: 1 } },
});
}
const hasExplicitLabor =
laborHours !== undefined && String(laborHours).trim() !== "";
const hasExplicitRate =
hourlyRate !== undefined && String(hourlyRate).trim() !== "";
const hasExplicitPrice = price !== undefined && String(price).trim() !== "";
const taskLaborHours =
quickTask && !hasExplicitLabor
? quickTask.laborHours
: Number(laborHours) || 0;
const taskHourlyRate =
quickTask && !hasExplicitRate
? quickTask.hourlyRate
: Number(hourlyRate) || 50;
const taskPrice =
quickTask && !hasExplicitPrice
? quickTask.price
: hasExplicitPrice
? Number(price) || 0
: null;
const effectiveTemplateParts =
templateParts && templateParts.length > 0
? templateParts
: quickTask?.parts || [];
const task = await prisma.task.create({
data: {
orderId: Number(orderId),
title,
notes: notes || "",
laborHours: Number(taskLaborHours) || 0,
hourlyRate: Number(taskHourlyRate) || 50,
price: taskPrice ?? undefined,
orderIndex: (last?.orderIndex ?? -1) + 1,
},
include: { parts: { include: { part: true } } },
});
// Attach selected existing parts — only link to the same car or global parts.
for (const item of partIds) {
const { partId, qty = 1, price: partPrice } = item;
const part = await prisma.part.findFirst({
where: {
id: Number(partId),
OR: [{ carId: order.carId }, { isGlobal: true }],
},
});
if (part) {
await prisma.taskPart.create({
data: {
taskId: task.id,
partId: part.id,
qty: Number(qty) || 1,
price: partPrice != null ? Number(partPrice) : part.purchasePrice,
},
});
}
}
// Convert template parts into private car parts (or link globals) and attach them.
for (const tp of effectiveTemplateParts) {
const isGlobal = tp.isGlobal || false;
let partId: number | undefined;
if (isGlobal) {
// Try to find an existing global part with the same name.
const existing = await prisma.part.findFirst({
where: { name: tp.name, isGlobal: true },
});
if (existing) {
partId = existing.id;
} else {
const created = await prisma.part.create({
data: {
name: tp.name,
purchasePrice: Number(tp.price) || 0,
notes: "",
isGlobal: true,
},
});
partId = created.id;
}
} else {
const existing = await prisma.part.findFirst({
where: { name: tp.name, carId: order.carId, isGlobal: false },
});
if (existing) {
partId = existing.id;
} else {
const created = await prisma.part.create({
data: {
name: tp.name,
purchasePrice: Number(tp.price) || 0,
notes: "",
carId: order.carId,
isGlobal: false,
},
});
partId = created.id;
}
}
if (partId) {
await prisma.taskPart.create({
data: {
taskId: task.id,
partId,
qty: Number(tp.qty) || 1,
price: Number(tp.price) || 0,
},
});
}
}
await recalcOrderTotal(Number(orderId));
if (quickTask) {
await prisma.quickTask.update({
where: { id: quickTask.id },
data: { useCount: { increment: 1 } },
});
} else {
await prisma.quickTask.upsert({
where: { title },
update: { useCount: { increment: 1 } },
create: { title, useCount: 1 },
});
}
const full = await prisma.task.findUnique({
where: { id: task.id },
include: { parts: { include: { part: true } } },
});
return jsonResponse(full, 201);
}