refactor(api): extract timeline allocation update support
This commit is contained in:
@@ -2,30 +2,10 @@ import { TRPCError } from "@trpc/server";
|
|||||||
import { describe, expect, it } from "vitest";
|
import { describe, expect, it } from "vitest";
|
||||||
import {
|
import {
|
||||||
assertTimelineDateRangeValid,
|
assertTimelineDateRangeValid,
|
||||||
buildTimelineAllocationEntryUpdate,
|
|
||||||
buildTimelineAllocationMetadata,
|
|
||||||
validateTimelineAllocationDateRanges,
|
validateTimelineAllocationDateRanges,
|
||||||
} from "../router/timeline-allocation-mutation-support.js";
|
} from "../router/timeline-allocation-mutation-support.js";
|
||||||
|
|
||||||
describe("timeline allocation mutation support", () => {
|
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", () => {
|
it("rejects inverted date ranges", () => {
|
||||||
expect(() =>
|
expect(() =>
|
||||||
assertTimelineDateRangeValid(
|
assertTimelineDateRangeValid(
|
||||||
@@ -47,33 +27,4 @@ describe("timeline allocation mutation support", () => {
|
|||||||
},
|
},
|
||||||
])).toThrowError(TRPCError);
|
])).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 { PrismaClient } from "@capakraken/db";
|
||||||
import type { RecurrencePattern, WeekdayAvailability } from "@capakraken/shared";
|
import type { RecurrencePattern, WeekdayAvailability } from "@capakraken/shared";
|
||||||
import { TRPCError } from "@trpc/server";
|
import { TRPCError } from "@trpc/server";
|
||||||
|
import { assertTimelineDateRangeValid } from "./timeline-allocation-mutation-support.js";
|
||||||
import {
|
import {
|
||||||
assertTimelineDateRangeValid,
|
|
||||||
buildTimelineAllocationEntryUpdate,
|
buildTimelineAllocationEntryUpdate,
|
||||||
buildTimelineAllocationMetadata,
|
buildTimelineAllocationMetadata,
|
||||||
buildTimelineAllocationUpdateAuditChanges,
|
buildTimelineAllocationUpdateAuditChanges,
|
||||||
} from "./timeline-allocation-mutation-support.js";
|
} from "./timeline-allocation-update-support.js";
|
||||||
import { calculateTimelineAllocationDailyCost } from "./timeline-cost-support.js";
|
import { calculateTimelineAllocationDailyCost } from "./timeline-cost-support.js";
|
||||||
|
|
||||||
export async function applyTimelineInlineAllocationUpdate(input: {
|
export async function applyTimelineInlineAllocationUpdate(input: {
|
||||||
|
|||||||
@@ -35,58 +35,3 @@ export function validateTimelineAllocationDateRanges(
|
|||||||
assertTimelineDateRangeValid(range.startDate, range.endDate);
|
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 } : {}),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user