feat(planning): explain and trace planning decisions

This commit is contained in:
nessar committed 2026-08-31 00:02:45 +02:00
1 parent 609b3dece9
commit d7a13b0b1e
24 files changed
+6073 -342

No files matched your search

+30 -11
View File
@@ -6,6 +6,7 @@ import weekOfYear from 'dayjs/plugin/weekOfYear';
import isoWeek from 'dayjs/plugin/isoWeek';
import { DateCacheService } from './date-cache.service';
import {
createPlanningCalculation as createPlanningCalculationOperation,
planLeave as planLeaveOperation,
LeaveType,
PlanOutcome,
@@ -30,14 +31,19 @@ export class CalendarService {
private dateCache = inject(DateCacheService);
readonly planLeave = planLeaveOperation;
readonly createPlanningCalculation = createPlanningCalculationOperation;
getDefaultPlanningHorizon(now = new Date()): { start: string; end: string } {
const start = planningDateInParis(now);
return { start, end: addPlanningYears(start, 1) };
}
selectedDatesForPlan(plan: PlanOutcome): SelectedDates {
const selected = this.getSelectedDatesForYear();
selectedDatesForPlan(
plan: Pick<PlanOutcome, 'placements'> & { horizon?: { start: string; end: string } },
): SelectedDates {
const selected = plan.horizon
? this.getSelectedDatesForHorizon(plan.horizon)
: this.getSelectedDatesForYear();
for (const placement of plan.placements) {
selected.push(
new SelectedDate(
@@ -65,8 +71,22 @@ export class CalendarService {
}
getSelectedDatesForYear(): SelectedDates {
const holidays = this.getHolidays();
const monthInAYear = this.monthsInAYearFromNow();
return this.getSelectedDatesForHorizon(this.getDefaultPlanningHorizon());
}
getSelectedDatesForHorizon(horizon: { start: string; end: string }): SelectedDates {
const start = new Date(`${horizon.start}T00:00:00`);
const last = new Date(`${horizon.end}T00:00:00`);
last.setDate(last.getDate() - 1);
const holidays = this.getHolidays(start.getFullYear());
const monthInHorizon: Year = [];
const cursor = new Date(start.getFullYear(), start.getMonth(), 1);
while (cursor <= last) {
monthInHorizon.push(
CalendarService.weeksInMonth(cursor.getMonth(), cursor.getFullYear()),
);
cursor.setMonth(cursor.getMonth() + 1);
}
const selectedDates: SelectedDates = new SelectedDates();
// Use DateCacheService to get today's date components
@@ -79,7 +99,7 @@ export class CalendarService {
// Create a Set of holiday timestamps for O(1) lookup instead of O(n)
const holidayTimestamps = new Set(holidays.map((h) => h.getTime()));
for (const weeksInAMonth of monthInAYear) {
for (const weeksInAMonth of monthInHorizon) {
for (const daysInAWeek of weeksInAMonth) {
for (const day of daysInAWeek) {
if (day) {
@@ -131,10 +151,9 @@ export class CalendarService {
}
}
getHolidays(): Date[] {
getHolidays(startYear = this.dateCache.getCurrentYear()): Date[] {
// Cache holidays to avoid recalculation
const currentYear = this.dateCache.getCurrentYear();
if (this.holidayCacheYear === currentYear && this.holidayTimestampsCache) {
if (this.holidayCacheYear === startYear && this.holidayTimestampsCache) {
// Return timestamps directly for efficient lookup
return Array.from(this.holidayTimestampsCache).map((ts) => new Date(ts));
}
@@ -142,10 +161,10 @@ export class CalendarService {
// Logic to return holidays for the given year
const hd = new Holidays('FR', allTypesOptions);
const holidays = hd
.getHolidays(currentYear, lang)
.getHolidays(startYear, lang)
.filter((holiday) => holiday.type === 'public');
const holidaysPlusOne = hd
.getHolidays(currentYear + 1, lang)
.getHolidays(startYear + 1, lang)
.filter((holiday) => holiday.type === 'public');
holidays.push(...holidaysPlusOne);
const holidaysDate: Date[] = holidays.map((holiday) => {
@@ -155,7 +174,7 @@ export class CalendarService {
// Update cache
this.holidayTimestampsCache = new Set(holidaysDate.map((d) => d.getTime()));
this.holidayCacheYear = currentYear;
this.holidayCacheYear = startYear;
return holidaysDate;
}