From 0b72eef61ff777e7ebcc34c2fc57279e8504e868 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hartmut=20N=C3=B6renberg?= Date: Tue, 31 Mar 2026 18:06:09 +0200 Subject: [PATCH] refactor(api): extract timeline allocation mutation schemas --- ...allocation-mutation-schema-support.test.ts | 68 +++++++++++++++++++ ...line-allocation-mutation-schema-support.ts | 38 +++++++++++ .../router/timeline-allocation-mutations.ts | 55 +++------------ 3 files changed, 116 insertions(+), 45 deletions(-) create mode 100644 packages/api/src/__tests__/timeline-allocation-mutation-schema-support.test.ts create mode 100644 packages/api/src/router/timeline-allocation-mutation-schema-support.ts diff --git a/packages/api/src/__tests__/timeline-allocation-mutation-schema-support.test.ts b/packages/api/src/__tests__/timeline-allocation-mutation-schema-support.test.ts new file mode 100644 index 0000000..0af266a --- /dev/null +++ b/packages/api/src/__tests__/timeline-allocation-mutation-schema-support.test.ts @@ -0,0 +1,68 @@ +import { AllocationStatus } from "@capakraken/shared"; +import { describe, expect, it } from "vitest"; +import { + timelineBatchQuickAssignInputSchema, + timelineBatchShiftAllocationsInputSchema, + timelineQuickAssignInputSchema, +} from "../router/timeline-allocation-mutation-schema-support.js"; + +describe("timeline allocation mutation schema support", () => { + it("applies defaults for quick assign input", () => { + expect( + timelineQuickAssignInputSchema.parse({ + resourceId: "resource_1", + projectId: "project_1", + startDate: "2026-04-01T00:00:00.000Z", + endDate: "2026-04-05T00:00:00.000Z", + }), + ).toEqual({ + resourceId: "resource_1", + projectId: "project_1", + startDate: new Date("2026-04-01T00:00:00.000Z"), + endDate: new Date("2026-04-05T00:00:00.000Z"), + hoursPerDay: 8, + role: "Team Member", + status: AllocationStatus.PROPOSED, + }); + }); + + it("applies defaults to each batch quick assign entry", () => { + expect( + timelineBatchQuickAssignInputSchema.parse({ + assignments: [ + { + resourceId: "resource_1", + projectId: "project_1", + startDate: "2026-04-01T00:00:00.000Z", + endDate: "2026-04-05T00:00:00.000Z", + }, + ], + }), + ).toEqual({ + assignments: [ + { + resourceId: "resource_1", + projectId: "project_1", + startDate: new Date("2026-04-01T00:00:00.000Z"), + endDate: new Date("2026-04-05T00:00:00.000Z"), + hoursPerDay: 8, + role: "Team Member", + status: AllocationStatus.PROPOSED, + }, + ], + }); + }); + + it("defaults batch shift mode to move", () => { + expect( + timelineBatchShiftAllocationsInputSchema.parse({ + allocationIds: ["allocation_1"], + daysDelta: 3, + }), + ).toEqual({ + allocationIds: ["allocation_1"], + daysDelta: 3, + mode: "move", + }); + }); +}); diff --git a/packages/api/src/router/timeline-allocation-mutation-schema-support.ts b/packages/api/src/router/timeline-allocation-mutation-schema-support.ts new file mode 100644 index 0000000..06207db --- /dev/null +++ b/packages/api/src/router/timeline-allocation-mutation-schema-support.ts @@ -0,0 +1,38 @@ +import { AllocationStatus, UpdateAllocationHoursSchema } from "@capakraken/shared"; +import { z } from "zod"; + +export { UpdateAllocationHoursSchema }; + +export const timelineQuickAssignInputSchema = 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), +}); + +export const timelineBatchQuickAssignInputSchema = 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), +}); + +export const timelineBatchShiftAllocationsInputSchema = 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"), +}); diff --git a/packages/api/src/router/timeline-allocation-mutations.ts b/packages/api/src/router/timeline-allocation-mutations.ts index 603e745..6936f3f 100644 --- a/packages/api/src/router/timeline-allocation-mutations.ts +++ b/packages/api/src/router/timeline-allocation-mutations.ts @@ -1,10 +1,5 @@ import type { PrismaClient } from "@capakraken/db"; -import { - AllocationStatus, - PermissionKey, - UpdateAllocationHoursSchema, -} from "@capakraken/shared"; -import { z } from "zod"; +import { PermissionKey } from "@capakraken/shared"; import { emitAllocationUpdated } from "../sse/event-bus.js"; import { managerProcedure, requirePermission } from "../trpc.js"; import { @@ -12,6 +7,12 @@ import { createTimelineQuickAssignment, shiftTimelineAllocations, } from "./timeline-allocation-procedure-support.js"; +import { + UpdateAllocationHoursSchema, + timelineBatchQuickAssignInputSchema, + timelineBatchShiftAllocationsInputSchema, + timelineQuickAssignInputSchema, +} from "./timeline-allocation-mutation-schema-support.js"; import { applyTimelineInlineAllocationUpdate } from "./timeline-allocation-inline-support.js"; export const timelineAllocationMutationProcedures = { @@ -39,18 +40,7 @@ export const timelineAllocationMutationProcedures = { }), 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), - }), - ) + .input(timelineQuickAssignInputSchema) .mutation(async ({ ctx, input }) => { requirePermission(ctx, PermissionKey.MANAGE_ALLOCATIONS); return createTimelineQuickAssignment(ctx.db as PrismaClient, { @@ -60,26 +50,7 @@ export const timelineAllocationMutationProcedures = { }), 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), - }), - ) + .input(timelineBatchQuickAssignInputSchema) .mutation(async ({ ctx, input }) => { requirePermission(ctx, PermissionKey.MANAGE_ALLOCATIONS); return createTimelineBatchQuickAssignments(ctx.db as PrismaClient, { @@ -91,13 +62,7 @@ export const timelineAllocationMutationProcedures = { }), 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"), - }), - ) + .input(timelineBatchShiftAllocationsInputSchema) .mutation(async ({ ctx, input }) => { requirePermission(ctx, PermissionKey.MANAGE_ALLOCATIONS); return shiftTimelineAllocations(ctx.db as PrismaClient, {