refactor(api): extract timeline allocation mutation support

This commit is contained in:
2026-03-31 15:06:38 +02:00
parent b05758db69
commit b1ada431e1
3 changed files with 265 additions and 77 deletions
@@ -0,0 +1,112 @@
import { Prisma } from "@capakraken/db";
import { TRPCError } from "@trpc/server";
export type TimelineBatchShiftMode = "move" | "resize-start" | "resize-end";
export function assertTimelineDateRangeValid(startDate: Date, endDate: Date): void {
if (endDate >= startDate) {
return;
}
throw new TRPCError({
code: "BAD_REQUEST",
message: "End date must be after start date",
});
}
export function buildTimelineAllocationMetadata(input: {
existingMetadata: Record<string, unknown> | null | undefined;
includeSaturday: boolean | undefined;
}): { metadata: Record<string, unknown>; includeSaturday: boolean } {
const existingMetadata = input.existingMetadata ?? {};
const metadata: Record<string, unknown> = {
...existingMetadata,
...(input.includeSaturday !== undefined
? { includeSaturday: input.includeSaturday }
: {}),
};
const includeSaturday =
input.includeSaturday ?? (existingMetadata.includeSaturday as boolean | undefined) ?? false;
return { metadata, includeSaturday };
}
export function calculateTimelineAllocationPercentage(hoursPerDay: number): number {
return Math.min(100, Math.round((hoursPerDay / 8) * 100));
}
export function buildTimelineQuickAssignMetadata(source: "quickAssign" | "batchQuickAssign") {
return { source } satisfies Record<string, unknown>;
}
export function shiftTimelineAllocationWindow(input: {
startDate: Date;
endDate: Date;
daysDelta: number;
mode: TimelineBatchShiftMode;
}): { startDate: Date; endDate: Date } {
const startDate = new Date(input.startDate);
const endDate = new Date(input.endDate);
if (input.mode === "move") {
startDate.setDate(startDate.getDate() + input.daysDelta);
endDate.setDate(endDate.getDate() + input.daysDelta);
return { startDate, endDate };
}
if (input.mode === "resize-start") {
startDate.setDate(startDate.getDate() + input.daysDelta);
if (startDate > endDate) {
startDate.setTime(endDate.getTime());
}
return { startDate, endDate };
}
endDate.setDate(endDate.getDate() + input.daysDelta);
if (endDate < startDate) {
endDate.setTime(startDate.getTime());
}
return { startDate, endDate };
}
export function buildTimelineAllocationUpdateAuditChanges(input: {
allocationId: string;
previousHoursPerDay: number;
previousStartDate: Date;
previousEndDate: Date;
nextAllocationId: string;
nextHoursPerDay: number;
nextStartDate: Date;
nextEndDate: Date;
includeSaturday: boolean;
}): Prisma.InputJsonValue {
return {
before: {
id: input.allocationId,
hoursPerDay: input.previousHoursPerDay,
startDate: input.previousStartDate,
endDate: input.previousEndDate,
},
after: {
id: input.nextAllocationId,
hoursPerDay: input.nextHoursPerDay,
startDate: input.nextStartDate,
endDate: input.nextEndDate,
includeSaturday: input.includeSaturday,
},
} as unknown as Prisma.InputJsonValue;
}
export function buildTimelineBatchShiftAuditChanges(input: {
mode: TimelineBatchShiftMode;
daysDelta: number;
count: number;
}): Prisma.InputJsonValue {
return {
operation: "batchShift",
mode: input.mode,
daysDelta: input.daysDelta,
count: input.count,
} as unknown as Prisma.InputJsonValue;
}