feat: add Vendor validation + CRUD (src/lib/vendors.ts)
This commit is contained in:
@@ -0,0 +1,123 @@
|
||||
import { z } from "zod";
|
||||
import { prisma } from "./prisma";
|
||||
|
||||
export interface VendorRecord {
|
||||
id: string;
|
||||
name: string;
|
||||
contact_email: string;
|
||||
contact_phone: string;
|
||||
address: string;
|
||||
tax_code: string | null;
|
||||
notes: string | null;
|
||||
created_at: string;
|
||||
}
|
||||
|
||||
export interface CreateVendorInput {
|
||||
name: string;
|
||||
contactEmail: string;
|
||||
contactPhone: string;
|
||||
address: string;
|
||||
taxCode?: string | null;
|
||||
notes?: string | null;
|
||||
}
|
||||
|
||||
export type ValidationResult =
|
||||
| { ok: true; value: CreateVendorInput }
|
||||
| { ok: false; error: string };
|
||||
|
||||
// Mirrors src/lib/licenses.ts's nonEmptyString helper.
|
||||
function nonEmptyString(message: string) {
|
||||
return z.string({ required_error: message, invalid_type_error: message }).refine(
|
||||
(value) => value.trim() !== "",
|
||||
{ message }
|
||||
);
|
||||
}
|
||||
|
||||
const createVendorSchema = z.object({
|
||||
name: nonEmptyString("name is required and must be a non-empty string"),
|
||||
contactEmail: z
|
||||
.string({
|
||||
required_error: "contactEmail is required and must be a valid email",
|
||||
invalid_type_error: "contactEmail is required and must be a valid email",
|
||||
})
|
||||
.email("contactEmail is required and must be a valid email"),
|
||||
contactPhone: nonEmptyString("contactPhone is required and must be a non-empty string"),
|
||||
address: nonEmptyString("address is required and must be a non-empty string"),
|
||||
// Nullable, per docs/asset-license-management.md's "## Vendor" spec — not
|
||||
// every vendor record has a known VN tax code at creation time.
|
||||
taxCode: nonEmptyString("taxCode must be a non-empty string").optional().nullable(),
|
||||
notes: nonEmptyString("notes must be a non-empty string").optional().nullable(),
|
||||
});
|
||||
|
||||
/**
|
||||
* Validates raw (untyped) vendor-creation input. name/contactPhone/address
|
||||
* must be non-empty strings, contactEmail must be a valid email string,
|
||||
* taxCode/notes are optional/nullable (per the "## Vendor" spec in
|
||||
* docs/asset-license-management.md) but must be non-empty strings if
|
||||
* present.
|
||||
*/
|
||||
export function validateCreateVendorInput(input: {
|
||||
name?: unknown;
|
||||
contactEmail?: unknown;
|
||||
contactPhone?: unknown;
|
||||
address?: unknown;
|
||||
taxCode?: unknown;
|
||||
notes?: unknown;
|
||||
}): ValidationResult {
|
||||
const result = createVendorSchema.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;
|
||||
contactEmail: string;
|
||||
contactPhone: string;
|
||||
address: string;
|
||||
taxCode: string | null;
|
||||
notes: string | null;
|
||||
createdAt: Date;
|
||||
}): VendorRecord {
|
||||
return {
|
||||
id: row.id,
|
||||
name: row.name,
|
||||
contact_email: row.contactEmail,
|
||||
contact_phone: row.contactPhone,
|
||||
address: row.address,
|
||||
tax_code: row.taxCode,
|
||||
notes: row.notes,
|
||||
created_at: row.createdAt.toISOString(),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a Vendor. No unique constraint exists on any field, no FKs
|
||||
* either (see docs/asset-license-management.md's "## Vendor" spec — this
|
||||
* is a standalone entity, same starting shape as Employee/License before
|
||||
* they grew relations) — so unlike createAssetItem()/createEmployee()
|
||||
* there's no known Prisma error code to catch/translate here.
|
||||
*/
|
||||
export async function createVendor(input: CreateVendorInput): Promise<VendorRecord> {
|
||||
const created = await prisma.vendor.create({
|
||||
data: {
|
||||
name: input.name,
|
||||
contactEmail: input.contactEmail,
|
||||
contactPhone: input.contactPhone,
|
||||
address: input.address,
|
||||
taxCode: input.taxCode ?? null,
|
||||
notes: input.notes ?? null,
|
||||
},
|
||||
});
|
||||
return toRecord(created);
|
||||
}
|
||||
|
||||
export async function listVendors(): Promise<VendorRecord[]> {
|
||||
const rows = await prisma.vendor.findMany({
|
||||
orderBy: [{ createdAt: "desc" }, { id: "desc" }],
|
||||
});
|
||||
return rows.map(toRecord);
|
||||
}
|
||||
Reference in New Issue
Block a user