124 lines
3.7 KiB
TypeScript
124 lines
3.7 KiB
TypeScript
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);
|
|
}
|