refactor(api): extract timeline allocation update support

This commit is contained in:
2026-03-31 17:59:16 +02:00
parent fb09d6487f
commit d0699c90fe
5 changed files with 129 additions and 106 deletions
@@ -2,30 +2,10 @@ import { TRPCError } from "@trpc/server";
import { describe, expect, it } from "vitest";
import {
assertTimelineDateRangeValid,
buildTimelineAllocationEntryUpdate,
buildTimelineAllocationMetadata,
validateTimelineAllocationDateRanges,
} from "../router/timeline-allocation-mutation-support.js";
describe("timeline allocation mutation support", () => {
it("preserves existing metadata while updating includeSaturday", () => {
const result = buildTimelineAllocationMetadata({
existingMetadata: {
recurrence: { frequency: "weekly", interval: 2 },
includeSaturday: false,
},
includeSaturday: true,
});
expect(result).toEqual({
metadata: {
recurrence: { frequency: "weekly", interval: 2 },
includeSaturday: true,
},
includeSaturday: true,
});
});
it("rejects inverted date ranges", () => {
expect(() =>
assertTimelineDateRangeValid(
@@ -47,33 +27,4 @@ describe("timeline allocation mutation support", () => {
},
])).toThrowError(TRPCError);
});
it("builds shared update payloads for demand and assignment changes", () => {
expect(
buildTimelineAllocationEntryUpdate({
hoursPerDay: 7.5,
startDate: new Date("2026-04-01T00:00:00.000Z"),
endDate: new Date("2026-04-10T00:00:00.000Z"),
metadata: { includeSaturday: true },
dailyCostCents: 48000,
role: "Architect",
}),
).toEqual({
demandRequirementUpdate: {
hoursPerDay: 7.5,
startDate: new Date("2026-04-01T00:00:00.000Z"),
endDate: new Date("2026-04-10T00:00:00.000Z"),
metadata: { includeSaturday: true },
role: "Architect",
},
assignmentUpdate: {
hoursPerDay: 7.5,
startDate: new Date("2026-04-01T00:00:00.000Z"),
endDate: new Date("2026-04-10T00:00:00.000Z"),
dailyCostCents: 48000,
metadata: { includeSaturday: true },
role: "Architect",
},
});
});
});
@@ -0,0 +1,54 @@
import { describe, expect, it } from "vitest";
import {
buildTimelineAllocationEntryUpdate,
buildTimelineAllocationMetadata,
} from "../router/timeline-allocation-update-support.js";
describe("timeline allocation update support", () => {
it("preserves existing metadata while updating includeSaturday", () => {
const result = buildTimelineAllocationMetadata({
existingMetadata: {
recurrence: { frequency: "weekly", interval: 2 },
includeSaturday: false,
},
includeSaturday: true,
});
expect(result).toEqual({
metadata: {
recurrence: { frequency: "weekly", interval: 2 },
includeSaturday: true,
},
includeSaturday: true,
});
});
it("builds shared update payloads for demand and assignment changes", () => {
expect(
buildTimelineAllocationEntryUpdate({
hoursPerDay: 7.5,
startDate: new Date("2026-04-01T00:00:00.000Z"),
endDate: new Date("2026-04-10T00:00:00.000Z"),
metadata: { includeSaturday: true },
dailyCostCents: 48000,
role: "Architect",
}),
).toEqual({
demandRequirementUpdate: {
hoursPerDay: 7.5,
startDate: new Date("2026-04-01T00:00:00.000Z"),
endDate: new Date("2026-04-10T00:00:00.000Z"),
metadata: { includeSaturday: true },
role: "Architect",
},
assignmentUpdate: {
hoursPerDay: 7.5,
startDate: new Date("2026-04-01T00:00:00.000Z"),
endDate: new Date("2026-04-10T00:00:00.000Z"),
dailyCostCents: 48000,
metadata: { includeSaturday: true },
role: "Architect",
},
});
});
});
@@ -2,12 +2,12 @@ import { loadAllocationEntry, updateAllocationEntry } from "@capakraken/applicat
import type { PrismaClient } from "@capakraken/db";
import type { RecurrencePattern, WeekdayAvailability } from "@capakraken/shared";
import { TRPCError } from "@trpc/server";
import { assertTimelineDateRangeValid } from "./timeline-allocation-mutation-support.js";
import {
assertTimelineDateRangeValid,
buildTimelineAllocationEntryUpdate,
buildTimelineAllocationMetadata,
buildTimelineAllocationUpdateAuditChanges,
} from "./timeline-allocation-mutation-support.js";
} from "./timeline-allocation-update-support.js";
import { calculateTimelineAllocationDailyCost } from "./timeline-cost-support.js";
export async function applyTimelineInlineAllocationUpdate(input: {
@@ -35,58 +35,3 @@ export function validateTimelineAllocationDateRanges(
assertTimelineDateRangeValid(range.startDate, range.endDate);
}
}
export function buildTimelineAllocationUpdateAuditChanges(input: {
allocationId: string;
previousHoursPerDay: number;
previousStartDate: Date;
previousEndDate: Date;
nextAllocationId: string;
nextHoursPerDay: number;
nextStartDate: Date;
nextEndDate: Date;
includeSaturday: boolean;
}) {
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 const;
}
export function buildTimelineAllocationEntryUpdate(input: {
hoursPerDay: number;
startDate: Date;
endDate: Date;
metadata: Record<string, unknown>;
dailyCostCents: number;
role?: string | undefined;
}) {
return {
demandRequirementUpdate: {
hoursPerDay: input.hoursPerDay,
startDate: input.startDate,
endDate: input.endDate,
metadata: input.metadata,
...(input.role !== undefined ? { role: input.role } : {}),
},
assignmentUpdate: {
hoursPerDay: input.hoursPerDay,
startDate: input.startDate,
endDate: input.endDate,
dailyCostCents: input.dailyCostCents,
metadata: input.metadata,
...(input.role !== undefined ? { role: input.role } : {}),
},
};
}
@@ -0,0 +1,73 @@
import { Prisma } from "@capakraken/db";
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 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 buildTimelineAllocationEntryUpdate(input: {
hoursPerDay: number;
startDate: Date;
endDate: Date;
metadata: Record<string, unknown>;
dailyCostCents: number;
role?: string | undefined;
}) {
return {
demandRequirementUpdate: {
hoursPerDay: input.hoursPerDay,
startDate: input.startDate,
endDate: input.endDate,
metadata: input.metadata,
...(input.role !== undefined ? { role: input.role } : {}),
},
assignmentUpdate: {
hoursPerDay: input.hoursPerDay,
startDate: input.startDate,
endDate: input.endDate,
dailyCostCents: input.dailyCostCents,
metadata: input.metadata,
...(input.role !== undefined ? { role: input.role } : {}),
},
};
}