136 lines
3.9 KiB
TypeScript
136 lines
3.9 KiB
TypeScript
import { NextResponse } from "next/server";
|
|
|
|
import { ensureSystemUser } from "@/features/purchases/lib/system-user";
|
|
import { generateReceiptNo } from "@/features/receipts/lib/generate-receipt-no";
|
|
import {
|
|
serializeReceiptDetail,
|
|
serializeReceiptListItem
|
|
} from "@/features/receipts/lib/serialize-receipt";
|
|
import { receiptInputSchema } from "@/features/receipts/schemas/receipt.schema";
|
|
import { createAuditTrailSafe } from "@/lib/audit-trail";
|
|
import { prisma } from "@/lib/prisma";
|
|
import { requireApiAccess } from "@/lib/authorization";
|
|
|
|
const receiptDetailInclude = {
|
|
purchase: true,
|
|
lines: {
|
|
include: {
|
|
grade: true,
|
|
unit: true,
|
|
warehouse: true,
|
|
warehouseLocation: true
|
|
}
|
|
},
|
|
lots: {
|
|
include: {
|
|
grade: true,
|
|
unit: true,
|
|
warehouse: true,
|
|
warehouseLocation: true
|
|
}
|
|
}
|
|
} as const;
|
|
|
|
export async function GET(request: Request) {
|
|
const auth = requireApiAccess(request);
|
|
if (!auth.ok) return auth.response;
|
|
const data = await prisma.receipt.findMany({
|
|
include: {
|
|
purchase: true,
|
|
lines: true,
|
|
lots: true
|
|
},
|
|
orderBy: [{ createdAt: "desc" }]
|
|
});
|
|
|
|
return NextResponse.json({
|
|
data: data.map(serializeReceiptListItem)
|
|
});
|
|
}
|
|
|
|
export async function POST(request: Request) {
|
|
const auth = requireApiAccess(request);
|
|
if (!auth.ok) return auth.response;
|
|
const parsed = receiptInputSchema.safeParse(await request.json());
|
|
if (!parsed.success) {
|
|
return NextResponse.json(
|
|
{ message: "Validasi gagal", errors: parsed.error.flatten().fieldErrors },
|
|
{ status: 400 }
|
|
);
|
|
}
|
|
|
|
const systemUser = await ensureSystemUser();
|
|
const receiptDate = new Date(`${parsed.data.receipt_date}T00:00:00.000Z`);
|
|
const receiptNo = await generateReceiptNo(receiptDate);
|
|
const purchaseId = BigInt(parsed.data.purchase_id);
|
|
|
|
const purchase = await prisma.purchase.findUnique({
|
|
where: { id: purchaseId },
|
|
include: {
|
|
receipts: { select: { id: true, receiptNo: true } },
|
|
lots: { select: { id: true } }
|
|
}
|
|
});
|
|
|
|
if (!purchase) {
|
|
return NextResponse.json({ message: "Purchase not found" }, { status: 404 });
|
|
}
|
|
|
|
if (purchase.status === "CANCELLED") {
|
|
return NextResponse.json({ message: "Purchase yang dibatalkan tidak bisa dibuatkan receipt" }, { status: 422 });
|
|
}
|
|
|
|
if (purchase.receipts.length > 0 || purchase.lots.length > 0) {
|
|
return NextResponse.json(
|
|
{ message: `Purchase sudah memiliki receipt/lot ${purchase.receipts[0]?.receiptNo ?? ""}`.trim() },
|
|
{ status: 409 }
|
|
);
|
|
}
|
|
|
|
const receipt = await prisma.receipt.create({
|
|
data: {
|
|
receiptNo,
|
|
purchaseId,
|
|
receiptDate,
|
|
status: "DRAFT",
|
|
notes: parsed.data.notes || null,
|
|
receivedById: systemUser.id,
|
|
lines: {
|
|
create: parsed.data.lines.map((line) => ({
|
|
purchaseLineId: BigInt(line.purchase_line_id),
|
|
gradeId: line.grade_id ? BigInt(line.grade_id) : null,
|
|
qtyReceived: line.qty_received,
|
|
qtyAccepted: line.qty_accepted,
|
|
qtyRejected: line.qty_rejected,
|
|
unitId: BigInt(line.unit_id),
|
|
unitCost: line.unit_cost,
|
|
warehouseId: BigInt(line.warehouse_id),
|
|
warehouseLocationId: line.warehouse_location_id
|
|
? BigInt(line.warehouse_location_id)
|
|
: null,
|
|
notes: line.notes || null
|
|
}))
|
|
}
|
|
},
|
|
include: receiptDetailInclude
|
|
});
|
|
|
|
await createAuditTrailSafe({
|
|
userId: auth.user.id,
|
|
action: "RECEIPT_CREATED",
|
|
entityType: "RECEIPT",
|
|
entityId: receipt.id,
|
|
method: request.method,
|
|
pathname: new URL(request.url).pathname,
|
|
statusCode: 201,
|
|
summary: `Receipt ${receipt.receiptNo} dibuat`,
|
|
metadata: {
|
|
receipt_no: receipt.receiptNo,
|
|
purchase_no: receipt.purchase.purchaseNo,
|
|
line_count: receipt.lines.length
|
|
}
|
|
});
|
|
|
|
return NextResponse.json({ data: serializeReceiptDetail(receipt) }, { status: 201 });
|
|
}
|