128 lines
3.5 KiB
TypeScript
128 lines
3.5 KiB
TypeScript
import {
|
|
calculateSAH,
|
|
getMonthRange,
|
|
DEFAULT_CALCULATION_RULES,
|
|
} from "@capakraken/engine";
|
|
import type { CalculationRule, SpainScheduleRule, WeekdayAvailability } from "@capakraken/shared";
|
|
import type { TRPCContext } from "../trpc.js";
|
|
import { loadResourceGraphAvailability } from "./computation-graph-resource-availability.js";
|
|
|
|
export type ResourceGraphInput = {
|
|
resourceId: string;
|
|
month: string;
|
|
};
|
|
|
|
export async function loadResourceGraphSnapshot(
|
|
ctx: { db: TRPCContext["db"] },
|
|
input: ResourceGraphInput,
|
|
) {
|
|
const [year, month] = input.month.split("-").map(Number) as [number, number];
|
|
const { start: monthStart, end: monthEnd } = getMonthRange(year, month);
|
|
|
|
const resource = await ctx.db.resource.findUniqueOrThrow({
|
|
where: { id: input.resourceId },
|
|
select: {
|
|
id: true,
|
|
displayName: true,
|
|
eid: true,
|
|
fte: true,
|
|
lcrCents: true,
|
|
chargeabilityTarget: true,
|
|
countryId: true,
|
|
federalState: true,
|
|
metroCityId: true,
|
|
availability: true,
|
|
country: { select: { id: true, code: true, name: true, dailyWorkingHours: true, scheduleRules: true } },
|
|
metroCity: { select: { id: true, name: true } },
|
|
managementLevelGroup: { select: { id: true, name: true, targetPercentage: true } },
|
|
},
|
|
});
|
|
|
|
const dailyHours = resource.country?.dailyWorkingHours ?? 8;
|
|
const scheduleRules = resource.country?.scheduleRules as SpainScheduleRule | null;
|
|
const targetPct = resource.managementLevelGroup?.targetPercentage ?? (resource.chargeabilityTarget / 100);
|
|
|
|
const availability = resource.availability as WeekdayAvailability | null;
|
|
const weeklyAvailability: WeekdayAvailability = availability ?? {
|
|
monday: dailyHours,
|
|
tuesday: dailyHours,
|
|
wednesday: dailyHours,
|
|
thursday: dailyHours,
|
|
friday: dailyHours,
|
|
saturday: 0,
|
|
sunday: 0,
|
|
};
|
|
|
|
const assignments = await ctx.db.assignment.findMany({
|
|
where: {
|
|
resourceId: input.resourceId,
|
|
startDate: { lte: monthEnd },
|
|
endDate: { gte: monthStart },
|
|
status: { in: ["CONFIRMED", "ACTIVE", "PROPOSED"] },
|
|
},
|
|
select: {
|
|
id: true,
|
|
hoursPerDay: true,
|
|
startDate: true,
|
|
endDate: true,
|
|
dailyCostCents: true,
|
|
status: true,
|
|
project: {
|
|
select: {
|
|
id: true,
|
|
name: true,
|
|
shortCode: true,
|
|
budgetCents: true,
|
|
winProbability: true,
|
|
utilizationCategory: { select: { code: true } },
|
|
},
|
|
},
|
|
},
|
|
});
|
|
|
|
const availabilitySummary = await loadResourceGraphAvailability({
|
|
db: ctx.db,
|
|
resource,
|
|
weeklyAvailability,
|
|
monthStart,
|
|
monthEnd,
|
|
});
|
|
|
|
let calcRules: CalculationRule[] = DEFAULT_CALCULATION_RULES;
|
|
try {
|
|
const dbRules = await ctx.db.calculationRule.findMany({
|
|
where: { isActive: true },
|
|
orderBy: [{ priority: "desc" }],
|
|
});
|
|
if (dbRules.length > 0) {
|
|
calcRules = dbRules as unknown as CalculationRule[];
|
|
}
|
|
} catch {
|
|
// table may not exist yet
|
|
}
|
|
|
|
const sahResult = calculateSAH({
|
|
dailyWorkingHours: dailyHours,
|
|
scheduleRules,
|
|
fte: resource.fte,
|
|
periodStart: monthStart,
|
|
periodEnd: monthEnd,
|
|
publicHolidays: availabilitySummary.publicHolidayStrings,
|
|
absenceDays: availabilitySummary.absenceDateStrings,
|
|
});
|
|
|
|
return {
|
|
assignments,
|
|
calcRules,
|
|
dailyHours,
|
|
monthEnd,
|
|
monthStart,
|
|
resource,
|
|
sahResult,
|
|
scheduleRules,
|
|
targetPct,
|
|
weeklyAvailability,
|
|
...availabilitySummary,
|
|
};
|
|
}
|