phase 2 and 3

This commit is contained in:
jason
2026-04-21 08:56:51 -05:00
parent b98837a72c
commit d79aaf6ef8
42 changed files with 4962 additions and 19 deletions
+237
View File
@@ -0,0 +1,237 @@
import { z } from "zod";
// ---- shared --------------------------------------------------------------
const NonEmpty = z.string().trim().min(1, "Required").max(200);
const Code = z.string().trim().min(1).max(64).regex(/^[A-Za-z0-9._\-/]+$/, "Use letters, digits, . _ - /");
const OptionalText = z
.string()
.trim()
.max(5000)
.transform((v) => (v.length === 0 ? null : v))
.nullable()
.optional();
const JsonString = z
.string()
.max(10_000)
.refine(
(s) => {
if (s.length === 0) return true;
try {
JSON.parse(s);
return true;
} catch {
return false;
}
},
{ message: "Must be valid JSON" },
)
.transform((s) => (s.length === 0 ? null : s))
.nullable()
.optional();
const Pin = z.string().regex(/^\d{4}$/, "PIN must be exactly 4 digits");
// ---- users ---------------------------------------------------------------
export const CreateAdminSchema = z.object({
role: z.literal("admin"),
name: NonEmpty,
email: z.string().email().max(200),
password: z.string().min(8).max(200),
});
export const CreateOperatorSchema = z.object({
role: z.literal("operator"),
name: NonEmpty,
pin: Pin,
});
export const CreateUserSchema = z.discriminatedUnion("role", [
CreateAdminSchema,
CreateOperatorSchema,
]);
export const UpdateUserSchema = z
.object({
name: NonEmpty.optional(),
active: z.boolean().optional(),
email: z.string().email().max(200).optional(),
password: z.string().min(8).max(200).optional(),
pin: Pin.optional(),
})
.strict();
// ---- machines ------------------------------------------------------------
export const MachineKinds = [
"NCT_PUNCH",
"PRESS_BRAKE",
"RIVET",
"WELD",
"LASER",
"SHEAR",
"ASSEMBLY",
"OTHER",
] as const;
export const CreateMachineSchema = z.object({
name: NonEmpty,
kind: z.enum(MachineKinds),
location: OptionalText,
notes: OptionalText,
});
export const UpdateMachineSchema = z
.object({
name: NonEmpty.optional(),
kind: z.enum(MachineKinds).optional(),
location: OptionalText,
notes: OptionalText,
active: z.boolean().optional(),
})
.strict();
// ---- operation templates -------------------------------------------------
export const CreateTemplateSchema = z.object({
name: NonEmpty,
machineId: z.string().min(1).nullable().optional(),
defaultSettings: JsonString,
defaultInstructions: OptionalText,
qcRequired: z.boolean().default(false),
});
export const UpdateTemplateSchema = z
.object({
name: NonEmpty.optional(),
machineId: z.string().min(1).nullable().optional(),
defaultSettings: JsonString,
defaultInstructions: OptionalText,
qcRequired: z.boolean().optional(),
active: z.boolean().optional(),
})
.strict();
// ---- projects ------------------------------------------------------------
export const ProjectStatuses = ["planning", "in_progress", "completed", "cancelled"] as const;
export const CreateProjectSchema = z.object({
code: Code,
name: NonEmpty,
customerCode: z
.string()
.trim()
.max(64)
.transform((v) => (v.length === 0 ? null : v))
.nullable()
.optional(),
dueDate: z.coerce.date().nullable().optional(),
notes: OptionalText,
});
export const UpdateProjectSchema = z
.object({
code: Code.optional(),
name: NonEmpty.optional(),
customerCode: z
.string()
.trim()
.max(64)
.transform((v) => (v.length === 0 ? null : v))
.nullable()
.optional(),
dueDate: z.coerce.date().nullable().optional(),
status: z.enum(ProjectStatuses).optional(),
notes: OptionalText,
})
.strict();
// ---- assemblies / parts --------------------------------------------------
export const CreateAssemblySchema = z.object({
code: Code,
name: NonEmpty,
qty: z.coerce.number().int().positive().max(100000).default(1),
notes: OptionalText,
});
export const UpdateAssemblySchema = z
.object({
code: Code.optional(),
name: NonEmpty.optional(),
qty: z.coerce.number().int().positive().max(100000).optional(),
notes: OptionalText,
})
.strict();
export const CreatePartSchema = z.object({
code: Code,
name: NonEmpty,
material: z
.string()
.trim()
.max(120)
.transform((v) => (v.length === 0 ? null : v))
.nullable()
.optional(),
qty: z.coerce.number().int().positive().max(100000).default(1),
notes: OptionalText,
});
export const UpdatePartSchema = z
.object({
code: Code.optional(),
name: NonEmpty.optional(),
material: z
.string()
.trim()
.max(120)
.transform((v) => (v.length === 0 ? null : v))
.nullable()
.optional(),
qty: z.coerce.number().int().positive().max(100000).optional(),
notes: OptionalText,
stepFileId: z.string().min(1).nullable().optional(),
drawingFileId: z.string().min(1).nullable().optional(),
cutFileId: z.string().min(1).nullable().optional(),
})
.strict();
// ---- operations ---------------------------------------------------------
export const OperationStatuses = ["pending", "in_progress", "completed"] as const;
export const CreateOperationSchema = z.object({
templateId: z.string().min(1).nullable().optional(),
name: NonEmpty,
machineId: z.string().min(1).nullable().optional(),
settings: JsonString,
materialNotes: OptionalText,
instructions: OptionalText,
qcRequired: z.boolean().default(false),
plannedMinutes: z.coerce.number().int().positive().max(100000).nullable().optional(),
plannedUnits: z.coerce.number().int().positive().max(100000).nullable().optional(),
sequence: z.coerce.number().int().positive().max(10000).optional(),
});
export const UpdateOperationSchema = z
.object({
templateId: z.string().min(1).nullable().optional(),
name: NonEmpty.optional(),
machineId: z.string().min(1).nullable().optional(),
settings: JsonString,
materialNotes: OptionalText,
instructions: OptionalText,
qcRequired: z.boolean().optional(),
plannedMinutes: z.coerce.number().int().positive().max(100000).nullable().optional(),
plannedUnits: z.coerce.number().int().positive().max(100000).nullable().optional(),
sequence: z.coerce.number().int().positive().max(10000).optional(),
status: z.enum(OperationStatuses).optional(),
})
.strict();
export const ReorderOperationsSchema = z.object({
order: z.array(z.string().min(1)).min(1),
});