import { z } from "zod"; import { AllocationStatus } from "../types/enums.js"; export const CreateAllocationBaseSchema = z.object({ resourceId: z.string().optional(), projectId: z.string(), startDate: z.coerce.date(), endDate: z.coerce.date(), hoursPerDay: z.number().positive().max(24), percentage: z.number().min(0).max(100), role: z.string().max(200).optional(), roleId: z.string().optional(), headcount: z.number().int().min(1).default(1), budgetCents: z.number().int().min(0).optional(), status: z.nativeEnum(AllocationStatus).default(AllocationStatus.PROPOSED), metadata: z.record(z.string(), z.unknown()).default({}), }); export const CreateDemandRequirementBaseSchema = z.object({ projectId: z.string(), startDate: z.coerce.date(), endDate: z.coerce.date(), hoursPerDay: z.number().positive().max(24), percentage: z.number().min(0).max(100), role: z.string().max(200).optional(), roleId: z.string().optional(), headcount: z.number().int().min(1).default(1), budgetCents: z.number().int().min(0).optional(), status: z.nativeEnum(AllocationStatus).default(AllocationStatus.PROPOSED), metadata: z.record(z.string(), z.unknown()).default({}), }); export const CreateAssignmentBaseSchema = z.object({ demandRequirementId: z.string().optional(), resourceId: z.string(), projectId: z.string(), startDate: z.coerce.date(), endDate: z.coerce.date(), hoursPerDay: z.number().positive().max(24), percentage: z.number().min(0).max(100), role: z.string().max(200).optional(), roleId: z.string().optional(), dailyCostCents: z.number().int().min(0).optional(), status: z.nativeEnum(AllocationStatus).default(AllocationStatus.PROPOSED), metadata: z.record(z.string(), z.unknown()).default({}), /** When true the caller acknowledges the resource will be overbooked. */ allowOverbooking: z.boolean().optional(), }); /** * @deprecated Use `CreateDemandRequirementSchema` (for open demand) or `CreateAssignmentSchema` * (for resource assignments) instead. This legacy facade remains for router backwards-compat * and will be removed once all consumers are migrated. */ export const CreateAllocationSchema = CreateAllocationBaseSchema.refine( (data) => data.endDate >= data.startDate, { message: "End date must be after start date", path: ["endDate"] }, ); /** * @deprecated Use `UpdateDemandRequirementSchema` or `UpdateAssignmentSchema` instead. * See `CreateAllocationSchema` note above. */ export const UpdateAllocationSchema = CreateAllocationBaseSchema.partial(); export const CreateDemandRequirementSchema = CreateDemandRequirementBaseSchema.refine( (data) => data.endDate >= data.startDate, { message: "End date must be after start date", path: ["endDate"] }, ); export const UpdateDemandRequirementSchema = CreateDemandRequirementBaseSchema.partial(); export const CreateAssignmentSchema = CreateAssignmentBaseSchema.refine( (data) => data.endDate >= data.startDate, { message: "End date must be after start date", path: ["endDate"] }, ); export const UpdateAssignmentSchema = CreateAssignmentBaseSchema.partial(); export const FillDemandRequirementSchema = z.object({ demandRequirementId: z.string(), resourceId: z.string(), hoursPerDay: z.number().min(0.5).max(24).optional(), status: z.nativeEnum(AllocationStatus).optional(), }); export const FillOpenDemandByAllocationSchema = z.object({ allocationId: z.string(), resourceId: z.string(), hoursPerDay: z.number().min(0.5).max(24).optional(), status: z.nativeEnum(AllocationStatus).optional(), }); export const ShiftProjectSchema = z .object({ projectId: z.string(), newStartDate: z.coerce.date(), newEndDate: z.coerce.date(), }) .refine((data) => data.newEndDate >= data.newStartDate, { message: "New end date must be after new start date", path: ["newEndDate"], }); export const UpdateAllocationHoursSchema = z.object({ allocationId: z.string(), hoursPerDay: z.number().min(0.5).max(24).optional(), startDate: z.coerce.date().optional(), endDate: z.coerce.date().optional(), includeSaturday: z.boolean().optional(), role: z.string().min(1).max(200).optional(), }); export type CreateAllocationInput = z.infer; export type UpdateAllocationInput = z.infer; export type CreateDemandRequirementInput = z.infer; export type UpdateDemandRequirementInput = z.infer; export type CreateAssignmentInput = z.infer; export type UpdateAssignmentInput = z.infer; export type FillDemandRequirementInput = z.infer; export type FillOpenDemandByAllocationInput = z.infer; export type ShiftProjectInput = z.infer; export type UpdateAllocationHoursInput = z.infer; // ─── Conflict check ─────────────────────────────────────────────────────────── export interface AllocationConflictDay { /** ISO date string (YYYY-MM-DD) */ date: string; availableHours: number; existingHours: number; requestedHours: number; overageHours: number; } export interface AllocationVacationOverlap { startDate: string; endDate: string; /** VacationType enum value */ type: string; isHalfDay: boolean; } export interface AllocationConflictCheckResult { isOverbooking: boolean; overbooking: { conflictDays: AllocationConflictDay[]; totalConflictDays: number; /** Highest single-day overage as a percentage above capacity */ maxOverbookPercent: number; } | null; vacationOverlap: AllocationVacationOverlap[]; hasVacationOverlap: boolean; }