import { calculateShoringRatio, type ShoringAssignment } from "@capakraken/engine/allocation"; import type { WeekdayAvailability } from "@capakraken/shared"; import { TRPCError } from "@trpc/server"; import { calculateEffectiveBookedHours, loadResourceDailyAvailabilityContexts, } from "../lib/resource-capacity.js"; import type { TRPCContext } from "../trpc.js"; type ProjectShoringRatioProjectRecord = { id: string; name: string; shoringThreshold: number | null; onshoreCountryCode: string | null; }; type ProjectShoringRatioAssignmentRecord = { resourceId: string; startDate: Date; endDate: Date; hoursPerDay: number; resource: { id: string; countryId: string | null; federalState: string | null; metroCityId: string | null; availability: unknown; country: { id: string; code: string } | null; metroCity: { id: string; name: string } | null; }; }; export async function getProjectShoringRatio( db: TRPCContext["db"], projectId: string, ) { const project = await db.project.findUnique({ where: { id: projectId }, select: { id: true, name: true, shoringThreshold: true, onshoreCountryCode: true, }, }) as ProjectShoringRatioProjectRecord | null; if (!project) { throw new TRPCError({ code: "NOT_FOUND", message: "Project not found" }); } const assignments = await db.assignment.findMany({ where: { projectId, status: { not: "CANCELLED" } }, include: { resource: { include: { country: { select: { id: true, code: true } }, metroCity: { select: { id: true, name: true } }, }, }, }, }) as ProjectShoringRatioAssignmentRecord[]; const periodStart = assignments.length > 0 ? new Date(Math.min(...assignments.map((assignment) => assignment.startDate.getTime()))) : new Date(); const periodEnd = assignments.length > 0 ? new Date(Math.max(...assignments.map((assignment) => assignment.endDate.getTime()))) : new Date(); const contexts = await loadResourceDailyAvailabilityContexts( db, assignments.map((assignment) => ({ id: assignment.resource.id, availability: assignment.resource.availability as WeekdayAvailability, countryId: assignment.resource.country?.id ?? assignment.resource.countryId, countryCode: assignment.resource.country?.code, federalState: assignment.resource.federalState, metroCityId: assignment.resource.metroCity?.id ?? assignment.resource.metroCityId, metroCityName: assignment.resource.metroCity?.name, })), periodStart, periodEnd, ); const mapped: ShoringAssignment[] = assignments.map((assignment) => { const workingDays = assignment.hoursPerDay > 0 ? calculateEffectiveBookedHours({ availability: assignment.resource.availability as WeekdayAvailability, startDate: assignment.startDate, endDate: assignment.endDate, hoursPerDay: assignment.hoursPerDay, periodStart, periodEnd, context: contexts.get(assignment.resourceId ?? assignment.resource.id), }) / assignment.hoursPerDay : 0; return { resourceId: assignment.resourceId, countryCode: assignment.resource.country?.code ?? null, hoursPerDay: assignment.hoursPerDay, workingDays: Math.max(0, workingDays), }; }); return calculateShoringRatio( mapped, project.shoringThreshold ?? 55, project.onshoreCountryCode ?? "DE", ); }