refactor(api): share staffing capacity summaries
This commit is contained in:
@@ -1,21 +1,11 @@
|
||||
import { PermissionKey, type WeekdayAvailability } from "@capakraken/shared";
|
||||
import { z } from "zod";
|
||||
import { findUniqueOrThrow } from "../db/helpers.js";
|
||||
import {
|
||||
calculateEffectiveAvailableHours,
|
||||
calculateEffectiveBookedHours,
|
||||
countEffectiveWorkingDays,
|
||||
loadResourceDailyAvailabilityContexts,
|
||||
} from "../lib/resource-capacity.js";
|
||||
import { calculateEffectiveBookedHours, loadResourceDailyAvailabilityContexts } from "../lib/resource-capacity.js";
|
||||
import { fmtEur } from "../lib/format-utils.js";
|
||||
import { planningReadProcedure, requirePermission } from "../trpc.js";
|
||||
import {
|
||||
averagePerWorkingDay,
|
||||
createDateRange,
|
||||
getBaseDayAvailability,
|
||||
round1,
|
||||
toIsoDate,
|
||||
} from "./staffing-shared.js";
|
||||
import { createDateRange, round1, toIsoDate } from "./staffing-shared.js";
|
||||
import { buildResourceCapacitySummary } from "./staffing-capacity-summary.js";
|
||||
|
||||
type BestProjectResourceRankingMode =
|
||||
| "lowest_lcr"
|
||||
@@ -194,44 +184,14 @@ async function queryBestProjectResource(
|
||||
const candidates = resources.map((resource) => {
|
||||
const availability = resource.availability as unknown as WeekdayAvailability;
|
||||
const context = contexts.get(resource.id);
|
||||
const baseWorkingDays = countEffectiveWorkingDays({
|
||||
availability,
|
||||
periodStart: input.startDate,
|
||||
periodEnd: input.endDate,
|
||||
context: undefined,
|
||||
});
|
||||
const workingDays = countEffectiveWorkingDays({
|
||||
availability,
|
||||
periodStart: input.startDate,
|
||||
periodEnd: input.endDate,
|
||||
context,
|
||||
});
|
||||
const baseAvailableHours = calculateEffectiveAvailableHours({
|
||||
availability,
|
||||
periodStart: input.startDate,
|
||||
periodEnd: input.endDate,
|
||||
context: undefined,
|
||||
});
|
||||
const availableHours = calculateEffectiveAvailableHours({
|
||||
availability,
|
||||
periodStart: input.startDate,
|
||||
periodEnd: input.endDate,
|
||||
context,
|
||||
});
|
||||
const assignments = assignmentsByResourceId.get(resource.id) ?? [];
|
||||
const bookedHours = assignments.reduce(
|
||||
(sum, assignment) =>
|
||||
sum + calculateEffectiveBookedHours({
|
||||
availability,
|
||||
startDate: assignment.startDate,
|
||||
endDate: assignment.endDate,
|
||||
hoursPerDay: assignment.hoursPerDay,
|
||||
periodStart: input.startDate,
|
||||
periodEnd: input.endDate,
|
||||
context,
|
||||
}),
|
||||
0,
|
||||
);
|
||||
const capacity = buildResourceCapacitySummary({
|
||||
availability,
|
||||
periodStart: input.startDate,
|
||||
periodEnd: input.endDate,
|
||||
context,
|
||||
bookings: assignments,
|
||||
});
|
||||
const projectHours = (assignmentsOnProjectByResourceId.get(resource.id) ?? []).reduce(
|
||||
(sum, assignment) =>
|
||||
sum + calculateEffectiveBookedHours({
|
||||
@@ -245,29 +205,6 @@ async function queryBestProjectResource(
|
||||
}),
|
||||
0,
|
||||
);
|
||||
let excludedCapacityDays = 0;
|
||||
for (const fraction of context?.absenceFractionsByDate.values() ?? []) {
|
||||
excludedCapacityDays += fraction;
|
||||
}
|
||||
const holidayWorkdayCount = [...(context?.holidayDates ?? new Set<string>())].reduce((count, isoDate) => (
|
||||
count + (getBaseDayAvailability(availability, new Date(`${isoDate}T00:00:00.000Z`)) > 0 ? 1 : 0)
|
||||
), 0);
|
||||
const holidayHoursDeduction = [...(context?.holidayDates ?? new Set<string>())].reduce((sum, isoDate) => (
|
||||
sum + getBaseDayAvailability(availability, new Date(`${isoDate}T00:00:00.000Z`))
|
||||
), 0);
|
||||
let absenceDayEquivalent = 0;
|
||||
let absenceHoursDeduction = 0;
|
||||
for (const [isoDate, fraction] of context?.vacationFractionsByDate ?? []) {
|
||||
const dayHours = getBaseDayAvailability(availability, new Date(`${isoDate}T00:00:00.000Z`));
|
||||
if (dayHours <= 0 || context?.holidayDates.has(isoDate)) {
|
||||
continue;
|
||||
}
|
||||
absenceDayEquivalent += fraction;
|
||||
absenceHoursDeduction += dayHours * fraction;
|
||||
}
|
||||
|
||||
const remainingHours = Math.max(0, availableHours - bookedHours);
|
||||
const remainingHoursPerDay = averagePerWorkingDay(remainingHours, workingDays);
|
||||
|
||||
return {
|
||||
id: resource.id,
|
||||
@@ -281,33 +218,27 @@ async function queryBestProjectResource(
|
||||
metroCity: resource.metroCity?.name ?? null,
|
||||
lcrCents: resource.lcrCents ?? null,
|
||||
lcr: resource.lcrCents != null ? fmtEur(resource.lcrCents) : null,
|
||||
baseWorkingDays: round1(baseWorkingDays),
|
||||
workingDays,
|
||||
excludedCapacityDays: round1(excludedCapacityDays),
|
||||
baseAvailableHours: round1(baseAvailableHours),
|
||||
availableHours: round1(availableHours),
|
||||
bookedHours: round1(bookedHours),
|
||||
remainingHours: round1(remainingHours),
|
||||
remainingHoursPerDay,
|
||||
baseWorkingDays: capacity.baseWorkingDays,
|
||||
workingDays: capacity.workingDays,
|
||||
excludedCapacityDays: capacity.excludedCapacityDays,
|
||||
baseAvailableHours: capacity.baseAvailableHours,
|
||||
availableHours: capacity.availableHours,
|
||||
bookedHours: capacity.bookedHours,
|
||||
remainingHours: capacity.remainingHours,
|
||||
remainingHoursPerDay: capacity.remainingHoursPerDay,
|
||||
projectHours: round1(projectHours),
|
||||
assignmentCount: assignments.length,
|
||||
holidaySummary: {
|
||||
count: context?.holidayDates.size ?? 0,
|
||||
workdayCount: holidayWorkdayCount,
|
||||
hoursDeduction: round1(holidayHoursDeduction),
|
||||
holidayDates: [...(context?.holidayDates ?? new Set<string>())].sort(),
|
||||
count: capacity.holidaySummary.count,
|
||||
workdayCount: capacity.holidaySummary.workdayCount,
|
||||
hoursDeduction: capacity.holidaySummary.hoursDeduction,
|
||||
holidayDates: capacity.holidaySummary.holidayDates,
|
||||
},
|
||||
absenceSummary: {
|
||||
dayEquivalent: round1(absenceDayEquivalent),
|
||||
hoursDeduction: round1(absenceHoursDeduction),
|
||||
},
|
||||
capacityBreakdown: {
|
||||
formula: "baseAvailableHours - holidayHoursDeduction - absenceHoursDeduction = availableHours",
|
||||
baseAvailableHours: round1(baseAvailableHours),
|
||||
holidayHoursDeduction: round1(holidayHoursDeduction),
|
||||
absenceHoursDeduction: round1(absenceHoursDeduction),
|
||||
availableHours: round1(availableHours),
|
||||
dayEquivalent: capacity.absenceSummary.dayEquivalent,
|
||||
hoursDeduction: capacity.absenceSummary.hoursDeduction,
|
||||
},
|
||||
capacityBreakdown: capacity.capacityBreakdown,
|
||||
};
|
||||
}).filter((candidate) => candidate.remainingHoursPerDay >= input.minHoursPerDay);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user