Files
CapaKraken/packages/shared/src/schemas/allocation.schema.ts
T

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;
}