refactor(api): extract timeline batch shift support
This commit is contained in:
@@ -0,0 +1,60 @@
|
|||||||
|
import { describe, expect, it } from "vitest";
|
||||||
|
import {
|
||||||
|
buildTimelineBatchShiftAuditChanges,
|
||||||
|
shiftTimelineAllocationWindow,
|
||||||
|
} from "../router/timeline-allocation-batch-shift-support.js";
|
||||||
|
|
||||||
|
describe("timeline allocation batch shift support", () => {
|
||||||
|
it("shifts and clamps allocation windows for each batch-shift mode", () => {
|
||||||
|
expect(
|
||||||
|
shiftTimelineAllocationWindow({
|
||||||
|
startDate: new Date("2026-04-10T00:00:00.000Z"),
|
||||||
|
endDate: new Date("2026-04-12T00:00:00.000Z"),
|
||||||
|
daysDelta: 2,
|
||||||
|
mode: "move",
|
||||||
|
}),
|
||||||
|
).toEqual({
|
||||||
|
startDate: new Date("2026-04-12T00:00:00.000Z"),
|
||||||
|
endDate: new Date("2026-04-14T00:00:00.000Z"),
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(
|
||||||
|
shiftTimelineAllocationWindow({
|
||||||
|
startDate: new Date("2026-04-10T00:00:00.000Z"),
|
||||||
|
endDate: new Date("2026-04-12T00:00:00.000Z"),
|
||||||
|
daysDelta: 5,
|
||||||
|
mode: "resize-start",
|
||||||
|
}),
|
||||||
|
).toEqual({
|
||||||
|
startDate: new Date("2026-04-12T00:00:00.000Z"),
|
||||||
|
endDate: new Date("2026-04-12T00:00:00.000Z"),
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(
|
||||||
|
shiftTimelineAllocationWindow({
|
||||||
|
startDate: new Date("2026-04-10T00:00:00.000Z"),
|
||||||
|
endDate: new Date("2026-04-12T00:00:00.000Z"),
|
||||||
|
daysDelta: -5,
|
||||||
|
mode: "resize-end",
|
||||||
|
}),
|
||||||
|
).toEqual({
|
||||||
|
startDate: new Date("2026-04-10T00:00:00.000Z"),
|
||||||
|
endDate: new Date("2026-04-10T00:00:00.000Z"),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("builds batch-shift audit payloads", () => {
|
||||||
|
expect(
|
||||||
|
buildTimelineBatchShiftAuditChanges({
|
||||||
|
mode: "move",
|
||||||
|
daysDelta: 3,
|
||||||
|
count: 2,
|
||||||
|
}),
|
||||||
|
).toEqual({
|
||||||
|
operation: "batchShift",
|
||||||
|
mode: "move",
|
||||||
|
daysDelta: 3,
|
||||||
|
count: 2,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -4,8 +4,6 @@ import {
|
|||||||
assertTimelineDateRangeValid,
|
assertTimelineDateRangeValid,
|
||||||
buildTimelineAllocationEntryUpdate,
|
buildTimelineAllocationEntryUpdate,
|
||||||
buildTimelineAllocationMetadata,
|
buildTimelineAllocationMetadata,
|
||||||
buildTimelineBatchShiftAuditChanges,
|
|
||||||
shiftTimelineAllocationWindow,
|
|
||||||
validateTimelineAllocationDateRanges,
|
validateTimelineAllocationDateRanges,
|
||||||
} from "../router/timeline-allocation-mutation-support.js";
|
} from "../router/timeline-allocation-mutation-support.js";
|
||||||
|
|
||||||
@@ -36,59 +34,6 @@ describe("timeline allocation mutation support", () => {
|
|||||||
)).toThrowError(TRPCError);
|
)).toThrowError(TRPCError);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("shifts and clamps allocation windows for each batch-shift mode", () => {
|
|
||||||
expect(
|
|
||||||
shiftTimelineAllocationWindow({
|
|
||||||
startDate: new Date("2026-04-10T00:00:00.000Z"),
|
|
||||||
endDate: new Date("2026-04-12T00:00:00.000Z"),
|
|
||||||
daysDelta: 2,
|
|
||||||
mode: "move",
|
|
||||||
}),
|
|
||||||
).toEqual({
|
|
||||||
startDate: new Date("2026-04-12T00:00:00.000Z"),
|
|
||||||
endDate: new Date("2026-04-14T00:00:00.000Z"),
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(
|
|
||||||
shiftTimelineAllocationWindow({
|
|
||||||
startDate: new Date("2026-04-10T00:00:00.000Z"),
|
|
||||||
endDate: new Date("2026-04-12T00:00:00.000Z"),
|
|
||||||
daysDelta: 5,
|
|
||||||
mode: "resize-start",
|
|
||||||
}),
|
|
||||||
).toEqual({
|
|
||||||
startDate: new Date("2026-04-12T00:00:00.000Z"),
|
|
||||||
endDate: new Date("2026-04-12T00:00:00.000Z"),
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(
|
|
||||||
shiftTimelineAllocationWindow({
|
|
||||||
startDate: new Date("2026-04-10T00:00:00.000Z"),
|
|
||||||
endDate: new Date("2026-04-12T00:00:00.000Z"),
|
|
||||||
daysDelta: -5,
|
|
||||||
mode: "resize-end",
|
|
||||||
}),
|
|
||||||
).toEqual({
|
|
||||||
startDate: new Date("2026-04-10T00:00:00.000Z"),
|
|
||||||
endDate: new Date("2026-04-10T00:00:00.000Z"),
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("builds batch-shift audit payloads", () => {
|
|
||||||
expect(
|
|
||||||
buildTimelineBatchShiftAuditChanges({
|
|
||||||
mode: "move",
|
|
||||||
daysDelta: 3,
|
|
||||||
count: 2,
|
|
||||||
}),
|
|
||||||
).toEqual({
|
|
||||||
operation: "batchShift",
|
|
||||||
mode: "move",
|
|
||||||
daysDelta: 3,
|
|
||||||
count: 2,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("validates date ranges in bulk", () => {
|
it("validates date ranges in bulk", () => {
|
||||||
expect(() =>
|
expect(() =>
|
||||||
validateTimelineAllocationDateRanges([
|
validateTimelineAllocationDateRanges([
|
||||||
|
|||||||
@@ -0,0 +1,47 @@
|
|||||||
|
import { Prisma } from "@capakraken/db";
|
||||||
|
|
||||||
|
export type TimelineBatchShiftMode = "move" | "resize-start" | "resize-end";
|
||||||
|
|
||||||
|
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 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;
|
||||||
|
}
|
||||||
@@ -1,8 +1,5 @@
|
|||||||
import { Prisma } from "@capakraken/db";
|
|
||||||
import { TRPCError } from "@trpc/server";
|
import { TRPCError } from "@trpc/server";
|
||||||
|
|
||||||
export type TimelineBatchShiftMode = "move" | "resize-start" | "resize-end";
|
|
||||||
|
|
||||||
export function assertTimelineDateRangeValid(startDate: Date, endDate: Date): void {
|
export function assertTimelineDateRangeValid(startDate: Date, endDate: Date): void {
|
||||||
if (endDate >= startDate) {
|
if (endDate >= startDate) {
|
||||||
return;
|
return;
|
||||||
@@ -39,37 +36,6 @@ export function validateTimelineAllocationDateRanges(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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: {
|
export function buildTimelineAllocationUpdateAuditChanges(input: {
|
||||||
allocationId: string;
|
allocationId: string;
|
||||||
previousHoursPerDay: number;
|
previousHoursPerDay: number;
|
||||||
@@ -80,7 +46,7 @@ export function buildTimelineAllocationUpdateAuditChanges(input: {
|
|||||||
nextStartDate: Date;
|
nextStartDate: Date;
|
||||||
nextEndDate: Date;
|
nextEndDate: Date;
|
||||||
includeSaturday: boolean;
|
includeSaturday: boolean;
|
||||||
}): Prisma.InputJsonValue {
|
}) {
|
||||||
return {
|
return {
|
||||||
before: {
|
before: {
|
||||||
id: input.allocationId,
|
id: input.allocationId,
|
||||||
@@ -95,7 +61,7 @@ export function buildTimelineAllocationUpdateAuditChanges(input: {
|
|||||||
endDate: input.nextEndDate,
|
endDate: input.nextEndDate,
|
||||||
includeSaturday: input.includeSaturday,
|
includeSaturday: input.includeSaturday,
|
||||||
},
|
},
|
||||||
} as unknown as Prisma.InputJsonValue;
|
} as const;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function buildTimelineAllocationEntryUpdate(input: {
|
export function buildTimelineAllocationEntryUpdate(input: {
|
||||||
@@ -124,16 +90,3 @@ export function buildTimelineAllocationEntryUpdate(input: {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import {
|
|||||||
buildTimelineBatchShiftAuditChanges,
|
buildTimelineBatchShiftAuditChanges,
|
||||||
shiftTimelineAllocationWindow,
|
shiftTimelineAllocationWindow,
|
||||||
type TimelineBatchShiftMode,
|
type TimelineBatchShiftMode,
|
||||||
} from "./timeline-allocation-mutation-support.js";
|
} from "./timeline-allocation-batch-shift-support.js";
|
||||||
|
|
||||||
export async function applyTimelineBatchAllocationShift(input: {
|
export async function applyTimelineBatchAllocationShift(input: {
|
||||||
db: PrismaClient;
|
db: PrismaClient;
|
||||||
|
|||||||
Reference in New Issue
Block a user