Zod schema + validate function, Prisma-backed CRUD following the
{ok,value}|{ok,error} pattern from the start. createAssetCategoryAttributeDef()
catches P2002 for the composite (categoryId, key) unique constraint (mirrors
createAssetItem()'s serialNumber handling) and P2003 for the categoryId FK
(mirrors createPurchaseOrder()).
Nullable FK from License to PurchaseOrder for supply-side traceability
(which PO a license came in through), mirroring AssetItem.purchaseOrderId's
just-merged pattern exactly. Separate commit from the earlier
License.vendorId retrofit per the project's 1-commit-per-business-change
convention.
License.vendor (free-text string) is now vendorId, a required FK to
Vendor, per docs/asset-license-management.md's "## Vendor (chốt
2026-08-02)" spec. Mirrors the Request.employeeId string->FK precedent:
Zod validates UUID format only, Prisma's own FK constraint (P2003) is
caught at insert time and translated into a clean 400 instead of a raw
500. No License data existed yet, so this is a pure column-type
migration, no backfill needed.
Also converts createLicense() to the {ok,value}|{ok,error} result
pattern used elsewhere (createRequest, createAssetItem) instead of
returning a bare LicenseRecord promise - closes the gap flagged in
CLAUDE.md now that License has its first FK-violation error case to
handle.
- createRequestSchema now requires employee_id to be a UUID-format
string (was a free-text non-empty check), matching the new FK to
Employee in schema.prisma.
- createRequest() now returns a CreateRequestResult discriminated
union and catches Prisma P2003 (FK violation), returning a friendly
ok:false error instead of a raw 500 when employee_id does not
reference an existing Employee. POST /api/requests branches on
created.ok and returns 400 in that case.
- tests/requests.test.ts: every test that used to POST a fake string
employee_id (emp-1, emp-2) now creates a real Employee first via
createTestEmployee() and uses its id, since employee_id is now a
real FK. Added coverage for a syntactically invalid UUID and for a
well-formed but non-existent UUID (expects the friendly 400 from
the P2003 catch, not a raw 500) - the exact case this retrofit was
meant to protect against.
Mirrors src/lib/requests.ts's shape: Zod schema, ValidationResult
discriminated union, CRUD via Prisma, toRecord() camelCase->snake_case
mapping. managerId is only format-checked as a UUID here; a
nonexistent managerId is caught via Prisma's P2003 FK-violation error
in createEmployee() and translated into a friendly {ok:false,error}
instead of leaking a raw Prisma error.