feat: add AssetCategory validation + CRUD (src/lib/asset-categories.ts)
This commit is contained in:
@@ -0,0 +1,107 @@
|
||||
import { z } from "zod";
|
||||
import { prisma } from "./prisma";
|
||||
|
||||
export type AssetCategoryValueGroup = "high_value" | "low_value";
|
||||
|
||||
export interface AssetCategoryRecord {
|
||||
id: string;
|
||||
name: string;
|
||||
value_group: AssetCategoryValueGroup;
|
||||
created_at: string;
|
||||
}
|
||||
|
||||
export interface CreateAssetCategoryInput {
|
||||
name: string;
|
||||
valueGroup: AssetCategoryValueGroup;
|
||||
}
|
||||
|
||||
export type ValidationResult =
|
||||
| { ok: true; value: CreateAssetCategoryInput }
|
||||
| { ok: false; error: string };
|
||||
|
||||
// Mirrors src/lib/vendors.ts's nonEmptyString helper.
|
||||
function nonEmptyString(message: string) {
|
||||
return z.string({ required_error: message, invalid_type_error: message }).refine(
|
||||
(value) => value.trim() !== "",
|
||||
{ message }
|
||||
);
|
||||
}
|
||||
|
||||
const createAssetCategorySchema = z.object({
|
||||
name: nonEmptyString("name is required and must be a non-empty string"),
|
||||
// Fixed 2-value set (see docs/asset-license-management.md's "## Phân
|
||||
// loại tài sản" section) — mirrors src/lib/licenses.ts's licenseType
|
||||
// z.enum() pattern.
|
||||
valueGroup: z.enum(["high_value", "low_value"], {
|
||||
errorMap: () => ({
|
||||
message: "valueGroup is required and must be either 'high_value' or 'low_value'",
|
||||
}),
|
||||
}),
|
||||
});
|
||||
|
||||
/**
|
||||
* Validates raw (untyped) asset-category-creation input. name must be a
|
||||
* non-empty string, valueGroup must be one of the two fixed enum values
|
||||
* (per the "## Phân loại tài sản" spec in docs/asset-license-management.md).
|
||||
*/
|
||||
export function validateCreateAssetCategoryInput(input: {
|
||||
name?: unknown;
|
||||
valueGroup?: unknown;
|
||||
}): ValidationResult {
|
||||
const result = createAssetCategorySchema.safeParse(input);
|
||||
if (!result.success) {
|
||||
const message = result.error.issues[0]?.message ?? "Invalid input";
|
||||
return { ok: false, error: message };
|
||||
}
|
||||
return { ok: true, value: result.data };
|
||||
}
|
||||
|
||||
function toRecord(row: {
|
||||
id: string;
|
||||
name: string;
|
||||
valueGroup: AssetCategoryValueGroup;
|
||||
createdAt: Date;
|
||||
}): AssetCategoryRecord {
|
||||
return {
|
||||
id: row.id,
|
||||
name: row.name,
|
||||
value_group: row.valueGroup,
|
||||
created_at: row.createdAt.toISOString(),
|
||||
};
|
||||
}
|
||||
|
||||
export type CreateAssetCategoryResult =
|
||||
| { ok: true; value: AssetCategoryRecord }
|
||||
| { ok: false; error: string };
|
||||
|
||||
/**
|
||||
* Creates an AssetCategory. No unique constraint on any field, no FKs
|
||||
* either (see docs/asset-license-management.md's "## Phân loại tài sản"
|
||||
* spec — standalone entity, same starting shape as Vendor before it grew
|
||||
* relations) — so there's no known Prisma error code to catch/translate
|
||||
* here today. Still returns the `{ok,value}|{ok,error}` shape (rather than
|
||||
* a bare Promise<AssetCategoryRecord> like createVendor()) to match the
|
||||
* convention used across the newer entities (AssetItem/License) — see the
|
||||
* code-reviewer finding on createLicense() in CLAUDE.md's "Trạng thái hiện
|
||||
* tại": adding this shape up front avoids a silent 500 if a unique
|
||||
* constraint is added later (e.g. on `name`) without remembering to also
|
||||
* add P2002 handling here.
|
||||
*/
|
||||
export async function createAssetCategory(
|
||||
input: CreateAssetCategoryInput
|
||||
): Promise<CreateAssetCategoryResult> {
|
||||
const created = await prisma.assetCategory.create({
|
||||
data: {
|
||||
name: input.name,
|
||||
valueGroup: input.valueGroup,
|
||||
},
|
||||
});
|
||||
return { ok: true, value: toRecord(created) };
|
||||
}
|
||||
|
||||
export async function listAssetCategories(): Promise<AssetCategoryRecord[]> {
|
||||
const rows = await prisma.assetCategory.findMany({
|
||||
orderBy: [{ createdAt: "desc" }, { id: "desc" }],
|
||||
});
|
||||
return rows.map(toRecord);
|
||||
}
|
||||
Reference in New Issue
Block a user