import { calculateAllocation } from "@capakraken/engine/allocation"; import type { WeekdayAvailability } from "@capakraken/shared"; import { calculateEffectiveBookedHours, loadResourceDailyAvailabilityContexts, } from "../lib/resource-capacity.js"; import type { TRPCContext } from "../trpc.js"; export type ScenarioDb = TRPCContext["db"]; export type ScenarioChangeInput = { assignmentId?: string | undefined; resourceId?: string | undefined; roleId?: string | undefined; startDate: Date; endDate: Date; hoursPerDay: number; remove?: boolean | undefined; }; export type ScenarioAvailability = { monday: number; tuesday: number; wednesday: number; thursday: number; friday: number; saturday: number; sunday: number; }; export type ScenarioEntry = { resourceId: string | null; lcrCents: number; hoursPerDay: number; startDate: Date; endDate: Date; availability: ScenarioAvailability; isNew: boolean; }; type ScenarioResourceContextInput = { id: string; availability: unknown; countryId: string | null; country?: { code: string } | null; federalState: string | null; metroCityId: string | null; metroCity?: { name: string } | null; }; type ScenarioSkillsValue = Array<{ skill?: string | null }> | null | undefined; export const DEFAULT_AVAILABILITY: ScenarioAvailability = { monday: 8, tuesday: 8, wednesday: 8, thursday: 8, friday: 8, saturday: 0, sunday: 0, }; export const scenarioRoleSelect = { id: true, name: true, color: true, } as const; export const scenarioResourceSelect = { id: true, displayName: true, eid: true, lcrCents: true, availability: true, chargeabilityTarget: true, skills: true, countryId: true, federalState: true, metroCityId: true, country: { select: { code: true } }, metroCity: { select: { name: true } }, } as const; export const scenarioBaselineProjectSelect = { id: true, name: true, shortCode: true, startDate: true, endDate: true, budgetCents: true, orderType: true, } as const; export const scenarioSimulationProjectSelect = { id: true, name: true, budgetCents: true, orderType: true, startDate: true, endDate: true, } as const; export const scenarioBaselineAssignmentInclude = { resource: { select: scenarioResourceSelect }, roleEntity: { select: scenarioRoleSelect }, } as const; export const scenarioDemandInclude = { roleEntity: { select: scenarioRoleSelect }, } as const; export const scenarioSimulationAssignmentInclude = { resource: { select: scenarioResourceSelect }, } as const; export const scenarioUtilizationAssignmentSelect = { id: true, resourceId: true, projectId: true, hoursPerDay: true, startDate: true, endDate: true, } as const; export function getScenarioAvailability(availability: unknown): ScenarioAvailability { return (availability as ScenarioAvailability) ?? DEFAULT_AVAILABILITY; } export function roundToTenths(value: number): number { return Math.round(value * 10) / 10; } export function collectScenarioSkillSet(skills: ScenarioSkillsValue): Set { const normalized = new Set(); for (const skill of skills ?? []) { if (typeof skill?.skill === "string" && skill.skill.trim().length > 0) { normalized.add(skill.skill.toLowerCase()); } } return normalized; } function toResourceAvailabilityContextInput(resource: ScenarioResourceContextInput) { return { id: resource.id, availability: resource.availability as WeekdayAvailability, countryId: resource.countryId, countryCode: resource.country?.code, federalState: resource.federalState, metroCityId: resource.metroCityId, metroCityName: resource.metroCity?.name, }; } export async function loadScenarioAvailabilityContexts( db: ScenarioDb, resources: ScenarioResourceContextInput[], periodStart: Date, periodEnd: Date, ) { return loadResourceDailyAvailabilityContexts( db, resources.map(toResourceAvailabilityContextInput), periodStart, periodEnd, ); } export function calculateScenarioEntryHours( entry: Pick, options: { periodStart: Date; periodEnd: Date; contexts: Awaited>; }, ): number { if (!entry.resourceId) { return calculateAllocation({ lcrCents: entry.lcrCents, hoursPerDay: entry.hoursPerDay, startDate: entry.startDate, endDate: entry.endDate, availability: entry.availability, }).totalHours; } return calculateEffectiveBookedHours({ availability: entry.availability, startDate: entry.startDate, endDate: entry.endDate, hoursPerDay: entry.hoursPerDay, periodStart: options.periodStart, periodEnd: options.periodEnd, context: options.contexts.get(entry.resourceId), }); }