108 lines
3.4 KiB
TypeScript
108 lines
3.4 KiB
TypeScript
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);
|
|
}
|