refactor(api): extract allocation router support modules
This commit is contained in:
@@ -0,0 +1,178 @@
|
||||
import { buildSplitAllocationReadModel, loadAllocationEntry } from "@capakraken/application";
|
||||
import { AllocationStatus, CreateDemandRequirementSchema } from "@capakraken/shared";
|
||||
import { TRPCError } from "@trpc/server";
|
||||
import { z } from "zod";
|
||||
import { findUniqueOrThrow } from "../db/helpers.js";
|
||||
import { anonymizeResource, getAnonymizationDirectory } from "../lib/anonymization.js";
|
||||
import { ASSIGNMENT_INCLUDE, type AllocationEntryUpdateInput, type AllocationListFilters, type AssignmentResolutionInput, type CreateDemandDraftInput, DEMAND_INCLUDE, toIsoDate } from "./allocation-shared.js";
|
||||
|
||||
export function toDemandRequirementUpdateInput(input: AllocationEntryUpdateInput) {
|
||||
return {
|
||||
...(input.projectId !== undefined ? { projectId: input.projectId } : {}),
|
||||
...(input.startDate !== undefined ? { startDate: input.startDate } : {}),
|
||||
...(input.endDate !== undefined ? { endDate: input.endDate } : {}),
|
||||
...(input.hoursPerDay !== undefined ? { hoursPerDay: input.hoursPerDay } : {}),
|
||||
...(input.percentage !== undefined ? { percentage: input.percentage } : {}),
|
||||
...(input.role !== undefined ? { role: input.role } : {}),
|
||||
...(input.roleId !== undefined ? { roleId: input.roleId } : {}),
|
||||
...(input.headcount !== undefined ? { headcount: input.headcount } : {}),
|
||||
...(input.budgetCents !== undefined ? { budgetCents: input.budgetCents } : {}),
|
||||
...(input.status !== undefined ? { status: input.status } : {}),
|
||||
...(input.metadata !== undefined ? { metadata: input.metadata } : {}),
|
||||
};
|
||||
}
|
||||
|
||||
export function toAssignmentUpdateInput(input: AllocationEntryUpdateInput) {
|
||||
return {
|
||||
...(input.resourceId !== undefined ? { resourceId: input.resourceId } : {}),
|
||||
...(input.projectId !== undefined ? { projectId: input.projectId } : {}),
|
||||
...(input.startDate !== undefined ? { startDate: input.startDate } : {}),
|
||||
...(input.endDate !== undefined ? { endDate: input.endDate } : {}),
|
||||
...(input.hoursPerDay !== undefined ? { hoursPerDay: input.hoursPerDay } : {}),
|
||||
...(input.percentage !== undefined ? { percentage: input.percentage } : {}),
|
||||
...(input.role !== undefined ? { role: input.role } : {}),
|
||||
...(input.roleId !== undefined ? { roleId: input.roleId } : {}),
|
||||
...(input.status !== undefined ? { status: input.status } : {}),
|
||||
...(input.metadata !== undefined ? { metadata: input.metadata } : {}),
|
||||
};
|
||||
}
|
||||
|
||||
export async function loadAllocationReadModel(
|
||||
db: Pick<import("@capakraken/db").PrismaClient, "demandRequirement" | "assignment" | "systemSettings" | "resource">,
|
||||
input: AllocationListFilters,
|
||||
) {
|
||||
const [demandRequirements, assignments] = await Promise.all([
|
||||
input.resourceId
|
||||
? Promise.resolve([])
|
||||
: db.demandRequirement.findMany({
|
||||
where: {
|
||||
...(input.projectId ? { projectId: input.projectId } : {}),
|
||||
...(input.status ? { status: input.status } : {}),
|
||||
},
|
||||
include: DEMAND_INCLUDE,
|
||||
orderBy: { startDate: "asc" },
|
||||
}),
|
||||
db.assignment.findMany({
|
||||
where: {
|
||||
...(input.projectId ? { projectId: input.projectId } : {}),
|
||||
...(input.resourceId ? { resourceId: input.resourceId } : {}),
|
||||
...(input.status ? { status: input.status } : {}),
|
||||
},
|
||||
include: ASSIGNMENT_INCLUDE,
|
||||
orderBy: { startDate: "asc" },
|
||||
}),
|
||||
]);
|
||||
|
||||
const readModel = buildSplitAllocationReadModel({ demandRequirements, assignments });
|
||||
const directory = await getAnonymizationDirectory(db as import("@capakraken/db").PrismaClient);
|
||||
if (!directory) {
|
||||
return readModel;
|
||||
}
|
||||
|
||||
function anonymizeAllocation<T extends { resource?: { id: string; eid?: string | null; displayName?: string | null; email?: string | null } | null }>(allocation: T): T {
|
||||
if (!allocation.resource) {
|
||||
return allocation;
|
||||
}
|
||||
return { ...allocation, resource: anonymizeResource(allocation.resource, directory) };
|
||||
}
|
||||
|
||||
return {
|
||||
...readModel,
|
||||
allocations: readModel.allocations.map(anonymizeAllocation),
|
||||
demands: readModel.demands.map(anonymizeAllocation),
|
||||
assignments: readModel.assignments.map(anonymizeAllocation),
|
||||
};
|
||||
}
|
||||
|
||||
export async function findAllocationEntryOrNull(
|
||||
db: Pick<import("@capakraken/db").PrismaClient, "demandRequirement" | "assignment">,
|
||||
id: string,
|
||||
) {
|
||||
try {
|
||||
return await loadAllocationEntry(db, id);
|
||||
} catch (error) {
|
||||
if (error instanceof TRPCError && error.code === "NOT_FOUND") {
|
||||
return null;
|
||||
}
|
||||
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
export function buildCreateDemandRequirementInput(input: CreateDemandDraftInput): z.infer<typeof CreateDemandRequirementSchema> {
|
||||
return {
|
||||
projectId: input.projectId,
|
||||
startDate: input.startDate,
|
||||
endDate: input.endDate,
|
||||
hoursPerDay: input.hoursPerDay,
|
||||
percentage: (input.hoursPerDay / 8) * 100,
|
||||
status: AllocationStatus.PROPOSED,
|
||||
headcount: input.headcount ?? 1,
|
||||
budgetCents: input.budgetCents ?? 0,
|
||||
metadata: input.metadata ?? {},
|
||||
...(input.role ? { role: input.role } : {}),
|
||||
...(input.roleId ? { roleId: input.roleId } : {}),
|
||||
};
|
||||
}
|
||||
|
||||
export async function getDemandRequirementByIdOrThrow(
|
||||
db: Pick<import("@capakraken/db").PrismaClient, "demandRequirement">,
|
||||
id: string,
|
||||
) {
|
||||
return findUniqueOrThrow(
|
||||
db.demandRequirement.findUnique({
|
||||
where: { id },
|
||||
include: DEMAND_INCLUDE,
|
||||
}),
|
||||
"Demand requirement",
|
||||
);
|
||||
}
|
||||
|
||||
export async function resolveAssignmentBySelection(
|
||||
db: Pick<import("@capakraken/db").PrismaClient, "assignment">,
|
||||
input: AssignmentResolutionInput,
|
||||
) {
|
||||
if (input.assignmentId) {
|
||||
return findUniqueOrThrow(
|
||||
db.assignment.findUnique({
|
||||
where: { id: input.assignmentId },
|
||||
include: ASSIGNMENT_INCLUDE,
|
||||
}),
|
||||
"Assignment",
|
||||
);
|
||||
}
|
||||
|
||||
if (!input.resourceId || !input.projectId) {
|
||||
throw new TRPCError({
|
||||
code: "BAD_REQUEST",
|
||||
message: "resourceId and projectId are required when assignmentId is not provided",
|
||||
});
|
||||
}
|
||||
|
||||
const assignments = await db.assignment.findMany({
|
||||
where: {
|
||||
resourceId: input.resourceId,
|
||||
projectId: input.projectId,
|
||||
...(input.excludeCancelled ? { status: { not: AllocationStatus.CANCELLED } } : {}),
|
||||
},
|
||||
include: ASSIGNMENT_INCLUDE,
|
||||
orderBy: { startDate: "asc" },
|
||||
});
|
||||
|
||||
const matchingAssignment = assignments
|
||||
.filter((assignment) => {
|
||||
if (input.selectionMode === "WINDOW") {
|
||||
return (!input.startDate || assignment.startDate >= input.startDate)
|
||||
&& (!input.endDate || assignment.endDate <= input.endDate);
|
||||
}
|
||||
|
||||
return !input.startDate || toIsoDate(assignment.startDate) === toIsoDate(input.startDate);
|
||||
})
|
||||
.sort((left, right) => right.startDate.getTime() - left.startDate.getTime())[0] ?? null;
|
||||
|
||||
if (!matchingAssignment) {
|
||||
throw new TRPCError({ code: "NOT_FOUND", message: "Assignment not found" });
|
||||
}
|
||||
|
||||
return matchingAssignment;
|
||||
}
|
||||
Reference in New Issue
Block a user