refactor(api): share staffing capacity summaries
This commit is contained in:
@@ -1,21 +1,11 @@
|
|||||||
import { PermissionKey, type WeekdayAvailability } from "@capakraken/shared";
|
import { PermissionKey, type WeekdayAvailability } from "@capakraken/shared";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { findUniqueOrThrow } from "../db/helpers.js";
|
import { findUniqueOrThrow } from "../db/helpers.js";
|
||||||
import {
|
import { calculateEffectiveBookedHours, loadResourceDailyAvailabilityContexts } from "../lib/resource-capacity.js";
|
||||||
calculateEffectiveAvailableHours,
|
|
||||||
calculateEffectiveBookedHours,
|
|
||||||
countEffectiveWorkingDays,
|
|
||||||
loadResourceDailyAvailabilityContexts,
|
|
||||||
} from "../lib/resource-capacity.js";
|
|
||||||
import { fmtEur } from "../lib/format-utils.js";
|
import { fmtEur } from "../lib/format-utils.js";
|
||||||
import { planningReadProcedure, requirePermission } from "../trpc.js";
|
import { planningReadProcedure, requirePermission } from "../trpc.js";
|
||||||
import {
|
import { createDateRange, round1, toIsoDate } from "./staffing-shared.js";
|
||||||
averagePerWorkingDay,
|
import { buildResourceCapacitySummary } from "./staffing-capacity-summary.js";
|
||||||
createDateRange,
|
|
||||||
getBaseDayAvailability,
|
|
||||||
round1,
|
|
||||||
toIsoDate,
|
|
||||||
} from "./staffing-shared.js";
|
|
||||||
|
|
||||||
type BestProjectResourceRankingMode =
|
type BestProjectResourceRankingMode =
|
||||||
| "lowest_lcr"
|
| "lowest_lcr"
|
||||||
@@ -194,44 +184,14 @@ async function queryBestProjectResource(
|
|||||||
const candidates = resources.map((resource) => {
|
const candidates = resources.map((resource) => {
|
||||||
const availability = resource.availability as unknown as WeekdayAvailability;
|
const availability = resource.availability as unknown as WeekdayAvailability;
|
||||||
const context = contexts.get(resource.id);
|
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 assignments = assignmentsByResourceId.get(resource.id) ?? [];
|
||||||
const bookedHours = assignments.reduce(
|
const capacity = buildResourceCapacitySummary({
|
||||||
(sum, assignment) =>
|
availability,
|
||||||
sum + calculateEffectiveBookedHours({
|
periodStart: input.startDate,
|
||||||
availability,
|
periodEnd: input.endDate,
|
||||||
startDate: assignment.startDate,
|
context,
|
||||||
endDate: assignment.endDate,
|
bookings: assignments,
|
||||||
hoursPerDay: assignment.hoursPerDay,
|
});
|
||||||
periodStart: input.startDate,
|
|
||||||
periodEnd: input.endDate,
|
|
||||||
context,
|
|
||||||
}),
|
|
||||||
0,
|
|
||||||
);
|
|
||||||
const projectHours = (assignmentsOnProjectByResourceId.get(resource.id) ?? []).reduce(
|
const projectHours = (assignmentsOnProjectByResourceId.get(resource.id) ?? []).reduce(
|
||||||
(sum, assignment) =>
|
(sum, assignment) =>
|
||||||
sum + calculateEffectiveBookedHours({
|
sum + calculateEffectiveBookedHours({
|
||||||
@@ -245,29 +205,6 @@ async function queryBestProjectResource(
|
|||||||
}),
|
}),
|
||||||
0,
|
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 {
|
return {
|
||||||
id: resource.id,
|
id: resource.id,
|
||||||
@@ -281,33 +218,27 @@ async function queryBestProjectResource(
|
|||||||
metroCity: resource.metroCity?.name ?? null,
|
metroCity: resource.metroCity?.name ?? null,
|
||||||
lcrCents: resource.lcrCents ?? null,
|
lcrCents: resource.lcrCents ?? null,
|
||||||
lcr: resource.lcrCents != null ? fmtEur(resource.lcrCents) : null,
|
lcr: resource.lcrCents != null ? fmtEur(resource.lcrCents) : null,
|
||||||
baseWorkingDays: round1(baseWorkingDays),
|
baseWorkingDays: capacity.baseWorkingDays,
|
||||||
workingDays,
|
workingDays: capacity.workingDays,
|
||||||
excludedCapacityDays: round1(excludedCapacityDays),
|
excludedCapacityDays: capacity.excludedCapacityDays,
|
||||||
baseAvailableHours: round1(baseAvailableHours),
|
baseAvailableHours: capacity.baseAvailableHours,
|
||||||
availableHours: round1(availableHours),
|
availableHours: capacity.availableHours,
|
||||||
bookedHours: round1(bookedHours),
|
bookedHours: capacity.bookedHours,
|
||||||
remainingHours: round1(remainingHours),
|
remainingHours: capacity.remainingHours,
|
||||||
remainingHoursPerDay,
|
remainingHoursPerDay: capacity.remainingHoursPerDay,
|
||||||
projectHours: round1(projectHours),
|
projectHours: round1(projectHours),
|
||||||
assignmentCount: assignments.length,
|
assignmentCount: assignments.length,
|
||||||
holidaySummary: {
|
holidaySummary: {
|
||||||
count: context?.holidayDates.size ?? 0,
|
count: capacity.holidaySummary.count,
|
||||||
workdayCount: holidayWorkdayCount,
|
workdayCount: capacity.holidaySummary.workdayCount,
|
||||||
hoursDeduction: round1(holidayHoursDeduction),
|
hoursDeduction: capacity.holidaySummary.hoursDeduction,
|
||||||
holidayDates: [...(context?.holidayDates ?? new Set<string>())].sort(),
|
holidayDates: capacity.holidaySummary.holidayDates,
|
||||||
},
|
},
|
||||||
absenceSummary: {
|
absenceSummary: {
|
||||||
dayEquivalent: round1(absenceDayEquivalent),
|
dayEquivalent: capacity.absenceSummary.dayEquivalent,
|
||||||
hoursDeduction: round1(absenceHoursDeduction),
|
hoursDeduction: capacity.absenceSummary.hoursDeduction,
|
||||||
},
|
|
||||||
capacityBreakdown: {
|
|
||||||
formula: "baseAvailableHours - holidayHoursDeduction - absenceHoursDeduction = availableHours",
|
|
||||||
baseAvailableHours: round1(baseAvailableHours),
|
|
||||||
holidayHoursDeduction: round1(holidayHoursDeduction),
|
|
||||||
absenceHoursDeduction: round1(absenceHoursDeduction),
|
|
||||||
availableHours: round1(availableHours),
|
|
||||||
},
|
},
|
||||||
|
capacityBreakdown: capacity.capacityBreakdown,
|
||||||
};
|
};
|
||||||
}).filter((candidate) => candidate.remainingHoursPerDay >= input.minHoursPerDay);
|
}).filter((candidate) => candidate.remainingHoursPerDay >= input.minHoursPerDay);
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,131 @@
|
|||||||
|
import { type WeekdayAvailability } from "@capakraken/shared";
|
||||||
|
import {
|
||||||
|
calculateEffectiveAvailableHours,
|
||||||
|
calculateEffectiveBookedHours,
|
||||||
|
countEffectiveWorkingDays,
|
||||||
|
type ResourceDailyAvailabilityContext,
|
||||||
|
} from "../lib/resource-capacity.js";
|
||||||
|
import { averagePerWorkingDay, getBaseDayAvailability, round1 } from "./staffing-shared.js";
|
||||||
|
|
||||||
|
type CapacityBooking = {
|
||||||
|
startDate: Date;
|
||||||
|
endDate: Date;
|
||||||
|
hoursPerDay: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function summarizeHolidayAndAbsenceCapacity(input: {
|
||||||
|
availability: WeekdayAvailability;
|
||||||
|
context: ResourceDailyAvailabilityContext | undefined;
|
||||||
|
}) {
|
||||||
|
const holidayDates = [...(input.context?.holidayDates ?? new Set<string>())].sort();
|
||||||
|
const holidayWorkdayCount = holidayDates.reduce((count, isoDate) => (
|
||||||
|
count + (getBaseDayAvailability(input.availability, new Date(`${isoDate}T00:00:00.000Z`)) > 0 ? 1 : 0)
|
||||||
|
), 0);
|
||||||
|
const holidayHoursDeduction = holidayDates.reduce((sum, isoDate) => (
|
||||||
|
sum + getBaseDayAvailability(input.availability, new Date(`${isoDate}T00:00:00.000Z`))
|
||||||
|
), 0);
|
||||||
|
|
||||||
|
let excludedCapacityDays = 0;
|
||||||
|
for (const fraction of input.context?.absenceFractionsByDate.values() ?? []) {
|
||||||
|
excludedCapacityDays += fraction;
|
||||||
|
}
|
||||||
|
|
||||||
|
let absenceDayEquivalent = 0;
|
||||||
|
let absenceHoursDeduction = 0;
|
||||||
|
for (const [isoDate, fraction] of input.context?.vacationFractionsByDate ?? []) {
|
||||||
|
const dayHours = getBaseDayAvailability(input.availability, new Date(`${isoDate}T00:00:00.000Z`));
|
||||||
|
if (dayHours <= 0 || input.context?.holidayDates.has(isoDate)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
absenceDayEquivalent += fraction;
|
||||||
|
absenceHoursDeduction += dayHours * fraction;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
excludedCapacityDays: round1(excludedCapacityDays),
|
||||||
|
holidayDates,
|
||||||
|
holidayWorkdayCount,
|
||||||
|
holidayHoursDeduction: round1(holidayHoursDeduction),
|
||||||
|
absenceDayEquivalent: round1(absenceDayEquivalent),
|
||||||
|
absenceHoursDeduction: round1(absenceHoursDeduction),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function buildResourceCapacitySummary(input: {
|
||||||
|
availability: WeekdayAvailability;
|
||||||
|
periodStart: Date;
|
||||||
|
periodEnd: Date;
|
||||||
|
context: ResourceDailyAvailabilityContext | undefined;
|
||||||
|
bookings?: CapacityBooking[] | undefined;
|
||||||
|
}) {
|
||||||
|
const baseWorkingDays = countEffectiveWorkingDays({
|
||||||
|
availability: input.availability,
|
||||||
|
periodStart: input.periodStart,
|
||||||
|
periodEnd: input.periodEnd,
|
||||||
|
context: undefined,
|
||||||
|
});
|
||||||
|
const workingDays = countEffectiveWorkingDays({
|
||||||
|
availability: input.availability,
|
||||||
|
periodStart: input.periodStart,
|
||||||
|
periodEnd: input.periodEnd,
|
||||||
|
context: input.context,
|
||||||
|
});
|
||||||
|
const baseAvailableHours = calculateEffectiveAvailableHours({
|
||||||
|
availability: input.availability,
|
||||||
|
periodStart: input.periodStart,
|
||||||
|
periodEnd: input.periodEnd,
|
||||||
|
context: undefined,
|
||||||
|
});
|
||||||
|
const availableHours = calculateEffectiveAvailableHours({
|
||||||
|
availability: input.availability,
|
||||||
|
periodStart: input.periodStart,
|
||||||
|
periodEnd: input.periodEnd,
|
||||||
|
context: input.context,
|
||||||
|
});
|
||||||
|
const bookedHours = (input.bookings ?? []).reduce(
|
||||||
|
(sum, booking) =>
|
||||||
|
sum + calculateEffectiveBookedHours({
|
||||||
|
availability: input.availability,
|
||||||
|
startDate: booking.startDate,
|
||||||
|
endDate: booking.endDate,
|
||||||
|
hoursPerDay: booking.hoursPerDay,
|
||||||
|
periodStart: input.periodStart,
|
||||||
|
periodEnd: input.periodEnd,
|
||||||
|
context: input.context,
|
||||||
|
}),
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
const remainingHours = Math.max(0, availableHours - bookedHours);
|
||||||
|
const holidayAndAbsence = summarizeHolidayAndAbsenceCapacity({
|
||||||
|
availability: input.availability,
|
||||||
|
context: input.context,
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
baseWorkingDays: round1(baseWorkingDays),
|
||||||
|
workingDays: round1(workingDays),
|
||||||
|
baseAvailableHours: round1(baseAvailableHours),
|
||||||
|
availableHours: round1(availableHours),
|
||||||
|
bookedHours: round1(bookedHours),
|
||||||
|
remainingHours: round1(remainingHours),
|
||||||
|
remainingHoursPerDay: averagePerWorkingDay(remainingHours, workingDays),
|
||||||
|
holidaySummary: {
|
||||||
|
count: holidayAndAbsence.holidayDates.length,
|
||||||
|
workdayCount: holidayAndAbsence.holidayWorkdayCount,
|
||||||
|
hoursDeduction: holidayAndAbsence.holidayHoursDeduction,
|
||||||
|
holidayDates: holidayAndAbsence.holidayDates,
|
||||||
|
},
|
||||||
|
absenceSummary: {
|
||||||
|
dayEquivalent: holidayAndAbsence.absenceDayEquivalent,
|
||||||
|
hoursDeduction: holidayAndAbsence.absenceHoursDeduction,
|
||||||
|
},
|
||||||
|
excludedCapacityDays: holidayAndAbsence.excludedCapacityDays,
|
||||||
|
capacityBreakdown: {
|
||||||
|
formula: "baseAvailableHours - holidayHoursDeduction - absenceHoursDeduction = availableHours",
|
||||||
|
baseAvailableHours: round1(baseAvailableHours),
|
||||||
|
holidayHoursDeduction: holidayAndAbsence.holidayHoursDeduction,
|
||||||
|
absenceHoursDeduction: holidayAndAbsence.absenceHoursDeduction,
|
||||||
|
availableHours: round1(availableHours),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -3,17 +3,11 @@ import { listAssignmentBookings } from "@capakraken/application";
|
|||||||
import { PermissionKey, type WeekdayAvailability } from "@capakraken/shared";
|
import { PermissionKey, type WeekdayAvailability } from "@capakraken/shared";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { findUniqueOrThrow } from "../db/helpers.js";
|
import { findUniqueOrThrow } from "../db/helpers.js";
|
||||||
import {
|
import { loadResourceDailyAvailabilityContexts } from "../lib/resource-capacity.js";
|
||||||
calculateEffectiveAvailableHours,
|
|
||||||
calculateEffectiveBookedHours,
|
|
||||||
countEffectiveWorkingDays,
|
|
||||||
loadResourceDailyAvailabilityContexts,
|
|
||||||
} from "../lib/resource-capacity.js";
|
|
||||||
import { fmtEur } from "../lib/format-utils.js";
|
import { fmtEur } from "../lib/format-utils.js";
|
||||||
import { planningReadProcedure, requirePermission } from "../trpc.js";
|
import { planningReadProcedure, requirePermission } from "../trpc.js";
|
||||||
import {
|
import {
|
||||||
ACTIVE_STATUSES,
|
ACTIVE_STATUSES,
|
||||||
averagePerWorkingDay,
|
|
||||||
calculateAllocatedHoursForDay,
|
calculateAllocatedHoursForDay,
|
||||||
createLocationLabel,
|
createLocationLabel,
|
||||||
getBaseDayAvailability,
|
getBaseDayAvailability,
|
||||||
@@ -21,6 +15,7 @@ import {
|
|||||||
round1,
|
round1,
|
||||||
toIsoDate,
|
toIsoDate,
|
||||||
} from "./staffing-shared.js";
|
} from "./staffing-shared.js";
|
||||||
|
import { buildResourceCapacitySummary } from "./staffing-capacity-summary.js";
|
||||||
|
|
||||||
function fmtDate(value: Date | null | undefined): string | null {
|
function fmtDate(value: Date | null | undefined): string | null {
|
||||||
return value ? value.toISOString().slice(0, 10) : null;
|
return value ? value.toISOString().slice(0, 10) : null;
|
||||||
@@ -143,60 +138,13 @@ async function queryStaffingSuggestions(
|
|||||||
const context = contexts.get(resource.id);
|
const context = contexts.get(resource.id);
|
||||||
const resourceBookings = bookingsByResourceId.get(resource.id) ?? [];
|
const resourceBookings = bookingsByResourceId.get(resource.id) ?? [];
|
||||||
const activeBookings = resourceBookings.filter((booking) => ACTIVE_STATUSES.has(booking.status));
|
const activeBookings = resourceBookings.filter((booking) => ACTIVE_STATUSES.has(booking.status));
|
||||||
const baseAvailableHours = calculateEffectiveAvailableHours({
|
const capacity = buildResourceCapacitySummary({
|
||||||
availability,
|
|
||||||
periodStart: startDate,
|
|
||||||
periodEnd: endDate,
|
|
||||||
context: undefined,
|
|
||||||
});
|
|
||||||
const totalAvailableHours = calculateEffectiveAvailableHours({
|
|
||||||
availability,
|
availability,
|
||||||
periodStart: startDate,
|
periodStart: startDate,
|
||||||
periodEnd: endDate,
|
periodEnd: endDate,
|
||||||
context,
|
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 conflictDays: string[] = [];
|
||||||
const conflictDetails: Array<{
|
const conflictDetails: Array<{
|
||||||
date: string;
|
date: string;
|
||||||
@@ -247,11 +195,12 @@ async function queryStaffingSuggestions(
|
|||||||
cursor.setUTCDate(cursor.getUTCDate() + 1);
|
cursor.setUTCDate(cursor.getUTCDate() + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
const remainingHours = Math.max(0, totalAvailableHours - allocatedHours);
|
const allocatedHours = capacity.bookedHours;
|
||||||
const remainingHoursPerDay = averagePerWorkingDay(remainingHours, effectiveWorkingDays);
|
const remainingHours = capacity.remainingHours;
|
||||||
|
const remainingHoursPerDay = capacity.remainingHoursPerDay;
|
||||||
const utilizationPercent =
|
const utilizationPercent =
|
||||||
totalAvailableHours > 0
|
capacity.availableHours > 0
|
||||||
? Math.min(100, (allocatedHours / totalAvailableHours) * 100)
|
? Math.min(100, (allocatedHours / capacity.availableHours) * 100)
|
||||||
: 0;
|
: 0;
|
||||||
type SkillRow = { skill: string; category?: string; proficiency: number; isMainSkill?: boolean };
|
type SkillRow = { skill: string; category?: string; proficiency: number; isMainSkill?: boolean };
|
||||||
let skills = resource.skills as unknown as SkillRow[];
|
let skills = resource.skills as unknown as SkillRow[];
|
||||||
@@ -293,19 +242,19 @@ async function queryStaffingSuggestions(
|
|||||||
},
|
},
|
||||||
capacity: {
|
capacity: {
|
||||||
requestedHoursPerDay: round1(hoursPerDay),
|
requestedHoursPerDay: round1(hoursPerDay),
|
||||||
requestedHoursTotal: round1(effectiveWorkingDays * hoursPerDay),
|
requestedHoursTotal: round1(capacity.workingDays * hoursPerDay),
|
||||||
baseWorkingDays: round1(baseWorkingDays),
|
baseWorkingDays: capacity.baseWorkingDays,
|
||||||
effectiveWorkingDays: round1(effectiveWorkingDays),
|
effectiveWorkingDays: capacity.workingDays,
|
||||||
baseAvailableHours: round1(baseAvailableHours),
|
baseAvailableHours: capacity.baseAvailableHours,
|
||||||
effectiveAvailableHours: round1(totalAvailableHours),
|
effectiveAvailableHours: capacity.availableHours,
|
||||||
bookedHours: round1(allocatedHours),
|
bookedHours: capacity.bookedHours,
|
||||||
remainingHours: round1(remainingHours),
|
remainingHours: capacity.remainingHours,
|
||||||
remainingHoursPerDay,
|
remainingHoursPerDay,
|
||||||
holidayCount: holidayDates.length,
|
holidayCount: capacity.holidaySummary.count,
|
||||||
holidayWorkdayCount,
|
holidayWorkdayCount: capacity.holidaySummary.workdayCount,
|
||||||
holidayHoursDeduction: round1(holidayHoursDeduction),
|
holidayHoursDeduction: capacity.holidaySummary.hoursDeduction,
|
||||||
absenceDayEquivalent: round1(absenceDayEquivalent),
|
absenceDayEquivalent: capacity.absenceSummary.dayEquivalent,
|
||||||
absenceHoursDeduction: round1(absenceHoursDeduction),
|
absenceHoursDeduction: capacity.absenceSummary.hoursDeduction,
|
||||||
},
|
},
|
||||||
conflicts: {
|
conflicts: {
|
||||||
count: conflictDays.length,
|
count: conflictDays.length,
|
||||||
|
|||||||
Reference in New Issue
Block a user