feat: add Request.purchaseOrderId (nullable FK)
This commit is contained in:
@@ -24,8 +24,8 @@ export async function POST(request: Request): Promise<NextResponse> {
|
||||
return NextResponse.json({ error: "Request body must be a JSON object" }, { status: 400 });
|
||||
}
|
||||
|
||||
const { employee_id, type, reason } = body as Record<string, unknown>;
|
||||
const validation = validateCreateRequestInput({ employee_id, type, reason });
|
||||
const { employee_id, type, reason, purchase_order_id } = body as Record<string, unknown>;
|
||||
const validation = validateCreateRequestInput({ employee_id, type, reason, purchase_order_id });
|
||||
|
||||
if (!validation.ok) {
|
||||
return NextResponse.json({ error: validation.error }, { status: 400 });
|
||||
|
||||
+29
-5
@@ -16,6 +16,7 @@ export interface RequestRecord {
|
||||
type: RequestType;
|
||||
reason: string;
|
||||
status: RequestStatus;
|
||||
purchase_order_id: string | null;
|
||||
created_at: string;
|
||||
}
|
||||
|
||||
@@ -23,6 +24,7 @@ export interface CreateRequestInput {
|
||||
employee_id: string;
|
||||
type: RequestType;
|
||||
reason: string;
|
||||
purchase_order_id?: string | null;
|
||||
}
|
||||
|
||||
export type ValidationResult =
|
||||
@@ -55,6 +57,18 @@ const createRequestSchema = z.object({
|
||||
message: "type is required and must be exactly 'asset' or 'license'",
|
||||
}),
|
||||
}),
|
||||
// Nullable/optional FK to PurchaseOrder — see schema.prisma's comment on
|
||||
// Request.purchaseOrderId for the demand-vs-supply-side distinction from
|
||||
// AssetItem/License's purchaseOrderId. Only a UUID *format* check here,
|
||||
// whether it references a real PurchaseOrder is left to Prisma's own FK
|
||||
// constraint at insert time (see createRequest()'s P2003 handling below).
|
||||
// Mirrors src/lib/assets.ts's purchaseOrderId optional().nullable()
|
||||
// pattern exactly.
|
||||
purchase_order_id: z
|
||||
.string({ invalid_type_error: "purchase_order_id must be a valid UUID string" })
|
||||
.uuid("purchase_order_id must be a valid UUID string")
|
||||
.optional()
|
||||
.nullable(),
|
||||
});
|
||||
|
||||
/**
|
||||
@@ -66,6 +80,7 @@ export function validateCreateRequestInput(input: {
|
||||
employee_id?: unknown;
|
||||
type?: unknown;
|
||||
reason?: unknown;
|
||||
purchase_order_id?: unknown;
|
||||
}): ValidationResult {
|
||||
const result = createRequestSchema.safeParse(input);
|
||||
if (!result.success) {
|
||||
@@ -81,6 +96,7 @@ function toRecord(row: {
|
||||
type: RequestType;
|
||||
reason: string;
|
||||
status: string;
|
||||
purchaseOrderId: string | null;
|
||||
createdAt: Date;
|
||||
}): RequestRecord {
|
||||
return {
|
||||
@@ -89,6 +105,7 @@ function toRecord(row: {
|
||||
type: row.type,
|
||||
reason: row.reason,
|
||||
status: row.status as RequestStatus,
|
||||
purchase_order_id: row.purchaseOrderId,
|
||||
created_at: row.createdAt.toISOString(),
|
||||
};
|
||||
}
|
||||
@@ -98,11 +115,13 @@ export type CreateRequestResult =
|
||||
| { ok: false; error: string };
|
||||
|
||||
/**
|
||||
* Creates a Request. If employee_id doesn't reference an existing Employee,
|
||||
* Prisma raises a known error (code P2003, FK violation) — caught here and
|
||||
* translated into the same `{ok:false,error}` shape as input validation
|
||||
* failures, rather than letting a raw Prisma error leak to the API layer.
|
||||
* Mirrors src/lib/employees.ts's createEmployee() pattern for managerId.
|
||||
* Creates a Request. Two separate FKs can fail here (employee_id ->
|
||||
* Employee, purchase_order_id -> PurchaseOrder) — Prisma raises the same
|
||||
* error code (P2003) for either, so the FK constraint name in
|
||||
* `error.meta.field_name` is used to tell them apart and return a targeted
|
||||
* message, rather than a raw Prisma error leaking to the API layer. Mirrors
|
||||
* src/lib/licenses.ts's createLicense() pattern for disambiguating between
|
||||
* two FKs on the same model.
|
||||
*/
|
||||
export async function createRequest(input: CreateRequestInput): Promise<CreateRequestResult> {
|
||||
try {
|
||||
@@ -111,11 +130,16 @@ export async function createRequest(input: CreateRequestInput): Promise<CreateRe
|
||||
employeeId: input.employee_id,
|
||||
type: input.type,
|
||||
reason: input.reason,
|
||||
purchaseOrderId: input.purchase_order_id ?? null,
|
||||
},
|
||||
});
|
||||
return { ok: true, value: toRecord(created) };
|
||||
} catch (error) {
|
||||
if (error instanceof Prisma.PrismaClientKnownRequestError && error.code === "P2003") {
|
||||
const fieldName = typeof error.meta?.field_name === "string" ? error.meta.field_name : "";
|
||||
if (fieldName.includes("purchase_order")) {
|
||||
return { ok: false, error: "purchase_order_id does not reference an existing purchase order" };
|
||||
}
|
||||
return { ok: false, error: "employee_id does not reference an existing employee" };
|
||||
}
|
||||
throw error;
|
||||
|
||||
Reference in New Issue
Block a user