c098cedf06
- Add indexes on Resource(blueprintId, roleId), DemandRequirement(roleId),
Assignment(roleId) — commonly filtered FK columns that were missing indexes
- Replace N+1 batch delete pattern (2N queries) with findAllocationEntries()
that does 2 total queries via findMany({ id: { in: ids } })
- Add take/skip pagination with default limit of 500 to listDemands and
listAssignments to prevent unbounded result sets
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
189 lines
6.2 KiB
TypeScript
189 lines
6.2 KiB
TypeScript
import { AllocationStatus } from "@capakraken/shared";
|
|
import { z } from "zod";
|
|
import { findUniqueOrThrow } from "../../db/helpers.js";
|
|
import { anonymizeResource, getAnonymizationDirectory } from "../../lib/anonymization.js";
|
|
import { planningReadProcedure } from "../../trpc.js";
|
|
import { buildResourceAvailabilitySummary, buildResourceAvailabilityView } from "./availability.js";
|
|
import { ASSIGNMENT_INCLUDE, DEMAND_INCLUDE } from "./shared.js";
|
|
import {
|
|
getDemandRequirementByIdOrThrow,
|
|
loadAllocationReadModel,
|
|
resolveAssignmentBySelection,
|
|
} from "./support.js";
|
|
|
|
export const allocationReadProcedures = {
|
|
list: planningReadProcedure
|
|
.input(
|
|
z.object({
|
|
projectId: z.string().optional(),
|
|
resourceId: z.string().optional(),
|
|
status: z.nativeEnum(AllocationStatus).optional(),
|
|
}),
|
|
)
|
|
.query(async ({ ctx, input }) => {
|
|
const readModel = await loadAllocationReadModel(ctx.db, input);
|
|
return readModel.allocations;
|
|
}),
|
|
|
|
listView: planningReadProcedure
|
|
.input(
|
|
z.object({
|
|
projectId: z.string().optional(),
|
|
resourceId: z.string().optional(),
|
|
status: z.nativeEnum(AllocationStatus).optional(),
|
|
}),
|
|
)
|
|
.query(async ({ ctx, input }) => loadAllocationReadModel(ctx.db, input)),
|
|
|
|
listDemands: planningReadProcedure
|
|
.input(
|
|
z.object({
|
|
projectId: z.string().optional(),
|
|
status: z.nativeEnum(AllocationStatus).optional(),
|
|
roleId: z.string().optional(),
|
|
take: z.number().int().min(1).max(1000).default(500),
|
|
skip: z.number().int().min(0).default(0),
|
|
}),
|
|
)
|
|
.query(async ({ ctx, input }) => {
|
|
const demands = await ctx.db.demandRequirement.findMany({
|
|
where: {
|
|
...(input.projectId ? { projectId: input.projectId } : {}),
|
|
...(input.status ? { status: input.status } : {}),
|
|
...(input.roleId ? { roleId: input.roleId } : {}),
|
|
},
|
|
include: DEMAND_INCLUDE,
|
|
orderBy: { startDate: "asc" },
|
|
take: input.take,
|
|
skip: input.skip,
|
|
});
|
|
const directory = await getAnonymizationDirectory(ctx.db);
|
|
if (!directory) {
|
|
return demands;
|
|
}
|
|
return demands.map((demand) => ({
|
|
...demand,
|
|
assignments: demand.assignments.map((assignment) =>
|
|
assignment.resource
|
|
? { ...assignment, resource: anonymizeResource(assignment.resource, directory) }
|
|
: assignment,
|
|
),
|
|
}));
|
|
}),
|
|
|
|
listAssignments: planningReadProcedure
|
|
.input(
|
|
z.object({
|
|
projectId: z.string().optional(),
|
|
resourceId: z.string().optional(),
|
|
status: z.nativeEnum(AllocationStatus).optional(),
|
|
demandRequirementId: z.string().optional(),
|
|
take: z.number().int().min(1).max(1000).default(500),
|
|
skip: z.number().int().min(0).default(0),
|
|
}),
|
|
)
|
|
.query(async ({ ctx, input }) => {
|
|
const assignments = await ctx.db.assignment.findMany({
|
|
where: {
|
|
...(input.projectId ? { projectId: input.projectId } : {}),
|
|
...(input.resourceId ? { resourceId: input.resourceId } : {}),
|
|
...(input.status ? { status: input.status } : {}),
|
|
...(input.demandRequirementId ? { demandRequirementId: input.demandRequirementId } : {}),
|
|
},
|
|
include: ASSIGNMENT_INCLUDE,
|
|
orderBy: { startDate: "asc" },
|
|
take: input.take,
|
|
skip: input.skip,
|
|
});
|
|
const directory = await getAnonymizationDirectory(ctx.db);
|
|
if (!directory) {
|
|
return assignments;
|
|
}
|
|
return assignments.map((assignment) =>
|
|
assignment.resource
|
|
? { ...assignment, resource: anonymizeResource(assignment.resource, directory) }
|
|
: assignment,
|
|
);
|
|
}),
|
|
|
|
getAssignmentById: planningReadProcedure
|
|
.input(z.object({ id: z.string() }))
|
|
.query(async ({ ctx, input }) => {
|
|
const assignment = await findUniqueOrThrow(
|
|
ctx.db.assignment.findUnique({
|
|
where: { id: input.id },
|
|
include: ASSIGNMENT_INCLUDE,
|
|
}),
|
|
"Assignment",
|
|
);
|
|
const directory = await getAnonymizationDirectory(ctx.db);
|
|
if (!directory || !assignment.resource) {
|
|
return assignment;
|
|
}
|
|
return {
|
|
...assignment,
|
|
resource: anonymizeResource(assignment.resource, directory),
|
|
};
|
|
}),
|
|
|
|
resolveAssignment: planningReadProcedure
|
|
.input(
|
|
z.object({
|
|
assignmentId: z.string().optional(),
|
|
resourceId: z.string().optional(),
|
|
projectId: z.string().optional(),
|
|
startDate: z.coerce.date().optional(),
|
|
endDate: z.coerce.date().optional(),
|
|
selectionMode: z.enum(["WINDOW", "EXACT_START"]).default("EXACT_START"),
|
|
excludeCancelled: z.boolean().default(false),
|
|
}),
|
|
)
|
|
.query(async ({ ctx, input }) => resolveAssignmentBySelection(ctx.db, input)),
|
|
|
|
getDemandRequirementById: planningReadProcedure
|
|
.input(z.object({ id: z.string() }))
|
|
.query(async ({ ctx, input }) => getDemandRequirementByIdOrThrow(ctx.db, input.id)),
|
|
|
|
checkResourceAvailability: planningReadProcedure
|
|
.input(
|
|
z.object({
|
|
resourceId: z.string(),
|
|
startDate: z.coerce.date(),
|
|
endDate: z.coerce.date(),
|
|
hoursPerDay: z.number().min(0.5).max(24).default(8),
|
|
}),
|
|
)
|
|
.query(async ({ ctx, input }) => {
|
|
const { vacations: _vacations, ...availability } = await buildResourceAvailabilityView(
|
|
ctx.db,
|
|
input,
|
|
);
|
|
return availability;
|
|
}),
|
|
|
|
getResourceAvailabilityView: planningReadProcedure
|
|
.input(
|
|
z.object({
|
|
resourceId: z.string(),
|
|
startDate: z.coerce.date(),
|
|
endDate: z.coerce.date(),
|
|
hoursPerDay: z.number().min(0.5).max(24).default(8),
|
|
}),
|
|
)
|
|
.query(async ({ ctx, input }) => buildResourceAvailabilityView(ctx.db, input)),
|
|
|
|
getResourceAvailabilitySummary: planningReadProcedure
|
|
.input(
|
|
z.object({
|
|
resourceId: z.string(),
|
|
startDate: z.coerce.date(),
|
|
endDate: z.coerce.date(),
|
|
hoursPerDay: z.number().min(0.5).max(24).default(8),
|
|
}),
|
|
)
|
|
.query(async ({ ctx, input }) => {
|
|
const availability = await buildResourceAvailabilityView(ctx.db, input);
|
|
return buildResourceAvailabilitySummary(availability, input);
|
|
}),
|
|
};
|