refactor(api): extract timeline allocation mutation payload helpers

This commit is contained in:
2026-03-31 15:44:09 +02:00
parent 93e03e0f65
commit 231d3f3124
3 changed files with 149 additions and 45 deletions
@@ -1,4 +1,5 @@
import { Prisma } from "@capakraken/db";
import { AllocationStatus } from "@capakraken/shared";
import { TRPCError } from "@trpc/server";
export type TimelineBatchShiftMode = "move" | "resize-start" | "resize-end";
@@ -39,6 +40,39 @@ export function buildTimelineQuickAssignMetadata(source: "quickAssign" | "batchQ
return { source } satisfies Record<string, unknown>;
}
export function buildTimelineQuickAssignAssignmentInput(input: {
resourceId: string;
projectId: string;
startDate: Date;
endDate: Date;
hoursPerDay: number;
role: string;
roleId?: string | undefined;
status: AllocationStatus;
source: "quickAssign" | "batchQuickAssign";
}) {
return {
resourceId: input.resourceId,
projectId: input.projectId,
startDate: input.startDate,
endDate: input.endDate,
hoursPerDay: input.hoursPerDay,
percentage: calculateTimelineAllocationPercentage(input.hoursPerDay),
role: input.role,
...(input.roleId !== undefined ? { roleId: input.roleId } : {}),
status: input.status,
metadata: buildTimelineQuickAssignMetadata(input.source),
};
}
export function validateTimelineAllocationDateRanges(
ranges: Array<{ startDate: Date; endDate: Date }>,
): void {
for (const range of ranges) {
assertTimelineDateRangeValid(range.startDate, range.endDate);
}
}
export function shiftTimelineAllocationWindow(input: {
startDate: Date;
endDate: Date;
@@ -98,6 +132,33 @@ export function buildTimelineAllocationUpdateAuditChanges(input: {
} as unknown as Prisma.InputJsonValue;
}
export function buildTimelineAllocationEntryUpdate(input: {
hoursPerDay: number;
startDate: Date;
endDate: Date;
metadata: Record<string, unknown>;
dailyCostCents: number;
role?: string | undefined;
}) {
return {
demandRequirementUpdate: {
hoursPerDay: input.hoursPerDay,
startDate: input.startDate,
endDate: input.endDate,
metadata: input.metadata,
...(input.role !== undefined ? { role: input.role } : {}),
},
assignmentUpdate: {
hoursPerDay: input.hoursPerDay,
startDate: input.startDate,
endDate: input.endDate,
dailyCostCents: input.dailyCostCents,
metadata: input.metadata,
...(input.role !== undefined ? { role: input.role } : {}),
},
};
}
export function buildTimelineBatchShiftAuditChanges(input: {
mode: TimelineBatchShiftMode;
daysDelta: number;
@@ -22,12 +22,13 @@ import {
import { managerProcedure, requirePermission } from "../trpc.js";
import {
assertTimelineDateRangeValid,
buildTimelineAllocationEntryUpdate,
buildTimelineAllocationMetadata,
buildTimelineAllocationUpdateAuditChanges,
buildTimelineBatchShiftAuditChanges,
buildTimelineQuickAssignMetadata,
calculateTimelineAllocationPercentage,
buildTimelineQuickAssignAssignmentInput,
shiftTimelineAllocationWindow,
validateTimelineAllocationDateRanges,
} from "./timeline-allocation-mutation-support.js";
import { calculateTimelineAllocationDailyCost } from "./timeline-cost-support.js";
@@ -81,21 +82,14 @@ export const timelineAllocationMutationProcedures = {
tx as unknown as Parameters<typeof updateAllocationEntry>[0],
{
id: input.allocationId,
demandRequirementUpdate: {
...buildTimelineAllocationEntryUpdate({
hoursPerDay: newHoursPerDay,
startDate: newStartDate,
endDate: newEndDate,
metadata: newMeta,
...(input.role !== undefined ? { role: input.role } : {}),
},
assignmentUpdate: {
hoursPerDay: newHoursPerDay,
startDate: newStartDate,
endDate: newEndDate,
dailyCostCents: newDailyCostCents,
metadata: newMeta,
...(input.role !== undefined ? { role: input.role } : {}),
},
role: input.role,
}),
},
);
@@ -147,24 +141,13 @@ export const timelineAllocationMutationProcedures = {
requirePermission(ctx, PermissionKey.MANAGE_ALLOCATIONS);
assertTimelineDateRangeValid(input.startDate, input.endDate);
const percentage = calculateTimelineAllocationPercentage(input.hoursPerDay);
const metadata = buildTimelineQuickAssignMetadata("quickAssign");
const allocation = await ctx.db.$transaction(async (tx) => {
const assignment = await createAssignment(
tx as unknown as Parameters<typeof createAssignment>[0],
{
resourceId: input.resourceId,
projectId: input.projectId,
startDate: input.startDate,
endDate: input.endDate,
hoursPerDay: input.hoursPerDay,
percentage,
role: input.role,
roleId: input.roleId ?? undefined,
status: input.status,
metadata,
},
buildTimelineQuickAssignAssignmentInput({
...input,
source: "quickAssign",
}),
);
return buildSplitAllocationReadModel({
@@ -205,30 +188,17 @@ export const timelineAllocationMutationProcedures = {
)
.mutation(async ({ ctx, input }) => {
requirePermission(ctx, PermissionKey.MANAGE_ALLOCATIONS);
for (const assignment of input.assignments) {
assertTimelineDateRangeValid(assignment.startDate, assignment.endDate);
}
validateTimelineAllocationDateRanges(input.assignments);
const results = await ctx.db.$transaction(async (tx) => {
const created = [];
for (const assignment of input.assignments) {
const percentage = calculateTimelineAllocationPercentage(assignment.hoursPerDay);
const metadata = buildTimelineQuickAssignMetadata("batchQuickAssign");
const createdAssignment = await createAssignment(
tx as unknown as Parameters<typeof createAssignment>[0],
{
resourceId: assignment.resourceId,
projectId: assignment.projectId,
startDate: assignment.startDate,
endDate: assignment.endDate,
hoursPerDay: assignment.hoursPerDay,
percentage,
role: assignment.role,
status: assignment.status,
metadata,
},
buildTimelineQuickAssignAssignmentInput({
...assignment,
source: "batchQuickAssign",
}),
);
created.push(createdAssignment);
}