refactor(api): extract timeline quick assign support

This commit is contained in:
2026-03-31 17:55:52 +02:00
parent ff57fc24ce
commit e082f1748b
5 changed files with 80 additions and 75 deletions
@@ -5,9 +5,6 @@ import {
buildTimelineAllocationEntryUpdate, buildTimelineAllocationEntryUpdate,
buildTimelineAllocationMetadata, buildTimelineAllocationMetadata,
buildTimelineBatchShiftAuditChanges, buildTimelineBatchShiftAuditChanges,
buildTimelineQuickAssignAssignmentInput,
buildTimelineQuickAssignMetadata,
calculateTimelineAllocationPercentage,
shiftTimelineAllocationWindow, shiftTimelineAllocationWindow,
validateTimelineAllocationDateRanges, validateTimelineAllocationDateRanges,
} from "../router/timeline-allocation-mutation-support.js"; } from "../router/timeline-allocation-mutation-support.js";
@@ -39,43 +36,6 @@ describe("timeline allocation mutation support", () => {
)).toThrowError(TRPCError); )).toThrowError(TRPCError);
}); });
it("rounds allocation percentages and clamps them to 100", () => {
expect(calculateTimelineAllocationPercentage(3.9)).toBe(49);
expect(calculateTimelineAllocationPercentage(9)).toBe(100);
});
it("builds source metadata for quick assign operations", () => {
expect(buildTimelineQuickAssignMetadata("quickAssign")).toEqual({ source: "quickAssign" });
expect(buildTimelineQuickAssignMetadata("batchQuickAssign")).toEqual({ source: "batchQuickAssign" });
});
it("builds assignment create payloads for quick assign flows", () => {
expect(
buildTimelineQuickAssignAssignmentInput({
resourceId: "resource_1",
projectId: "project_1",
startDate: new Date("2026-04-01T00:00:00.000Z"),
endDate: new Date("2026-04-05T00:00:00.000Z"),
hoursPerDay: 6,
role: "Engineer",
roleId: "role_1",
status: "PROPOSED",
source: "quickAssign",
}),
).toEqual({
resourceId: "resource_1",
projectId: "project_1",
startDate: new Date("2026-04-01T00:00:00.000Z"),
endDate: new Date("2026-04-05T00:00:00.000Z"),
hoursPerDay: 6,
percentage: 75,
role: "Engineer",
roleId: "role_1",
status: "PROPOSED",
metadata: { source: "quickAssign" },
});
});
it("shifts and clamps allocation windows for each batch-shift mode", () => { it("shifts and clamps allocation windows for each batch-shift mode", () => {
expect( expect(
shiftTimelineAllocationWindow({ shiftTimelineAllocationWindow({
@@ -0,0 +1,45 @@
import { describe, expect, it } from "vitest";
import {
buildTimelineQuickAssignAssignmentInput,
buildTimelineQuickAssignMetadata,
calculateTimelineAllocationPercentage,
} from "../router/timeline-allocation-quick-assign-support.js";
describe("timeline allocation quick assign support", () => {
it("rounds allocation percentages and clamps them to 100", () => {
expect(calculateTimelineAllocationPercentage(3.9)).toBe(49);
expect(calculateTimelineAllocationPercentage(9)).toBe(100);
});
it("builds source metadata for quick assign operations", () => {
expect(buildTimelineQuickAssignMetadata("quickAssign")).toEqual({ source: "quickAssign" });
expect(buildTimelineQuickAssignMetadata("batchQuickAssign")).toEqual({ source: "batchQuickAssign" });
});
it("builds assignment create payloads for quick assign flows", () => {
expect(
buildTimelineQuickAssignAssignmentInput({
resourceId: "resource_1",
projectId: "project_1",
startDate: new Date("2026-04-01T00:00:00.000Z"),
endDate: new Date("2026-04-05T00:00:00.000Z"),
hoursPerDay: 6,
role: "Engineer",
roleId: "role_1",
status: "PROPOSED",
source: "quickAssign",
}),
).toEqual({
resourceId: "resource_1",
projectId: "project_1",
startDate: new Date("2026-04-01T00:00:00.000Z"),
endDate: new Date("2026-04-05T00:00:00.000Z"),
hoursPerDay: 6,
percentage: 75,
role: "Engineer",
roleId: "role_1",
status: "PROPOSED",
metadata: { source: "quickAssign" },
});
});
});
@@ -1,5 +1,4 @@
import { Prisma } from "@capakraken/db"; import { Prisma } from "@capakraken/db";
import { AllocationStatus } from "@capakraken/shared";
import { TRPCError } from "@trpc/server"; import { TRPCError } from "@trpc/server";
export type TimelineBatchShiftMode = "move" | "resize-start" | "resize-end"; export type TimelineBatchShiftMode = "move" | "resize-start" | "resize-end";
@@ -32,39 +31,6 @@ export function buildTimelineAllocationMetadata(input: {
return { metadata, includeSaturday }; 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 buildTimelineQuickAssignAssignmentInput(input: {
resourceId: string;
projectId: string;
startDate: Date;
endDate: Date;
hoursPerDay: number;
role: string;
roleId?: string | undefined;
status: AllocationStatus;
source: "quickAssign" | "batchQuickAssign";
}) {
return {
resourceId: input.resourceId,
projectId: input.projectId,
startDate: input.startDate,
endDate: input.endDate,
hoursPerDay: input.hoursPerDay,
percentage: calculateTimelineAllocationPercentage(input.hoursPerDay),
role: input.role,
...(input.roleId !== undefined ? { roleId: input.roleId } : {}),
status: input.status,
metadata: buildTimelineQuickAssignMetadata(input.source),
};
}
export function validateTimelineAllocationDateRanges( export function validateTimelineAllocationDateRanges(
ranges: Array<{ startDate: Date; endDate: Date }>, ranges: Array<{ startDate: Date; endDate: Date }>,
): void { ): void {
@@ -9,9 +9,9 @@ import {
} from "../sse/event-bus.js"; } from "../sse/event-bus.js";
import { import {
assertTimelineDateRangeValid, assertTimelineDateRangeValid,
buildTimelineQuickAssignAssignmentInput,
validateTimelineAllocationDateRanges, validateTimelineAllocationDateRanges,
} from "./timeline-allocation-mutation-support.js"; } from "./timeline-allocation-mutation-support.js";
import { buildTimelineQuickAssignAssignmentInput } from "./timeline-allocation-quick-assign-support.js";
import { applyTimelineBatchAllocationShift } from "./timeline-allocation-shift-support.js"; import { applyTimelineBatchAllocationShift } from "./timeline-allocation-shift-support.js";
export async function createTimelineQuickAssignment( export async function createTimelineQuickAssignment(
@@ -0,0 +1,34 @@
import { AllocationStatus } from "@capakraken/shared";
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 buildTimelineQuickAssignAssignmentInput(input: {
resourceId: string;
projectId: string;
startDate: Date;
endDate: Date;
hoursPerDay: number;
role: string;
roleId?: string | undefined;
status: AllocationStatus;
source: "quickAssign" | "batchQuickAssign";
}) {
return {
resourceId: input.resourceId,
projectId: input.projectId,
startDate: input.startDate,
endDate: input.endDate,
hoursPerDay: input.hoursPerDay,
percentage: calculateTimelineAllocationPercentage(input.hoursPerDay),
role: input.role,
...(input.roleId !== undefined ? { roleId: input.roleId } : {}),
status: input.status,
metadata: buildTimelineQuickAssignMetadata(input.source),
};
}