186 lines
6.1 KiB
TypeScript
186 lines
6.1 KiB
TypeScript
import { VacationStatus } from "@capakraken/db";
|
|
import type { WeekdayAvailability } from "@capakraken/shared";
|
|
import {
|
|
asHolidayResolverDb,
|
|
collectHolidayAvailability,
|
|
getResolvedCalendarHolidays,
|
|
} from "../lib/holiday-availability.js";
|
|
import {
|
|
calculateEffectiveAvailableHours,
|
|
countEffectiveWorkingDays,
|
|
loadResourceDailyAvailabilityContexts,
|
|
} from "../lib/resource-capacity.js";
|
|
|
|
type ResourceLocation = {
|
|
countryId: string | null;
|
|
federalState: string | null;
|
|
metroCityId: string | null;
|
|
country?: { code?: string | null; name?: string | null } | null;
|
|
metroCity?: { name?: string | null } | null;
|
|
};
|
|
|
|
type ResourceAvailabilityInput = {
|
|
db: any;
|
|
resource: ResourceLocation & {
|
|
id: string;
|
|
};
|
|
weeklyAvailability: WeekdayAvailability;
|
|
monthStart: Date;
|
|
monthEnd: Date;
|
|
};
|
|
|
|
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 loadResourceGraphAvailability(input: ResourceAvailabilityInput) {
|
|
const { db, resource, weeklyAvailability, monthStart, monthEnd } = input;
|
|
|
|
const vacations = await db.vacation.findMany({
|
|
where: {
|
|
resourceId: resource.id,
|
|
status: VacationStatus.APPROVED,
|
|
startDate: { lte: monthEnd },
|
|
endDate: { gte: monthStart },
|
|
},
|
|
select: { startDate: true, endDate: true, type: true, isHalfDay: true },
|
|
});
|
|
const resolvedHolidays = await getResolvedCalendarHolidays(asHolidayResolverDb(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(
|
|
db,
|
|
[{
|
|
id: resource.id,
|
|
availability: weeklyAvailability,
|
|
countryId: resource.countryId,
|
|
countryCode: resource.country?.code,
|
|
federalState: resource.federalState,
|
|
metroCityId: resource.metroCityId,
|
|
metroCityName: resource.metroCity?.name,
|
|
}],
|
|
monthStart,
|
|
monthEnd,
|
|
);
|
|
const availabilityContext = contexts.get(resource.id);
|
|
|
|
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;
|
|
}, {});
|
|
|
|
return {
|
|
absenceDateStrings,
|
|
absenceDayEquivalent,
|
|
absenceDays,
|
|
absenceHoursDeduction,
|
|
availabilityContext,
|
|
baseAvailableHours,
|
|
baseWorkingDays,
|
|
effectiveAvailableHours,
|
|
effectiveHoursPerWorkingDay,
|
|
effectiveWorkingDays,
|
|
halfDayCount,
|
|
holidayExamples,
|
|
holidayScopeBreakdown,
|
|
holidayScopeSummary,
|
|
publicHolidayCount,
|
|
publicHolidayHoursDeduction,
|
|
publicHolidayStrings,
|
|
publicHolidayWorkdayCount,
|
|
resolvedHolidays,
|
|
sickDayCount,
|
|
vacationDayCount,
|
|
};
|
|
}
|