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 { 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 { const rows = await prisma.vendor.findMany({ orderBy: [{ createdAt: "desc" }, { id: "desc" }], }); return rows.map(toRecord); }