import { buildSplitAllocationReadModel, createAssignment, } from "@capakraken/application"; import type { PrismaClient } from "@capakraken/db"; import { AllocationStatus, PermissionKey, UpdateAllocationHoursSchema, } from "@capakraken/shared"; import { z } from "zod"; import { emitAllocationCreated, emitAllocationUpdated, } from "../sse/event-bus.js"; import { managerProcedure, requirePermission } from "../trpc.js"; import { assertTimelineDateRangeValid, buildTimelineQuickAssignAssignmentInput, validateTimelineAllocationDateRanges, } from "./timeline-allocation-mutation-support.js"; import { applyTimelineInlineAllocationUpdate } from "./timeline-allocation-inline-support.js"; import { applyTimelineBatchAllocationShift } from "./timeline-allocation-shift-support.js"; export const timelineAllocationMutationProcedures = { updateAllocationInline: managerProcedure .input(UpdateAllocationHoursSchema) .mutation(async ({ ctx, input }) => { requirePermission(ctx, PermissionKey.MANAGE_ALLOCATIONS); const updated = await applyTimelineInlineAllocationUpdate({ db: ctx.db as PrismaClient, allocationId: input.allocationId, hoursPerDay: input.hoursPerDay, startDate: input.startDate, endDate: input.endDate, includeSaturday: input.includeSaturday, role: input.role, }); emitAllocationUpdated({ id: updated.id, projectId: updated.projectId, resourceId: updated.resourceId, }); return updated; }), quickAssign: managerProcedure .input( z.object({ resourceId: z.string(), projectId: z.string(), startDate: z.coerce.date(), endDate: z.coerce.date(), hoursPerDay: z.number().min(0.5).max(24).default(8), role: z.string().min(1).max(200).default("Team Member"), roleId: z.string().optional(), status: z.nativeEnum(AllocationStatus).default(AllocationStatus.PROPOSED), }), ) .mutation(async ({ ctx, input }) => { requirePermission(ctx, PermissionKey.MANAGE_ALLOCATIONS); assertTimelineDateRangeValid(input.startDate, input.endDate); const allocation = await ctx.db.$transaction(async (tx) => { const assignment = await createAssignment( tx as unknown as Parameters[0], buildTimelineQuickAssignAssignmentInput({ ...input, source: "quickAssign", }), ); return buildSplitAllocationReadModel({ demandRequirements: [], assignments: [assignment], }).allocations[0]!; }); emitAllocationCreated({ id: allocation.id, projectId: allocation.projectId, resourceId: allocation.resourceId, }); return allocation; }), batchQuickAssign: managerProcedure .input( z.object({ assignments: z .array( z.object({ resourceId: z.string(), projectId: z.string(), startDate: z.coerce.date(), endDate: z.coerce.date(), hoursPerDay: z.number().min(0.5).max(24).default(8), role: z.string().min(1).max(200).default("Team Member"), status: z .nativeEnum(AllocationStatus) .default(AllocationStatus.PROPOSED), }), ) .min(1) .max(50), }), ) .mutation(async ({ ctx, input }) => { requirePermission(ctx, PermissionKey.MANAGE_ALLOCATIONS); validateTimelineAllocationDateRanges(input.assignments); const results = await ctx.db.$transaction(async (tx) => { const created = []; for (const assignment of input.assignments) { const createdAssignment = await createAssignment( tx as unknown as Parameters[0], buildTimelineQuickAssignAssignmentInput({ ...assignment, source: "batchQuickAssign", }), ); created.push(createdAssignment); } return created; }); for (const assignment of results) { emitAllocationCreated({ id: assignment.id, projectId: assignment.projectId, resourceId: assignment.resourceId, }); } return { count: results.length }; }), batchShiftAllocations: managerProcedure .input( z.object({ allocationIds: z.array(z.string()).min(1).max(100), daysDelta: z.number().int().min(-3650).max(3650), mode: z.enum(["move", "resize-start", "resize-end"]).default("move"), }), ) .mutation(async ({ ctx, input }) => { requirePermission(ctx, PermissionKey.MANAGE_ALLOCATIONS); const results = await applyTimelineBatchAllocationShift({ db: ctx.db as PrismaClient, allocationIds: input.allocationIds, daysDelta: input.daysDelta, mode: input.mode, }); for (const allocation of results) { emitAllocationUpdated({ id: allocation.id, projectId: allocation.projectId, resourceId: allocation.resourceId, }); } return { count: results.length }; }), };