Add option to allow force assignment with leave conflict in RotaBuilder

This commit is contained in:
Ross
2025-09-17 21:35:52 +01:00
parent 47bfb16891
commit e1aec5011c
2 changed files with 28 additions and 11 deletions
+1
View File
@@ -322,6 +322,7 @@ def main(
weeks_to_rota=weeks,
balance_offset_modifier=bom,
name="cons rota test",
allow_force_assignment_with_leave_conflict=True,
)
# Rota = RotaBuilder(start_date, weeks_to_rota=20, balance_offset_modifier=1)
Rota.constraint_options["balance_weekends"] = True
+27 -11
View File
@@ -329,6 +329,7 @@ class RotaBuilder(object):
use_previous_shifts: bool = False,
use_shift_balance_extra: bool = False,
use_bank_holiday_extra: bool = False,
allow_force_assignment_with_leave_conflict: bool = False,
SHIFT_BOUNDS=SHIFT_BOUNDS,
name: str = "",
bank_holidays: dict[datetime.date, str] = bank_holiday_map,
@@ -422,12 +423,16 @@ class RotaBuilder(object):
self.run_start_time: datetime.datetime | None = None
self.run_end_time: datetime.datetime | None = None
self.allow_force_assignment_with_leave_conflict = allow_force_assignment_with_leave_conflict
self.bank_holidays = bank_holidays
self.paired_shifts = []
self.exported_rota_file = None
self.MAX_SHIFTS_PER_DAY = 2
def set_rota_constraint(self, constraint: str, value: Any):
if constraint not in self.constraint_options:
raise ValueError(f"Constraint {constraint} not valid")
@@ -962,12 +967,32 @@ class RotaBuilder(object):
# self.model.unavailable = Var(((worker, week, day) for worker in self.workers for week in weeks for day in days),
# within=Binary, initialize=0)
for worker in self.workers:
for week, day, shift_name in getattr(worker, "forced_assignments", []):
# Only add if this shift is valid for this week/day
if shift_name in self.get_shift_names_by_week_day(week, day, return_empty_if_week_day_not_found=True):
# Check for leave conflict
if hasattr(self, "unavailable_to_work"):
if (worker.id, week, day) in self.unavailable_to_work:
if self.allow_force_assignment_with_leave_conflict:
self.unavailable_to_work.remove((worker.id, week, day))
self.add_warning(
"Force assignment/leave conflict",
f"Worker '{worker.name}' has a forced assignment for shift '{shift_name}' on week {week}, day {day} (date: {self.get_date_by_week_day(week, day)}), which conflicts with a leave request. The leave request has been overridden.",
)
else:
self.add_warning(
"Force assignment/leave conflict",
f"Worker '{worker.name}' has a forced assignment for shift '{shift_name}' on week {week}, day {day} (date: {self.get_date_by_week_day(week, day)}), which conflicts with a leave request.",
)
def availability_init(model, wid, week, day):
# This needs to be higher than the max number of possible shifts per day
if (wid, week, day) in self.unavailable_to_work:
# print((wid, week, day))
return 0
return 3
return self.MAX_SHIFTS_PER_DAY + 1
self.model.available = Param(
(
@@ -1556,16 +1581,7 @@ class RotaBuilder(object):
for week, day, shift_name in getattr(worker, "forced_assignments", []):
# Only add if this shift is valid for this week/day
if shift_name in self.get_shift_names_by_week_day(week, day, return_empty_if_week_day_not_found=True):
# Check for leave conflict
leave_conflict = False
if hasattr(self, "unavailable_to_work"):
if (worker.id, week, day) in self.unavailable_to_work:
leave_conflict = True
if leave_conflict:
self.add_warning(
"Force assignment/leave conflict",
f"Worker '{worker.name}' has a forced assignment for shift '{shift_name}' on week {week}, day {day} (date: {self.get_date_by_week_day(week, day)}), which conflicts with a leave request.",
)
# We have already checked for leave conflicts when building the model
self.model.constraints.add(
self.model.works[worker.id, week, day, shift_name] == 1
)