refactor(api): extract timeline allocation batch shift support

This commit is contained in:
2026-03-31 16:04:37 +02:00
parent 803de725ad
commit b17110edaf
3 changed files with 226 additions and 60 deletions
@@ -1,7 +1,6 @@
import {
buildSplitAllocationReadModel,
createAssignment,
findAllocationEntry,
loadAllocationEntry,
updateAllocationEntry,
} from "@capakraken/application";
@@ -25,11 +24,10 @@ import {
buildTimelineAllocationEntryUpdate,
buildTimelineAllocationMetadata,
buildTimelineAllocationUpdateAuditChanges,
buildTimelineBatchShiftAuditChanges,
buildTimelineQuickAssignAssignmentInput,
shiftTimelineAllocationWindow,
validateTimelineAllocationDateRanges,
} from "./timeline-allocation-mutation-support.js";
import { applyTimelineBatchAllocationShift } from "./timeline-allocation-shift-support.js";
import { calculateTimelineAllocationDailyCost } from "./timeline-cost-support.js";
export const timelineAllocationMutationProcedures = {
@@ -227,63 +225,11 @@ export const timelineAllocationMutationProcedures = {
.mutation(async ({ ctx, input }) => {
requirePermission(ctx, PermissionKey.MANAGE_ALLOCATIONS);
if (input.daysDelta === 0) {
return { count: 0 };
}
const entries = await Promise.all(
input.allocationIds.map((allocationId) => findAllocationEntry(ctx.db, allocationId)),
);
const resolved = entries.filter(
(entry): entry is NonNullable<typeof entry> => entry !== null,
);
if (resolved.length === 0) {
throw new TRPCError({ code: "NOT_FOUND", message: "No allocations found" });
}
const results = await ctx.db.$transaction(async (tx) => {
const updated = [];
for (const entry of resolved) {
const existing = entry.entry;
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: shiftedWindow.startDate,
endDate: shiftedWindow.endDate,
},
assignmentUpdate: {
startDate: shiftedWindow.startDate,
endDate: shiftedWindow.endDate,
},
},
);
updated.push(result.allocation);
}
await tx.auditLog.create({
data: {
entityType: "Allocation",
entityId: input.allocationIds.join(","),
action: "UPDATE",
changes: buildTimelineBatchShiftAuditChanges({
mode: input.mode,
daysDelta: input.daysDelta,
count: resolved.length,
}),
},
});
return updated;
const results = await applyTimelineBatchAllocationShift({
db: ctx.db as PrismaClient,
allocationIds: input.allocationIds,
daysDelta: input.daysDelta,
mode: input.mode,
});
for (const allocation of results) {
@@ -0,0 +1,74 @@
import { findAllocationEntry, updateAllocationEntry } from "@capakraken/application";
import type { PrismaClient } from "@capakraken/db";
import { TRPCError } from "@trpc/server";
import {
buildTimelineBatchShiftAuditChanges,
shiftTimelineAllocationWindow,
type TimelineBatchShiftMode,
} from "./timeline-allocation-mutation-support.js";
export async function applyTimelineBatchAllocationShift(input: {
db: PrismaClient;
allocationIds: string[];
daysDelta: number;
mode: TimelineBatchShiftMode;
}) {
if (input.daysDelta === 0) {
return [];
}
const entries = await Promise.all(
input.allocationIds.map((allocationId) => findAllocationEntry(input.db, allocationId)),
);
const resolved = entries.filter(
(entry): entry is NonNullable<typeof entry> => entry !== null,
);
if (resolved.length === 0) {
throw new TRPCError({ code: "NOT_FOUND", message: "No allocations found" });
}
return input.db.$transaction(async (tx) => {
const updated = [];
for (const entry of resolved) {
const existing = entry.entry;
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: shiftedWindow.startDate,
endDate: shiftedWindow.endDate,
},
assignmentUpdate: {
startDate: shiftedWindow.startDate,
endDate: shiftedWindow.endDate,
},
},
);
updated.push(result.allocation);
}
await tx.auditLog.create({
data: {
entityType: "Allocation",
entityId: input.allocationIds.join(","),
action: "UPDATE",
changes: buildTimelineBatchShiftAuditChanges({
mode: input.mode,
daysDelta: input.daysDelta,
count: resolved.length,
}),
},
});
return updated;
});
}