155 lines
5.8 KiB
TypeScript
155 lines
5.8 KiB
TypeScript
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<typeof CreateAllocationSchema>;
|
|
export type UpdateAllocationInput = z.infer<typeof UpdateAllocationSchema>;
|
|
export type CreateDemandRequirementInput = z.infer<typeof CreateDemandRequirementSchema>;
|
|
export type UpdateDemandRequirementInput = z.infer<typeof UpdateDemandRequirementSchema>;
|
|
export type CreateAssignmentInput = z.infer<typeof CreateAssignmentSchema>;
|
|
export type UpdateAssignmentInput = z.infer<typeof UpdateAssignmentSchema>;
|
|
export type FillDemandRequirementInput = z.infer<typeof FillDemandRequirementSchema>;
|
|
export type FillOpenDemandByAllocationInput = z.infer<typeof FillOpenDemandByAllocationSchema>;
|
|
export type ShiftProjectInput = z.infer<typeof ShiftProjectSchema>;
|
|
export type UpdateAllocationHoursInput = z.infer<typeof UpdateAllocationHoursSchema>;
|
|
|
|
// ─── 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;
|
|
}
|