Files
CapaKraken/packages/api/src/router/timeline-allocation-mutations.ts
T

170 lines
5.2 KiB
TypeScript

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<typeof createAssignment>[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<typeof createAssignment>[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 };
}),
};