refactor(api): share staffing capacity summaries
This commit is contained in:
@@ -3,17 +3,11 @@ import { listAssignmentBookings } from "@capakraken/application";
|
||||
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 { loadResourceDailyAvailabilityContexts } from "../lib/resource-capacity.js";
|
||||
import { fmtEur } from "../lib/format-utils.js";
|
||||
import { planningReadProcedure, requirePermission } from "../trpc.js";
|
||||
import {
|
||||
ACTIVE_STATUSES,
|
||||
averagePerWorkingDay,
|
||||
calculateAllocatedHoursForDay,
|
||||
createLocationLabel,
|
||||
getBaseDayAvailability,
|
||||
@@ -21,6 +15,7 @@ import {
|
||||
round1,
|
||||
toIsoDate,
|
||||
} from "./staffing-shared.js";
|
||||
import { buildResourceCapacitySummary } from "./staffing-capacity-summary.js";
|
||||
|
||||
function fmtDate(value: Date | null | undefined): string | null {
|
||||
return value ? value.toISOString().slice(0, 10) : null;
|
||||
@@ -143,60 +138,13 @@ async function queryStaffingSuggestions(
|
||||
const context = contexts.get(resource.id);
|
||||
const resourceBookings = bookingsByResourceId.get(resource.id) ?? [];
|
||||
const activeBookings = resourceBookings.filter((booking) => ACTIVE_STATUSES.has(booking.status));
|
||||
const baseAvailableHours = calculateEffectiveAvailableHours({
|
||||
availability,
|
||||
periodStart: startDate,
|
||||
periodEnd: endDate,
|
||||
context: undefined,
|
||||
});
|
||||
const totalAvailableHours = calculateEffectiveAvailableHours({
|
||||
const capacity = buildResourceCapacitySummary({
|
||||
availability,
|
||||
periodStart: startDate,
|
||||
periodEnd: endDate,
|
||||
context,
|
||||
bookings: activeBookings,
|
||||
});
|
||||
const baseWorkingDays = countEffectiveWorkingDays({
|
||||
availability,
|
||||
periodStart: startDate,
|
||||
periodEnd: endDate,
|
||||
context: undefined,
|
||||
});
|
||||
const effectiveWorkingDays = countEffectiveWorkingDays({
|
||||
availability,
|
||||
periodStart: startDate,
|
||||
periodEnd: endDate,
|
||||
context,
|
||||
});
|
||||
const allocatedHours = activeBookings.reduce(
|
||||
(sum, booking) =>
|
||||
sum + calculateEffectiveBookedHours({
|
||||
availability,
|
||||
startDate: booking.startDate,
|
||||
endDate: booking.endDate,
|
||||
hoursPerDay: booking.hoursPerDay,
|
||||
periodStart: startDate,
|
||||
periodEnd: endDate,
|
||||
context,
|
||||
}),
|
||||
0,
|
||||
);
|
||||
const holidayDates = [...(context?.holidayDates ?? new Set<string>())].sort();
|
||||
const holidayWorkdayCount = holidayDates.reduce((count, isoDate) => (
|
||||
count + (getBaseDayAvailability(availability, new Date(`${isoDate}T00:00:00.000Z`)) > 0 ? 1 : 0)
|
||||
), 0);
|
||||
const holidayHoursDeduction = holidayDates.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 conflictDays: string[] = [];
|
||||
const conflictDetails: Array<{
|
||||
date: string;
|
||||
@@ -247,11 +195,12 @@ async function queryStaffingSuggestions(
|
||||
cursor.setUTCDate(cursor.getUTCDate() + 1);
|
||||
}
|
||||
|
||||
const remainingHours = Math.max(0, totalAvailableHours - allocatedHours);
|
||||
const remainingHoursPerDay = averagePerWorkingDay(remainingHours, effectiveWorkingDays);
|
||||
const allocatedHours = capacity.bookedHours;
|
||||
const remainingHours = capacity.remainingHours;
|
||||
const remainingHoursPerDay = capacity.remainingHoursPerDay;
|
||||
const utilizationPercent =
|
||||
totalAvailableHours > 0
|
||||
? Math.min(100, (allocatedHours / totalAvailableHours) * 100)
|
||||
capacity.availableHours > 0
|
||||
? Math.min(100, (allocatedHours / capacity.availableHours) * 100)
|
||||
: 0;
|
||||
type SkillRow = { skill: string; category?: string; proficiency: number; isMainSkill?: boolean };
|
||||
let skills = resource.skills as unknown as SkillRow[];
|
||||
@@ -293,19 +242,19 @@ async function queryStaffingSuggestions(
|
||||
},
|
||||
capacity: {
|
||||
requestedHoursPerDay: round1(hoursPerDay),
|
||||
requestedHoursTotal: round1(effectiveWorkingDays * hoursPerDay),
|
||||
baseWorkingDays: round1(baseWorkingDays),
|
||||
effectiveWorkingDays: round1(effectiveWorkingDays),
|
||||
baseAvailableHours: round1(baseAvailableHours),
|
||||
effectiveAvailableHours: round1(totalAvailableHours),
|
||||
bookedHours: round1(allocatedHours),
|
||||
remainingHours: round1(remainingHours),
|
||||
requestedHoursTotal: round1(capacity.workingDays * hoursPerDay),
|
||||
baseWorkingDays: capacity.baseWorkingDays,
|
||||
effectiveWorkingDays: capacity.workingDays,
|
||||
baseAvailableHours: capacity.baseAvailableHours,
|
||||
effectiveAvailableHours: capacity.availableHours,
|
||||
bookedHours: capacity.bookedHours,
|
||||
remainingHours: capacity.remainingHours,
|
||||
remainingHoursPerDay,
|
||||
holidayCount: holidayDates.length,
|
||||
holidayWorkdayCount,
|
||||
holidayHoursDeduction: round1(holidayHoursDeduction),
|
||||
absenceDayEquivalent: round1(absenceDayEquivalent),
|
||||
absenceHoursDeduction: round1(absenceHoursDeduction),
|
||||
holidayCount: capacity.holidaySummary.count,
|
||||
holidayWorkdayCount: capacity.holidaySummary.workdayCount,
|
||||
holidayHoursDeduction: capacity.holidaySummary.hoursDeduction,
|
||||
absenceDayEquivalent: capacity.absenceSummary.dayEquivalent,
|
||||
absenceHoursDeduction: capacity.absenceSummary.hoursDeduction,
|
||||
},
|
||||
conflicts: {
|
||||
count: conflictDays.length,
|
||||
|
||||
Reference in New Issue
Block a user