refactor(api): extract timeline allocation mutation support
This commit is contained in:
@@ -6,7 +6,13 @@ import {
|
||||
updateAllocationEntry,
|
||||
} from "@capakraken/application";
|
||||
import type { PrismaClient } from "@capakraken/db";
|
||||
import { AllocationStatus, PermissionKey, UpdateAllocationHoursSchema } from "@capakraken/shared";
|
||||
import {
|
||||
AllocationStatus,
|
||||
PermissionKey,
|
||||
UpdateAllocationHoursSchema,
|
||||
type RecurrencePattern,
|
||||
type WeekdayAvailability,
|
||||
} from "@capakraken/shared";
|
||||
import { TRPCError } from "@trpc/server";
|
||||
import { z } from "zod";
|
||||
import {
|
||||
@@ -14,6 +20,15 @@ import {
|
||||
emitAllocationUpdated,
|
||||
} from "../sse/event-bus.js";
|
||||
import { managerProcedure, requirePermission } from "../trpc.js";
|
||||
import {
|
||||
assertTimelineDateRangeValid,
|
||||
buildTimelineAllocationMetadata,
|
||||
buildTimelineAllocationUpdateAuditChanges,
|
||||
buildTimelineBatchShiftAuditChanges,
|
||||
buildTimelineQuickAssignMetadata,
|
||||
calculateTimelineAllocationPercentage,
|
||||
shiftTimelineAllocationWindow,
|
||||
} from "./timeline-allocation-mutation-support.js";
|
||||
import { calculateTimelineAllocationDailyCost } from "./timeline-cost-support.js";
|
||||
|
||||
export const timelineAllocationMutationProcedures = {
|
||||
@@ -34,22 +49,11 @@ export const timelineAllocationMutationProcedures = {
|
||||
const newStartDate = input.startDate ?? existing.startDate;
|
||||
const newEndDate = input.endDate ?? existing.endDate;
|
||||
|
||||
if (newEndDate < newStartDate) {
|
||||
throw new TRPCError({
|
||||
code: "BAD_REQUEST",
|
||||
message: "End date must be after start date",
|
||||
});
|
||||
}
|
||||
|
||||
const existingMeta = (existing.metadata as Record<string, unknown>) ?? {};
|
||||
const newMeta: Record<string, unknown> = {
|
||||
...existingMeta,
|
||||
...(input.includeSaturday !== undefined
|
||||
? { includeSaturday: input.includeSaturday }
|
||||
: {}),
|
||||
};
|
||||
const includeSaturday =
|
||||
input.includeSaturday ?? (existingMeta.includeSaturday as boolean | undefined) ?? false;
|
||||
assertTimelineDateRangeValid(newStartDate, newEndDate);
|
||||
const { metadata: newMeta, includeSaturday } = buildTimelineAllocationMetadata({
|
||||
existingMetadata: existing.metadata as Record<string, unknown> | null | undefined,
|
||||
includeSaturday: input.includeSaturday,
|
||||
});
|
||||
|
||||
let newDailyCostCents = 0;
|
||||
if (resolved.resourceId) {
|
||||
@@ -57,9 +61,8 @@ export const timelineAllocationMutationProcedures = {
|
||||
throw new TRPCError({ code: "NOT_FOUND", message: "Resource not found" });
|
||||
}
|
||||
|
||||
const availability =
|
||||
existingResource.availability as unknown as import("@capakraken/shared").WeekdayAvailability;
|
||||
const recurrence = newMeta.recurrence as import("@capakraken/shared").RecurrencePattern | undefined;
|
||||
const availability = existingResource.availability as unknown as WeekdayAvailability;
|
||||
const recurrence = newMeta.recurrence as RecurrencePattern | undefined;
|
||||
newDailyCostCents = await calculateTimelineAllocationDailyCost({
|
||||
db: ctx.db as PrismaClient,
|
||||
resourceId: resolved.resourceId,
|
||||
@@ -101,21 +104,17 @@ export const timelineAllocationMutationProcedures = {
|
||||
entityType: "Allocation",
|
||||
entityId: input.allocationId,
|
||||
action: "UPDATE",
|
||||
changes: {
|
||||
before: {
|
||||
id: resolved.entry.id,
|
||||
hoursPerDay: existing.hoursPerDay,
|
||||
startDate: existing.startDate,
|
||||
endDate: existing.endDate,
|
||||
},
|
||||
after: {
|
||||
id: updatedAllocation.id,
|
||||
hoursPerDay: newHoursPerDay,
|
||||
startDate: newStartDate,
|
||||
endDate: newEndDate,
|
||||
includeSaturday,
|
||||
},
|
||||
},
|
||||
changes: buildTimelineAllocationUpdateAuditChanges({
|
||||
allocationId: resolved.entry.id,
|
||||
previousHoursPerDay: existing.hoursPerDay,
|
||||
previousStartDate: existing.startDate,
|
||||
previousEndDate: existing.endDate,
|
||||
nextAllocationId: updatedAllocation.id,
|
||||
nextHoursPerDay: newHoursPerDay,
|
||||
nextStartDate: newStartDate,
|
||||
nextEndDate: newEndDate,
|
||||
includeSaturday,
|
||||
}),
|
||||
},
|
||||
});
|
||||
|
||||
@@ -146,12 +145,10 @@ export const timelineAllocationMutationProcedures = {
|
||||
)
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
requirePermission(ctx, PermissionKey.MANAGE_ALLOCATIONS);
|
||||
if (input.endDate < input.startDate) {
|
||||
throw new TRPCError({ code: "BAD_REQUEST", message: "End date must be after start date" });
|
||||
}
|
||||
assertTimelineDateRangeValid(input.startDate, input.endDate);
|
||||
|
||||
const percentage = Math.min(100, Math.round((input.hoursPerDay / 8) * 100));
|
||||
const metadata = { source: "quickAssign" } satisfies Record<string, unknown>;
|
||||
const percentage = calculateTimelineAllocationPercentage(input.hoursPerDay);
|
||||
const metadata = buildTimelineQuickAssignMetadata("quickAssign");
|
||||
|
||||
const allocation = await ctx.db.$transaction(async (tx) => {
|
||||
const assignment = await createAssignment(
|
||||
@@ -210,24 +207,14 @@ export const timelineAllocationMutationProcedures = {
|
||||
requirePermission(ctx, PermissionKey.MANAGE_ALLOCATIONS);
|
||||
|
||||
for (const assignment of input.assignments) {
|
||||
if (assignment.endDate < assignment.startDate) {
|
||||
throw new TRPCError({
|
||||
code: "BAD_REQUEST",
|
||||
message: "End date must be after start date",
|
||||
});
|
||||
}
|
||||
assertTimelineDateRangeValid(assignment.startDate, assignment.endDate);
|
||||
}
|
||||
|
||||
const results = await ctx.db.$transaction(async (tx) => {
|
||||
const created = [];
|
||||
for (const assignment of input.assignments) {
|
||||
const percentage = Math.min(
|
||||
100,
|
||||
Math.round((assignment.hoursPerDay / 8) * 100),
|
||||
);
|
||||
const metadata = {
|
||||
source: "batchQuickAssign",
|
||||
} satisfies Record<string, unknown>;
|
||||
const percentage = calculateTimelineAllocationPercentage(assignment.hoursPerDay);
|
||||
const metadata = buildTimelineQuickAssignMetadata("batchQuickAssign");
|
||||
|
||||
const createdAssignment = await createAssignment(
|
||||
tx as unknown as Parameters<typeof createAssignment>[0],
|
||||
@@ -289,35 +276,24 @@ export const timelineAllocationMutationProcedures = {
|
||||
const updated = [];
|
||||
for (const entry of resolved) {
|
||||
const existing = entry.entry;
|
||||
const newStart = new Date(existing.startDate);
|
||||
const newEnd = new Date(existing.endDate);
|
||||
|
||||
if (input.mode === "move") {
|
||||
newStart.setDate(newStart.getDate() + input.daysDelta);
|
||||
newEnd.setDate(newEnd.getDate() + input.daysDelta);
|
||||
} else if (input.mode === "resize-start") {
|
||||
newStart.setDate(newStart.getDate() + input.daysDelta);
|
||||
if (newStart > newEnd) {
|
||||
newStart.setTime(newEnd.getTime());
|
||||
}
|
||||
} else {
|
||||
newEnd.setDate(newEnd.getDate() + input.daysDelta);
|
||||
if (newEnd < newStart) {
|
||||
newEnd.setTime(newStart.getTime());
|
||||
}
|
||||
}
|
||||
const shiftedWindow = shiftTimelineAllocationWindow({
|
||||
startDate: existing.startDate,
|
||||
endDate: existing.endDate,
|
||||
daysDelta: input.daysDelta,
|
||||
mode: input.mode,
|
||||
});
|
||||
|
||||
const result = await updateAllocationEntry(
|
||||
tx as unknown as Parameters<typeof updateAllocationEntry>[0],
|
||||
{
|
||||
id: existing.id,
|
||||
demandRequirementUpdate: {
|
||||
startDate: newStart,
|
||||
endDate: newEnd,
|
||||
startDate: shiftedWindow.startDate,
|
||||
endDate: shiftedWindow.endDate,
|
||||
},
|
||||
assignmentUpdate: {
|
||||
startDate: newStart,
|
||||
endDate: newEnd,
|
||||
startDate: shiftedWindow.startDate,
|
||||
endDate: shiftedWindow.endDate,
|
||||
},
|
||||
},
|
||||
);
|
||||
@@ -329,12 +305,11 @@ export const timelineAllocationMutationProcedures = {
|
||||
entityType: "Allocation",
|
||||
entityId: input.allocationIds.join(","),
|
||||
action: "UPDATE",
|
||||
changes: {
|
||||
operation: "batchShift",
|
||||
changes: buildTimelineBatchShiftAuditChanges({
|
||||
mode: input.mode,
|
||||
daysDelta: input.daysDelta,
|
||||
count: resolved.length,
|
||||
} as unknown as import("@capakraken/db").Prisma.InputJsonValue,
|
||||
}),
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user