fix multishifts
This commit is contained in:
+68
-42
@@ -2419,26 +2419,43 @@ class RotaBuilder(object):
|
||||
if self.allowed_multi_shift_sets:
|
||||
shifts_today = self.get_shift_names_by_week_day(week, day)
|
||||
if shifts_today:
|
||||
# For each allowed set, allow those specific shifts to be assigned together
|
||||
for allowed_set in self.allowed_multi_shift_sets:
|
||||
if allowed_set.issubset(shifts_today):
|
||||
# Allow up to len(allowed_set) shifts, but only for those shifts
|
||||
# Build all allowed sets that are subsets of today's shifts
|
||||
allowed_sets_today = [
|
||||
allowed_set for allowed_set in self.allowed_multi_shift_sets
|
||||
if allowed_set.issubset(shifts_today)
|
||||
]
|
||||
|
||||
# For each possible assignment, only allow:
|
||||
# - a single shift
|
||||
# - OR exactly one of the allowed sets
|
||||
assigned_vars = [self.model.works[worker.id, week, day, shift] for shift in shifts_today]
|
||||
# The sum of assigned shifts must be 1 (single shift) or exactly len(allowed_set) for one allowed set
|
||||
allowed_sums = [1] + [len(allowed_set) for allowed_set in allowed_sets_today]
|
||||
# Enforce: sum in allowed_sums
|
||||
self.model.constraints.add(
|
||||
sum(assigned_vars) <= max(allowed_sums)
|
||||
)
|
||||
self.model.constraints.add(
|
||||
sum(assigned_vars) >= min(allowed_sums)
|
||||
)
|
||||
# Now, for each allowed set, prevent partial assignment of the set
|
||||
for allowed_set in allowed_sets_today:
|
||||
# If any shift in allowed_set is assigned, all must be assigned
|
||||
for shift in allowed_set:
|
||||
self.model.constraints.add(
|
||||
sum(
|
||||
self.model.works[worker.id, week, day, shift]
|
||||
for shift in allowed_set
|
||||
) <= len(allowed_set)
|
||||
sum(self.model.works[worker.id, week, day, s] for s in allowed_set)
|
||||
>= len(allowed_set) * self.model.works[worker.id, week, day, shift]
|
||||
)
|
||||
# Prevent assigning any other shift together with the allowed set
|
||||
for shift in shifts_today:
|
||||
if shift not in allowed_set:
|
||||
self.model.constraints.add(
|
||||
sum(
|
||||
self.model.works[worker.id, week, day, s]
|
||||
for s in allowed_set
|
||||
) + self.model.works[worker.id, week, day, shift]
|
||||
<= len(allowed_set)
|
||||
)
|
||||
# No other shifts can be assigned together with the allowed set
|
||||
for shift in shifts_today:
|
||||
if shift not in allowed_set:
|
||||
self.model.constraints.add(
|
||||
sum(self.model.works[worker.id, week, day, s] for s in allowed_set)
|
||||
+ self.model.works[worker.id, week, day, shift]
|
||||
<= len(allowed_set)
|
||||
)
|
||||
|
||||
|
||||
# Otherwise only one shift per day
|
||||
else:
|
||||
self.model.constraints.add(
|
||||
@@ -3514,19 +3531,24 @@ class RotaBuilder(object):
|
||||
week_table[week][day][shift].append(worker.get_details())
|
||||
return week_table
|
||||
|
||||
|
||||
def get_worker_timetable(self):
|
||||
works = self.model.works
|
||||
timetable = {
|
||||
worker.name: {week: {day: "" for day in days} for week in self.weeks}
|
||||
worker.name: {week: {day: [] for day in days} for week in self.weeks}
|
||||
for worker in self.workers
|
||||
}
|
||||
for worker in self.workers:
|
||||
for week in self.weeks:
|
||||
for day, shift in self.get_day_shiftname_combinations():
|
||||
if works[worker.id, week, day, shift].value == 1:
|
||||
timetable[worker.name][week][day] = shift
|
||||
for day in days:
|
||||
assigned_shifts = []
|
||||
for shift in self.get_shift_names_by_week_day(week, day):
|
||||
if works[worker.id, week, day, shift].value > 0.5:
|
||||
assigned_shifts.append(shift)
|
||||
timetable[worker.name][week][day] = assigned_shifts
|
||||
return timetable
|
||||
|
||||
|
||||
def get_worker_timetable_brief(
|
||||
self, show_prefs=False, marker_every=30, show_unavailable=False
|
||||
):
|
||||
@@ -3541,17 +3563,17 @@ class RotaBuilder(object):
|
||||
w = [f"{worker.name:20}"]
|
||||
for week, day in self.get_week_day_combinations():
|
||||
a = "-"
|
||||
shift_names = []
|
||||
for shift in self.get_shift_names_by_week_day(week, day):
|
||||
shift_obj = self.get_shift_by_name(shift)
|
||||
if model.works[worker.id, week, day, shift].value > 0:
|
||||
shifts.append(shift)
|
||||
a = shift_obj.get_display_char()
|
||||
if model.works[worker.id, week, day, shift].value > 0.5:
|
||||
shift_names.append(self.get_shift_by_name(shift).get_display_char())
|
||||
if shift_names:
|
||||
a = "".join(shift_names)
|
||||
shifts.extend(shift_names)
|
||||
w.append(a)
|
||||
|
||||
shift_count = ""
|
||||
for s in set(shifts):
|
||||
shift_count = shift_count + f"{s}: {shifts.count(s)}, "
|
||||
|
||||
shift_count = (
|
||||
shift_count
|
||||
+ f"#weekends_worked: {model.worker_weekend_count[worker.id].value}\\#"
|
||||
@@ -3653,29 +3675,33 @@ class RotaBuilder(object):
|
||||
shift_tds = []
|
||||
for week, day in self.get_week_day_combinations():
|
||||
d = self.start_date + datetime.timedelta(n)
|
||||
|
||||
n = n + 1
|
||||
a = "-"
|
||||
shift_name = ""
|
||||
|
||||
# Loop through all the days possible shifts and see
|
||||
# if the worker has been assigned
|
||||
shift_names = []
|
||||
assigned_shift_names = []
|
||||
locum = False
|
||||
shift_name = ""
|
||||
for shift in self.get_shift_names_by_week_day(week, day):
|
||||
shift_obj = self.get_shift_by_name(shift)
|
||||
if model.works[worker.id, week, day, shift].value > 0.8:
|
||||
shifts.append(shift)
|
||||
a = shift_obj.get_display_char()
|
||||
shift_name = shift
|
||||
break
|
||||
if self.get_workers_who_require_locums():
|
||||
shift_names.append(shift_obj.get_display_char())
|
||||
assigned_shift_names.append(shift)
|
||||
elif self.get_workers_who_require_locums():
|
||||
if model.locum_works[worker.id, week, day, shift].value > 0.8:
|
||||
locum_shifts.append(shift)
|
||||
a = shift_obj.get_display_char()
|
||||
shift_name = shift
|
||||
shift_names.append(shift_obj.get_display_char())
|
||||
assigned_shift_names.append(shift)
|
||||
locum = True
|
||||
if shift_names:
|
||||
a = "<br/>".join(shift_names)
|
||||
shifts.extend(assigned_shift_names)
|
||||
shift_name = assigned_shift_names[0] if assigned_shift_names else ""
|
||||
|
||||
break
|
||||
# Visual indication for multiple shifts
|
||||
multi_shift = len(shift_names) > 1
|
||||
css_class = day
|
||||
if multi_shift:
|
||||
css_class += " multi-shift"
|
||||
|
||||
title = f"{shift_name} ({d})"
|
||||
css_class = day
|
||||
@@ -3725,7 +3751,7 @@ class RotaBuilder(object):
|
||||
css_class = " ".join((css_class, "night-shift"))
|
||||
|
||||
shift_tds.append(
|
||||
f"<td title='{title}' class='rota-day {css_class}' data-shift='{shift_name}' data-available='{available}' data-unavailable_reason='{unavailable_reason}' data-date='{d}' data-week='{week}' data-day='{day}'{remote_site}{requests}{bank_holiday}>{a}</td>"
|
||||
f"<td title='{','.join(shift_names)} ({d})' class='rota-day {css_class}' data-shift='{','.join(shift_names)}' data-available='{available}' data-unavailable_reason='{unavailable_reason}' data-date='{d}' data-week='{week}' data-day='{day}'{remote_site}{requests}{bank_holiday}>{a}</td>"
|
||||
)
|
||||
|
||||
shift_count = ""
|
||||
|
||||
Reference in New Issue
Block a user