Merge branch 'master' of git.xkjq.uk:30001]:ross/proc-rota
This commit is contained in:
@@ -89,12 +89,7 @@ class SingleShift(object):
|
||||
Returns a text summary of the shift
|
||||
"""
|
||||
|
||||
return "{}: {} workers for sites ({}) on days ({})".format(
|
||||
self.name,
|
||||
self.workers_required,
|
||||
", ".join(self.site),
|
||||
", ".join(self.shift_days),
|
||||
)
|
||||
return f"{self.name}: {self.workers_required} workers for sites ({', '.join(self.site)}) on days ({', '.join(self.shift_days)})"
|
||||
|
||||
|
||||
class RotaBuilder(object):
|
||||
@@ -102,18 +97,21 @@ class RotaBuilder(object):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
start_date,
|
||||
weeks_to_rota=26,
|
||||
balance_offset_modifier=1,
|
||||
ltft_balance_offset=2,
|
||||
max_night_frequency=2,
|
||||
max_weekend_frequency=2,
|
||||
use_previous_shifts=False,
|
||||
start_date: datetime.datetime,
|
||||
weeks_to_rota: int = 26,
|
||||
balance_offset_modifier: int = 1,
|
||||
ltft_balance_offset: int = 1,
|
||||
max_night_frequency: int = 2,
|
||||
max_weekend_frequency: int = 2,
|
||||
use_previous_shifts: bool = False,
|
||||
use_shift_balance_extra: bool = False,
|
||||
use_bank_holiday_extra: bool = False,
|
||||
):
|
||||
|
||||
print("Start time: {}".format(datetime.datetime.now()))
|
||||
print("Weeks to rota: {}".format(weeks_to_rota))
|
||||
print("Use previous shifts {}".format(use_previous_shifts))
|
||||
print(f"Start time: {datetime.datetime.now()}")
|
||||
print(f"Weeks to rota: {weeks_to_rota}")
|
||||
print(f"Use previous shifts {use_previous_shifts}")
|
||||
print(f"Use previous shifts (balance_extra) {use_shift_balance_extra}")
|
||||
|
||||
self.shifts = [] # type List[SingleShift]
|
||||
|
||||
@@ -124,12 +122,12 @@ class RotaBuilder(object):
|
||||
self.weeks_days_product = list(itertools.product(self.weeks, days))
|
||||
|
||||
self.use_previous_shifts = use_previous_shifts
|
||||
self.use_shift_balance_extra = use_shift_balance_extra
|
||||
self.use_bank_holiday_extra = use_bank_holiday_extra
|
||||
|
||||
if start_date.weekday() != 0:
|
||||
raise ValueError(
|
||||
"Start date {} must be a Mon (not a {})".format(
|
||||
start_date.isoformat(), days[start_date.weekday()]
|
||||
)
|
||||
f"Start date {start_date.isoformat()} must be a Mon (not a {days[start_date.weekday()]})"
|
||||
)
|
||||
|
||||
self.start_date = start_date
|
||||
@@ -166,6 +164,7 @@ class RotaBuilder(object):
|
||||
"balance_bank_holidays": True,
|
||||
"balance_blocks": True,
|
||||
"balance_shifts": True,
|
||||
"minimise_shift_diffs": True,
|
||||
"balance_weekends": True,
|
||||
"max_weekends": False,
|
||||
"max_shifts_per_week": 4,
|
||||
@@ -189,6 +188,8 @@ class RotaBuilder(object):
|
||||
self.week_day_date_map[(week, day)] = d
|
||||
n = n + 1
|
||||
|
||||
self.results = None
|
||||
|
||||
def solve_model(self, solver="cbc", use_neos=False, options={}):
|
||||
print("Setting up solver")
|
||||
self.opt = SolverFactory(solver)
|
||||
@@ -353,6 +354,22 @@ class RotaBuilder(object):
|
||||
initialize=0,
|
||||
)
|
||||
|
||||
self.model.shift_count_diff = Var(
|
||||
(
|
||||
(worker.id, shift)
|
||||
for worker in self.workers
|
||||
for shift in self.get_shift_names()
|
||||
),
|
||||
within=Reals,
|
||||
initialize=0,
|
||||
)
|
||||
|
||||
self.model.shift_count_diff_summed = Var(
|
||||
(worker.id for worker in self.workers),
|
||||
within=Reals,
|
||||
initialize=0,
|
||||
)
|
||||
|
||||
if self.constraint_options["balance_shifts"]:
|
||||
|
||||
self.model.shift_count_t1 = Var(
|
||||
@@ -822,6 +839,15 @@ class RotaBuilder(object):
|
||||
# Most of our constraints apply per worker
|
||||
for worker in self.workers:
|
||||
|
||||
if self.constraint_options["minimise_shift_diffs"]:
|
||||
self.model.constraints.add(
|
||||
self.model.shift_count_diff_summed[worker.id]
|
||||
== sum(
|
||||
self.model.shift_count_diff[worker.id, shift.name]
|
||||
for shift in self.get_shifts()
|
||||
)
|
||||
)
|
||||
|
||||
for week_blocks in self.get_week_block_iterator(4):
|
||||
# Prevent weekend shifts more than once every n weeks
|
||||
self.model.constraints.add(
|
||||
@@ -877,7 +903,7 @@ class RotaBuilder(object):
|
||||
total_shifts = self.shift_counts[shift] * shift.workers_required
|
||||
|
||||
full_time_equivalent_joined = sum(
|
||||
[self.full_time_equivalent_sites[i] for i in shift.site]
|
||||
self.full_time_equivalent_sites[i] for i in shift.site
|
||||
)
|
||||
|
||||
target_shifts = (
|
||||
@@ -891,6 +917,11 @@ class RotaBuilder(object):
|
||||
target_shifts + float(allocated) - float(worked)
|
||||
)
|
||||
|
||||
if self.use_shift_balance_extra:
|
||||
if shift.name in worker.shift_balance_extra:
|
||||
extra = worker.shift_balance_extra[shift.name]
|
||||
target_shifts = target_shifts + extra
|
||||
|
||||
# print(worker.name, shift.name, target_shifts)
|
||||
worker.shift_target_number[shift.name] = target_shifts
|
||||
|
||||
@@ -940,6 +971,12 @@ class RotaBuilder(object):
|
||||
)
|
||||
)
|
||||
|
||||
self.model.constraints.add(
|
||||
self.model.shift_count_diff[worker.id, shift.name]
|
||||
== self.model.shift_count[worker.id, shift.name]
|
||||
- worker.shift_target_number[shift.name]
|
||||
)
|
||||
|
||||
# Define shift_count_t1 and shift_count_t2 constraints for the object
|
||||
# Thus bypassing the need for a quadratic solver
|
||||
# t1-t2 is the target
|
||||
@@ -951,8 +988,7 @@ class RotaBuilder(object):
|
||||
- self.model.shift_count_t2[worker.id]
|
||||
== sum(
|
||||
(
|
||||
self.model.shift_count[worker.id, shift.name]
|
||||
- worker.shift_target_number[shift.name]
|
||||
self.model.shift_count_diff[worker.id, shift.name]
|
||||
)
|
||||
* shift.balance_weighting
|
||||
for shift in self.get_shifts()
|
||||
@@ -961,6 +997,12 @@ class RotaBuilder(object):
|
||||
# if "night" not in shift.constraints))
|
||||
|
||||
if self.constraint_options["balance_bank_holidays"]:
|
||||
|
||||
extra_bank_holiday = 0
|
||||
if self.use_bank_holiday_extra:
|
||||
print(worker.name, worker.bank_holiday_extra)
|
||||
extra_bank_holiday = worker.bank_holiday_extra
|
||||
|
||||
self.model.constraints.add(
|
||||
self.model.bank_holiday_count[worker.id]
|
||||
== sum(
|
||||
@@ -968,6 +1010,7 @@ class RotaBuilder(object):
|
||||
for week, day, shift in self.get_all_shiftname_combinations()
|
||||
if self.week_day_date_map[(week, day)] in bank_holiday_map
|
||||
)
|
||||
+ extra_bank_holiday
|
||||
)
|
||||
|
||||
xU = len(self.get_bank_holiday_week_days()) + 1
|
||||
@@ -1524,6 +1567,16 @@ class RotaBuilder(object):
|
||||
else:
|
||||
shift_balancing = 0
|
||||
|
||||
if self.constraint_options["minimise_shift_diffs"]:
|
||||
shift_diff_modifier_constant = 10
|
||||
shift_diff_balancing = sum(
|
||||
shift_diff_modifier_constant
|
||||
* self.model.shift_count_diff_summed[(worker.id)]
|
||||
for worker in self.workers
|
||||
)
|
||||
else:
|
||||
shift_diff_balancing = 0
|
||||
|
||||
if self.constraint_options["balance_nights"]:
|
||||
night_balance_modifier_constant = 10
|
||||
night_shift_balancing = sum(
|
||||
@@ -1621,6 +1674,7 @@ class RotaBuilder(object):
|
||||
weekend_shift_balancing
|
||||
+ shift_balancing
|
||||
+ night_shift_balancing
|
||||
+ shift_diff_balancing
|
||||
+ preferences
|
||||
+ nights_site_balancing
|
||||
+ bank_holiday_balancing
|
||||
@@ -1656,14 +1710,14 @@ class RotaBuilder(object):
|
||||
|
||||
self.workers = sorted(self.workers)
|
||||
|
||||
self.full_time_equivalent = sum([w.fte_adj for w in self.workers])
|
||||
self.full_time_equivalent = sum(w.fte_adj for w in self.workers)
|
||||
self.full_time_equivalent_sites = {}
|
||||
self.workers_at_sites = {}
|
||||
|
||||
for site in self.sites:
|
||||
self.workers_at_sites[site] = [w for w in self.workers if w.site == site]
|
||||
self.full_time_equivalent_sites[site] = sum(
|
||||
[w.fte_adj for w in self.workers if w.site == site]
|
||||
w.fte_adj for w in self.workers if w.site == site
|
||||
)
|
||||
|
||||
pairs = defaultdict(list)
|
||||
@@ -1905,11 +1959,9 @@ class RotaBuilder(object):
|
||||
|
||||
l = []
|
||||
for site in w:
|
||||
l.append(
|
||||
"{}: {}".format(site, sum([worker.fte_adj for worker in w[site]]) / 100)
|
||||
)
|
||||
l.append(f"{site}: {sum(worker.fte_adj for worker in w[site]) / 100}")
|
||||
t = "\n".join(l)
|
||||
return "Full time equivalent trainees by site:\n{}".format(t)
|
||||
return f"Full time equivalent trainees by site:\n{t}"
|
||||
|
||||
def get_bank_holiday_week_days(self):
|
||||
return [
|
||||
@@ -1920,17 +1972,17 @@ class RotaBuilder(object):
|
||||
|
||||
|
||||
class RotaResults(object):
|
||||
def __init__(self, rota, results):
|
||||
def __init__(self, rota):
|
||||
self.rota = rota
|
||||
self.results = results
|
||||
# self.results = results
|
||||
|
||||
def export_rota_to_html(self, filename="rota"):
|
||||
with open("{}.html".format(filename), "w") as f:
|
||||
with open(f"{filename}.html", "w") as f:
|
||||
f.write(self.get_worker_timetable_html(True))
|
||||
|
||||
def export_rota_to_csv(self, filename="rota"):
|
||||
works = self.rota.model.works
|
||||
with open("{}.csv".format(filename), "w", newline="") as f:
|
||||
with open(f"{filename}.csv", "w", newline="") as f:
|
||||
wr = csv.writer(f, quoting=csv.QUOTE_ALL)
|
||||
l = ["Name"]
|
||||
l.extend([worker.name for worker in self.rota.workers])
|
||||
@@ -1949,7 +2001,7 @@ class RotaResults(object):
|
||||
wr.writerow(l4)
|
||||
|
||||
for week, day in self.rota.get_week_day_combinations():
|
||||
d = ["Week {} Day {}".format(week, day)]
|
||||
d = [f"Week {week} Day {day}"]
|
||||
|
||||
for worker in self.rota.workers:
|
||||
i = ""
|
||||
@@ -1994,16 +2046,14 @@ class RotaResults(object):
|
||||
self, show_prefs=False, marker_every=30, show_unavailable=False
|
||||
):
|
||||
model = self.rota.model
|
||||
week_string = "{:20}".format("-Week-") + "".join(
|
||||
[7 * str("{}".format(w))[-1:] for w in self.rota.weeks]
|
||||
)
|
||||
days_string = "{:20}".format("-Day-") + "".join(
|
||||
"MTWTFSS" * len(self.rota.weeks)
|
||||
week_string = f"{'-Week-':20}" + "".join(
|
||||
[7 * str(f"{w}")[-1:] for w in self.rota.weeks]
|
||||
)
|
||||
days_string = f"{'-Day-':20}" + "".join("MTWTFSS" * len(self.rota.weeks))
|
||||
timetable = []
|
||||
for worker in self.rota.workers:
|
||||
shifts = []
|
||||
w = ["{:20}".format(worker.name)]
|
||||
w = [f"{worker.name:20}"]
|
||||
for week, day in self.rota.get_week_day_combinations():
|
||||
a = "-"
|
||||
for shift in self.rota.get_shift_names_by_week_day(week, day):
|
||||
@@ -2014,16 +2064,17 @@ class RotaResults(object):
|
||||
|
||||
shift_count = ""
|
||||
for s in set(shifts):
|
||||
shift_count = shift_count + "{}: {}, ".format(s, shifts.count(s))
|
||||
shift_count = shift_count + f"{s}: {shifts.count(s)}, "
|
||||
|
||||
shift_count = shift_count + "#weekends_worked: {}\\#".format(
|
||||
model.worker_weekend_count[worker.id].value
|
||||
shift_count = (
|
||||
shift_count
|
||||
+ f"#weekends_worked: {model.worker_weekend_count[worker.id].value}\\#"
|
||||
)
|
||||
timetable.append("{} {}".format("".join(w), shift_count))
|
||||
timetable.append(f"{''.join(w)} {shift_count}")
|
||||
|
||||
if show_prefs:
|
||||
# prefs
|
||||
w = ["{:20}".format("Preferences")]
|
||||
w = [f"{'Preferences':20}"]
|
||||
for week, day in self.rota.get_week_day_combinations():
|
||||
if model.pref_not_to_work[worker.id, week, day] > 0:
|
||||
w.append("Y")
|
||||
@@ -2033,7 +2084,7 @@ class RotaResults(object):
|
||||
|
||||
if show_unavailable:
|
||||
# prefs
|
||||
w = ["{:20}".format("Unavailable")]
|
||||
w = [f"{'Unavailable':20}"]
|
||||
for week, day in self.rota.get_week_day_combinations():
|
||||
if model.available[worker.id, week, day] > 0:
|
||||
w.append("A")
|
||||
@@ -2060,9 +2111,7 @@ class RotaResults(object):
|
||||
timetable = []
|
||||
|
||||
timetable.append(
|
||||
"<h2>Rota start date: {} ({} weeks)</h2>".format(
|
||||
self.rota.start_date.isoformat(), self.rota.weeks[-1]
|
||||
)
|
||||
f"<h2>Rota start date: {self.rota.start_date.isoformat()} ({self.rota.weeks[-1]} weeks)</h2>"
|
||||
)
|
||||
|
||||
date_row = ["<th class='worker'></th>"]
|
||||
@@ -2070,9 +2119,9 @@ class RotaResults(object):
|
||||
n = 0
|
||||
for week, day in self.rota.get_week_day_combinations():
|
||||
d = self.rota.start_date + datetime.timedelta(n)
|
||||
date_row.append("<th title='{}'>Week {}: {}</th>".format(d, week, day))
|
||||
date_row.append(f"<th title='{d}'>Week {week}: {day}</th>")
|
||||
n = n + 1
|
||||
timetable.append("<tr class='data-row'>{}</tr>".format("".join(date_row)))
|
||||
timetable.append(f"<tr class='data-row'>{''.join(date_row)}</tr>")
|
||||
|
||||
current_site = ""
|
||||
|
||||
@@ -2080,11 +2129,7 @@ class RotaResults(object):
|
||||
if worker.site != current_site:
|
||||
try:
|
||||
timetable.append(
|
||||
"<tr><th class='site-title'>{} n={} fte={}</th><tr>".format(
|
||||
worker.site,
|
||||
len(self.rota.workers_at_sites[worker.site]),
|
||||
self.rota.full_time_equivalent_sites[worker.site],
|
||||
)
|
||||
f"<tr><th class='site-title'>{worker.site} n={len(self.rota.workers_at_sites[worker.site])} fte={self.rota.full_time_equivalent_sites[worker.site]}</th><tr>"
|
||||
)
|
||||
except KeyError as e:
|
||||
print(e)
|
||||
@@ -2117,7 +2162,8 @@ class RotaResults(object):
|
||||
a = shift[0]
|
||||
shift_name = shift
|
||||
break
|
||||
title = "{} ({})".format(shift_name, d)
|
||||
# If we haven't run just add the shift
|
||||
title = f"{shift_name} ({d})"
|
||||
css_class = day
|
||||
unavailable_reason = ""
|
||||
if model.available[worker.id, week, day] > 0:
|
||||
@@ -2132,37 +2178,23 @@ class RotaResults(object):
|
||||
except KeyError:
|
||||
print("Error getting reason")
|
||||
print(worker.id, worker.name)
|
||||
print("Week {}, Day {}".format(week, day))
|
||||
print(f"Week {week}, Day {day}")
|
||||
unavailable_reason = "????"
|
||||
title = "{} / {}".format(title, unavailable_reason)
|
||||
title = f"{title} / {unavailable_reason}"
|
||||
|
||||
bank_holiday = ""
|
||||
if d in bank_holiday_map:
|
||||
css_class = " ".join((css_class, "bank-holiday"))
|
||||
bank_holiday = " data-bank-holiday='{}'".format(bank_holiday_map[d])
|
||||
bank_holiday = f" data-bank-holiday='{bank_holiday_map[d]}'"
|
||||
|
||||
requests = ""
|
||||
if (worker.id, week, day) in self.rota.work_requests_map:
|
||||
css_class = " ".join((css_class, "shift-requested"))
|
||||
title = " ".join((title, "[REQUESTED]"))
|
||||
requests = " data-shift-request='{}'".format(
|
||||
self.rota.work_requests_map[(worker.id, week, day)]
|
||||
)
|
||||
requests = f" data-shift-request='{self.rota.work_requests_map[worker.id, week, day]}'"
|
||||
|
||||
shift_tds.append(
|
||||
"<td title='{}' class='rota-day {}' data-shift='{}' data-available='{}' data-unavailable_reason='{}' data-date='{}' data-week='{}' data-day='{}'{}{}>{}</td>".format(
|
||||
title,
|
||||
css_class,
|
||||
shift_name,
|
||||
available,
|
||||
unavailable_reason,
|
||||
d,
|
||||
week,
|
||||
day,
|
||||
requests,
|
||||
bank_holiday,
|
||||
a,
|
||||
)
|
||||
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}'{requests}{bank_holiday}>{a}</td>"
|
||||
)
|
||||
|
||||
shift_count = ""
|
||||
@@ -2170,9 +2202,28 @@ class RotaResults(object):
|
||||
for s in set(shifts):
|
||||
c = shifts.count(s)
|
||||
shift_count_dict[s] = c
|
||||
shift_count = shift_count + "{}: {}, ".format(s, c)
|
||||
shift_count = shift_count + f"{s}: {c}, "
|
||||
|
||||
worker_td = "<td title='Site: {site}' class='worker {site}' data-nwds='{nwds}' data-site='{site}' data-worker='{name}' data-fte='{fte}' data-fte_adj='{fte_adj}' data-end_date='{end_date}' data-worker-targets='{targets}' data-shift-counts='{shift_counts}' data-weekend-target='{weekend_target}' data-night-at-derriford='{nights_at_derriford}' data-pair='{pair}'><span class='name' title='{name}'>{name}</span> ({grade}) [{fte}]</td>".format(
|
||||
|
||||
shift_diff_dict = {}
|
||||
for shift in self.rota.get_shifts():
|
||||
diff = model.shift_count_diff[worker.id, shift.name].value
|
||||
shift_diff_dict[shift.name] = diff
|
||||
|
||||
|
||||
worker_td = """<td title='Site: {site}' class='worker {site}'
|
||||
data-nwds='{nwds}' data-site='{site}' data-worker='{name}'
|
||||
data-fte='{fte}' data-fte_adj='{fte_adj}' data-end_date='{end_date}'
|
||||
data-worker-targets='{targets}' data-shift-counts='{shift_counts}'
|
||||
data-weekend-target='{weekend_target}'
|
||||
data-night-at-derriford='{nights_at_derriford}'
|
||||
data-pair='{pair}'
|
||||
data-shift-balance-extra='{shift_balance_extra}'
|
||||
data-bank-holiday-extra='{bank_holiday_extra}'
|
||||
data-shift-diff='{shift_diff}'
|
||||
data-shift-diff_summed='{shift_diff_summed}'
|
||||
>
|
||||
<span class='name' title='{name}'>{name}</span> ({grade}) [{fte}]</td>""".format(
|
||||
site=worker.site,
|
||||
nwds=nwds,
|
||||
name=worker.name,
|
||||
@@ -2185,17 +2236,22 @@ class RotaResults(object):
|
||||
nights_at_derriford=worker.night_at_derriford,
|
||||
grade=worker.grade,
|
||||
pair=worker.pair,
|
||||
shift_balance_extra=json.dumps(worker.shift_balance_extra),
|
||||
shift_diff=json.dumps(shift_diff_dict),
|
||||
shift_diff_summed=model.shift_count_diff_summed[worker.id].value,
|
||||
bank_holiday_extra=worker.bank_holiday_extra,
|
||||
)
|
||||
|
||||
shift_count = shift_count + "#weekends_worked: {}\\#".format(
|
||||
model.worker_weekend_count[worker.id].value
|
||||
shift_count = (
|
||||
shift_count
|
||||
+ f"#weekends_worked: {model.worker_weekend_count[worker.id].value}\\#"
|
||||
)
|
||||
|
||||
bank_holiday_count = model.bank_holiday_count[worker.id].value
|
||||
bank_holiday_count_w = model.bank_holiday_count_w[worker.id].value - 1
|
||||
# print(worker.name, bank_holiday_count, bank_holiday_count_w)
|
||||
timetable.append(
|
||||
"<tr class='worker-row'>{}{}</tr>".format(worker_td, "".join(shift_tds))
|
||||
f"<tr class='worker-row'>{worker_td}{''.join(shift_tds)}</tr>"
|
||||
)
|
||||
# timetable.append("<tr>{}</tr>".format("".join(w), shift_count))
|
||||
|
||||
@@ -2221,54 +2277,51 @@ class RotaResults(object):
|
||||
|
||||
result_stream = StringIO()
|
||||
|
||||
self.results.write(ostream=result_stream)
|
||||
self.results.write_json(ostream=result_stream)
|
||||
result_string = result_stream.getvalue()
|
||||
if self.rota.results is not None:
|
||||
self.rota.results.write(ostream=result_stream)
|
||||
self.rota.results.write_json(ostream=result_stream)
|
||||
result_string = result_stream.getvalue()
|
||||
else:
|
||||
result_string = "Rota not run"
|
||||
|
||||
html = """
|
||||
joined_timetable = "\n".join(timetable)
|
||||
|
||||
html = f"""
|
||||
<body>
|
||||
<div class="table-div" id="{}">
|
||||
<table>{}</table>
|
||||
<div class="table-div" id="{table_name}">
|
||||
<table>{joined_timetable}</table>
|
||||
</div>
|
||||
<details>
|
||||
<summary><h2>Rota settings</h2></summary>
|
||||
<div>
|
||||
<pre>
|
||||
{}
|
||||
{json.dumps(self.rota.constraint_options, indent=4)}
|
||||
</pre>
|
||||
</details>
|
||||
</div>
|
||||
<details>
|
||||
<summary><h2>Shifts settings</h2></summary>
|
||||
<div>
|
||||
{}
|
||||
<div id="shifts-container" data-shifts='{json.dumps([i.name for i in self.rota.shifts])}'>
|
||||
{"<br/>".join(str(i) for i in self.rota.shifts)}
|
||||
</div>
|
||||
</details>
|
||||
<details>
|
||||
<summary><h2>Output</h2></summary>
|
||||
<pre>
|
||||
{}
|
||||
{result_string}
|
||||
</pre>
|
||||
<div>
|
||||
</body>
|
||||
""".format(
|
||||
table_name,
|
||||
"\n".join(timetable),
|
||||
json.dumps(self.rota.constraint_options, indent=4),
|
||||
"<br/>".join(str(i) for i in self.rota.shifts),
|
||||
result_string,
|
||||
)
|
||||
"""
|
||||
|
||||
if include_html_tag:
|
||||
html = """<html>
|
||||
html = f"""<html>
|
||||
<head>
|
||||
<link rel="stylesheet" type="text/css" href="timetable.css">
|
||||
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
|
||||
<script src="timetable.js" defer></script>
|
||||
</head>
|
||||
{}</html>""".format(
|
||||
html
|
||||
)
|
||||
{html}</html>"""
|
||||
|
||||
return html
|
||||
|
||||
@@ -2294,15 +2347,11 @@ class RotaResults(object):
|
||||
for day in days
|
||||
].count(1)
|
||||
if c > 0:
|
||||
l.append("{} ({})".format(shift, c))
|
||||
l.append(f"{shift} ({c})")
|
||||
total_shifts = total_shifts + c
|
||||
# print(worker.id, shift)
|
||||
timetable[worker.get_details()][shift[0]] = c
|
||||
t.append(
|
||||
"{} [{}]: {}".format(
|
||||
worker.get_full_details(), total_shifts, ", ".join(l)
|
||||
)
|
||||
)
|
||||
t.append(f"{worker.get_full_details()} [{total_shifts}]: {', '.join(l)}")
|
||||
return "\n".join(t)
|
||||
|
||||
def get_shift_summary_html(self):
|
||||
@@ -2327,15 +2376,11 @@ class RotaResults(object):
|
||||
for day in days
|
||||
].count(1)
|
||||
if c > 0:
|
||||
l.append("{} ({})".format(shift, c))
|
||||
l.append(f"{shift} ({c})")
|
||||
total_shifts = total_shifts + c
|
||||
# print(worker.id, shift)
|
||||
timetable[worker.get_details()][shift[0]] = c
|
||||
t.append(
|
||||
"{} [{}]: {}".format(
|
||||
worker.get_full_details(), total_shifts, ", ".join(l)
|
||||
)
|
||||
)
|
||||
t.append(f"{worker.get_full_details()} [{total_shifts}]: {', '.join(l)}")
|
||||
return "\n".join(t)
|
||||
|
||||
# def get_no_preference(no_pref):
|
||||
|
||||
Reference in New Issue
Block a user