From fb09d6487f22d363543d5419476c934d68cb1d83 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hartmut=20N=C3=B6renberg?= Date: Tue, 31 Mar 2026 17:57:28 +0200 Subject: [PATCH] refactor(api): extract timeline batch shift support --- ...ine-allocation-batch-shift-support.test.ts | 60 +++++++++++++++++++ ...meline-allocation-mutation-support.test.ts | 55 ----------------- ...timeline-allocation-batch-shift-support.ts | 47 +++++++++++++++ .../timeline-allocation-mutation-support.ts | 51 +--------------- .../timeline-allocation-shift-support.ts | 2 +- 5 files changed, 110 insertions(+), 105 deletions(-) create mode 100644 packages/api/src/__tests__/timeline-allocation-batch-shift-support.test.ts create mode 100644 packages/api/src/router/timeline-allocation-batch-shift-support.ts diff --git a/packages/api/src/__tests__/timeline-allocation-batch-shift-support.test.ts b/packages/api/src/__tests__/timeline-allocation-batch-shift-support.test.ts new file mode 100644 index 0000000..57173e7 --- /dev/null +++ b/packages/api/src/__tests__/timeline-allocation-batch-shift-support.test.ts @@ -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, + }); + }); +}); diff --git a/packages/api/src/__tests__/timeline-allocation-mutation-support.test.ts b/packages/api/src/__tests__/timeline-allocation-mutation-support.test.ts index dd2b30e..0194e35 100644 --- a/packages/api/src/__tests__/timeline-allocation-mutation-support.test.ts +++ b/packages/api/src/__tests__/timeline-allocation-mutation-support.test.ts @@ -4,8 +4,6 @@ import { assertTimelineDateRangeValid, buildTimelineAllocationEntryUpdate, buildTimelineAllocationMetadata, - buildTimelineBatchShiftAuditChanges, - shiftTimelineAllocationWindow, validateTimelineAllocationDateRanges, } from "../router/timeline-allocation-mutation-support.js"; @@ -36,59 +34,6 @@ describe("timeline allocation mutation support", () => { )).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", () => { expect(() => validateTimelineAllocationDateRanges([ diff --git a/packages/api/src/router/timeline-allocation-batch-shift-support.ts b/packages/api/src/router/timeline-allocation-batch-shift-support.ts new file mode 100644 index 0000000..0975c25 --- /dev/null +++ b/packages/api/src/router/timeline-allocation-batch-shift-support.ts @@ -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; +} diff --git a/packages/api/src/router/timeline-allocation-mutation-support.ts b/packages/api/src/router/timeline-allocation-mutation-support.ts index 4f09ec8..9e866ab 100644 --- a/packages/api/src/router/timeline-allocation-mutation-support.ts +++ b/packages/api/src/router/timeline-allocation-mutation-support.ts @@ -1,8 +1,5 @@ -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; @@ -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: { allocationId: string; previousHoursPerDay: number; @@ -80,7 +46,7 @@ export function buildTimelineAllocationUpdateAuditChanges(input: { nextStartDate: Date; nextEndDate: Date; includeSaturday: boolean; -}): Prisma.InputJsonValue { +}) { return { before: { id: input.allocationId, @@ -95,7 +61,7 @@ export function buildTimelineAllocationUpdateAuditChanges(input: { endDate: input.nextEndDate, includeSaturday: input.includeSaturday, }, - } as unknown as Prisma.InputJsonValue; + } as const; } 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; -} diff --git a/packages/api/src/router/timeline-allocation-shift-support.ts b/packages/api/src/router/timeline-allocation-shift-support.ts index 14a67af..a6b4afb 100644 --- a/packages/api/src/router/timeline-allocation-shift-support.ts +++ b/packages/api/src/router/timeline-allocation-shift-support.ts @@ -5,7 +5,7 @@ import { buildTimelineBatchShiftAuditChanges, shiftTimelineAllocationWindow, type TimelineBatchShiftMode, -} from "./timeline-allocation-mutation-support.js"; +} from "./timeline-allocation-batch-shift-support.js"; export async function applyTimelineBatchAllocationShift(input: { db: PrismaClient;