refactor(api): split resource graph availability
This commit is contained in:
@@ -7,19 +7,9 @@ import {
|
||||
type AssignmentSlice,
|
||||
} from "@capakraken/engine";
|
||||
import type { CalculationRule, SpainScheduleRule, WeekdayAvailability } from "@capakraken/shared";
|
||||
import { VacationStatus } from "@capakraken/db";
|
||||
import type { TRPCContext } from "../trpc.js";
|
||||
import { fmtEur } from "../lib/format-utils.js";
|
||||
import {
|
||||
asHolidayResolverDb,
|
||||
collectHolidayAvailability,
|
||||
getResolvedCalendarHolidays,
|
||||
} from "../lib/holiday-availability.js";
|
||||
import {
|
||||
calculateEffectiveAvailableHours,
|
||||
countEffectiveWorkingDays,
|
||||
loadResourceDailyAvailabilityContexts,
|
||||
} from "../lib/resource-capacity.js";
|
||||
import { loadResourceGraphAvailability } from "./computation-graph-resource-availability.js";
|
||||
import { readResourceBudgetGraph } from "./computation-graph-resource-budget.js";
|
||||
import { buildResourceGraphSnapshot } from "./computation-graph-resource-graph.js";
|
||||
|
||||
@@ -28,21 +18,6 @@ type ResourceGraphInput = {
|
||||
month: string;
|
||||
};
|
||||
|
||||
function getAvailabilityHoursForDate(
|
||||
availability: WeekdayAvailability,
|
||||
date: Date,
|
||||
): number {
|
||||
const dayKey = ["sunday", "monday", "tuesday", "wednesday", "thursday", "friday", "saturday"][date.getUTCDay()] as keyof WeekdayAvailability;
|
||||
return availability[dayKey] ?? 0;
|
||||
}
|
||||
|
||||
function sumAvailabilityHoursForDates(
|
||||
availability: WeekdayAvailability,
|
||||
dates: Date[],
|
||||
): number {
|
||||
return dates.reduce((sum, date) => sum + getAvailabilityHoursForDate(availability, date), 0);
|
||||
}
|
||||
|
||||
export async function readResourceGraphSnapshot(
|
||||
ctx: { db: TRPCContext["db"] },
|
||||
input: ResourceGraphInput,
|
||||
@@ -106,62 +81,34 @@ export async function readResourceGraphSnapshot(
|
||||
},
|
||||
});
|
||||
|
||||
const vacations = await ctx.db.vacation.findMany({
|
||||
where: {
|
||||
resourceId: input.resourceId,
|
||||
status: VacationStatus.APPROVED,
|
||||
startDate: { lte: monthEnd },
|
||||
endDate: { gte: monthStart },
|
||||
},
|
||||
select: { startDate: true, endDate: true, type: true, isHalfDay: true },
|
||||
});
|
||||
const resolvedHolidays = await getResolvedCalendarHolidays(asHolidayResolverDb(ctx.db), {
|
||||
periodStart: monthStart,
|
||||
periodEnd: monthEnd,
|
||||
countryId: resource.countryId,
|
||||
countryCode: resource.country?.code,
|
||||
federalState: resource.federalState,
|
||||
metroCityId: resource.metroCityId,
|
||||
metroCityName: resource.metroCity?.name,
|
||||
});
|
||||
const holidayAvailability = collectHolidayAvailability({
|
||||
vacations,
|
||||
periodStart: monthStart,
|
||||
periodEnd: monthEnd,
|
||||
countryCode: resource.country?.code,
|
||||
federalState: resource.federalState,
|
||||
metroCityName: resource.metroCity?.name,
|
||||
resolvedHolidayStrings: resolvedHolidays.map((holiday) => holiday.date),
|
||||
});
|
||||
const publicHolidayStrings = holidayAvailability.publicHolidayStrings;
|
||||
const absenceDateStrings = holidayAvailability.absenceDateStrings;
|
||||
const absenceDays = holidayAvailability.absenceDays;
|
||||
const halfDayCount = absenceDays.filter((absence) => absence.isHalfDay).length;
|
||||
const vacationDayCount = absenceDays.filter((absence) => absence.type === "VACATION").length;
|
||||
const sickDayCount = absenceDays.filter((absence) => absence.type === "SICK").length;
|
||||
const publicHolidayCount = resolvedHolidays.length;
|
||||
const absenceDayEquivalent = absenceDays.reduce((sum, absence) => {
|
||||
if (absence.type === "PUBLIC_HOLIDAY") {
|
||||
return sum;
|
||||
}
|
||||
return sum + (absence.isHalfDay ? 0.5 : 1);
|
||||
}, 0);
|
||||
|
||||
const contexts = await loadResourceDailyAvailabilityContexts(
|
||||
ctx.db,
|
||||
[{
|
||||
id: resource.id,
|
||||
availability: weeklyAvailability,
|
||||
countryId: resource.countryId,
|
||||
countryCode: resource.country?.code,
|
||||
federalState: resource.federalState,
|
||||
metroCityId: resource.metroCityId,
|
||||
metroCityName: resource.metroCity?.name,
|
||||
}],
|
||||
const {
|
||||
absenceDateStrings,
|
||||
absenceDayEquivalent,
|
||||
absenceDays,
|
||||
absenceHoursDeduction,
|
||||
baseAvailableHours,
|
||||
baseWorkingDays,
|
||||
effectiveAvailableHours,
|
||||
effectiveHoursPerWorkingDay,
|
||||
effectiveWorkingDays,
|
||||
halfDayCount,
|
||||
holidayExamples,
|
||||
holidayScopeBreakdown,
|
||||
holidayScopeSummary,
|
||||
publicHolidayCount,
|
||||
publicHolidayHoursDeduction,
|
||||
publicHolidayStrings,
|
||||
publicHolidayWorkdayCount,
|
||||
resolvedHolidays,
|
||||
sickDayCount,
|
||||
vacationDayCount,
|
||||
} = await loadResourceGraphAvailability({
|
||||
db: ctx.db,
|
||||
resource,
|
||||
weeklyAvailability,
|
||||
monthStart,
|
||||
monthEnd,
|
||||
);
|
||||
const availabilityContext = contexts.get(resource.id);
|
||||
});
|
||||
|
||||
let calcRules: CalculationRule[] = DEFAULT_CALCULATION_RULES;
|
||||
try {
|
||||
@@ -185,60 +132,6 @@ export async function readResourceGraphSnapshot(
|
||||
publicHolidays: publicHolidayStrings,
|
||||
absenceDays: absenceDateStrings,
|
||||
});
|
||||
const baseWorkingDays = countEffectiveWorkingDays({
|
||||
availability: weeklyAvailability,
|
||||
periodStart: monthStart,
|
||||
periodEnd: monthEnd,
|
||||
context: undefined,
|
||||
});
|
||||
const effectiveWorkingDays = countEffectiveWorkingDays({
|
||||
availability: weeklyAvailability,
|
||||
periodStart: monthStart,
|
||||
periodEnd: monthEnd,
|
||||
context: availabilityContext,
|
||||
});
|
||||
const baseAvailableHours = calculateEffectiveAvailableHours({
|
||||
availability: weeklyAvailability,
|
||||
periodStart: monthStart,
|
||||
periodEnd: monthEnd,
|
||||
context: undefined,
|
||||
});
|
||||
const effectiveAvailableHours = calculateEffectiveAvailableHours({
|
||||
availability: weeklyAvailability,
|
||||
periodStart: monthStart,
|
||||
periodEnd: monthEnd,
|
||||
context: availabilityContext,
|
||||
});
|
||||
const publicHolidayDates = resolvedHolidays.map((holiday) => new Date(`${holiday.date}T00:00:00.000Z`));
|
||||
const publicHolidayWorkdayCount = publicHolidayDates.reduce((count, date) => (
|
||||
count + (getAvailabilityHoursForDate(weeklyAvailability, date) > 0 ? 1 : 0)
|
||||
), 0);
|
||||
const publicHolidayHoursDeduction = sumAvailabilityHoursForDates(
|
||||
weeklyAvailability,
|
||||
publicHolidayDates,
|
||||
);
|
||||
const absenceHoursDeduction = absenceDays.reduce((sum, absence) => {
|
||||
if (absence.type === "PUBLIC_HOLIDAY") {
|
||||
return sum;
|
||||
}
|
||||
const baseHours = getAvailabilityHoursForDate(weeklyAvailability, absence.date);
|
||||
return sum + baseHours * (absence.isHalfDay ? 0.5 : 1);
|
||||
}, 0);
|
||||
const effectiveHoursPerWorkingDay = effectiveWorkingDays > 0
|
||||
? effectiveAvailableHours / effectiveWorkingDays
|
||||
: 0;
|
||||
const holidayScopeSummary = [
|
||||
resource.country?.code ?? "—",
|
||||
resource.federalState ?? "—",
|
||||
resource.metroCity?.name ?? "—",
|
||||
].join(" / ");
|
||||
const holidayExamples = resolvedHolidays.length > 0
|
||||
? resolvedHolidays.slice(0, 4).map((holiday) => `${holiday.date} ${holiday.name}`).join(", ")
|
||||
: "none";
|
||||
const holidayScopeBreakdown = resolvedHolidays.reduce<Record<string, number>>((counts, holiday) => {
|
||||
counts[holiday.scope] = (counts[holiday.scope] ?? 0) + 1;
|
||||
return counts;
|
||||
}, {});
|
||||
|
||||
const slices: AssignmentSlice[] = [];
|
||||
const assignmentBreakdown: Array<{
|
||||
|
||||
Reference in New Issue
Block a user