85 lines
2.5 KiB
TypeScript
85 lines
2.5 KiB
TypeScript
import { type NextRequest, NextResponse } from "next/server";
|
|
import { prisma } from "@/lib/prisma";
|
|
import { errorResponse, requireRole, ApiError } from "@/lib/api";
|
|
import { renderOperationCard, type OperationCardData } from "@/lib/pdf";
|
|
|
|
/**
|
|
* Admin-only. Stream a one-page traveler card PDF for the given operation.
|
|
* We respond with `Content-Disposition: inline` so hitting the link in a
|
|
* new tab previews the PDF rather than forcing a download — makes the
|
|
* "print for the shop floor" flow a single click.
|
|
*/
|
|
export async function GET(_req: NextRequest, ctx: { params: Promise<{ id: string }> }) {
|
|
try {
|
|
await requireRole("admin");
|
|
const { id } = await ctx.params;
|
|
|
|
const op = await prisma.operation.findUnique({
|
|
where: { id },
|
|
include: {
|
|
machine: { select: { name: true, kind: true } },
|
|
part: {
|
|
select: {
|
|
code: true,
|
|
name: true,
|
|
material: true,
|
|
qty: true,
|
|
assembly: {
|
|
select: {
|
|
code: true,
|
|
name: true,
|
|
qty: true,
|
|
project: { select: { code: true, name: true } },
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
});
|
|
if (!op) throw new ApiError(404, "not_found", "Operation not found");
|
|
|
|
const data: OperationCardData = {
|
|
project: op.part.assembly.project,
|
|
assembly: {
|
|
code: op.part.assembly.code,
|
|
name: op.part.assembly.name,
|
|
qty: op.part.assembly.qty,
|
|
},
|
|
part: {
|
|
code: op.part.code,
|
|
name: op.part.name,
|
|
material: op.part.material,
|
|
qty: op.part.qty,
|
|
},
|
|
operation: {
|
|
id: op.id,
|
|
sequence: op.sequence,
|
|
name: op.name,
|
|
qrToken: op.qrToken,
|
|
machineName: op.machine?.name ?? null,
|
|
machineKind: op.machine?.kind ?? null,
|
|
settings: op.settings,
|
|
materialNotes: op.materialNotes,
|
|
instructions: op.instructions,
|
|
qcRequired: op.qcRequired,
|
|
plannedMinutes: op.plannedMinutes,
|
|
plannedUnits: op.plannedUnits,
|
|
},
|
|
};
|
|
|
|
const pdf = await renderOperationCard(data);
|
|
const safeName = `${op.part.code}-op${op.sequence}.pdf`.replace(/[^A-Za-z0-9._-]/g, "_");
|
|
|
|
return new NextResponse(pdf as unknown as BodyInit, {
|
|
status: 200,
|
|
headers: {
|
|
"Content-Type": "application/pdf",
|
|
"Content-Disposition": `inline; filename="${safeName}"`,
|
|
"Cache-Control": "private, no-store",
|
|
},
|
|
});
|
|
} catch (err) {
|
|
return errorResponse(err);
|
|
}
|
|
}
|