|
|
@ -48,15 +48,14 @@ class TeamMember(models.Model):
|
|
|
|
)
|
|
|
|
)
|
|
|
|
shifts_per_member = math.ceil(total_slot_count / (active_team_members))
|
|
|
|
shifts_per_member = math.ceil(total_slot_count / (active_team_members))
|
|
|
|
shift_count = min(max_shifts_per_member, shifts_per_member)
|
|
|
|
shift_count = min(max_shifts_per_member, shifts_per_member)
|
|
|
|
print(
|
|
|
|
|
|
|
|
f"total:{total_slot_count} max:{max_shifts_per_member} calc:{shifts_per_member} chosen:{shift_count} calc:{active_team_members*shift_count} free `before:{free_slot_count} {self.name}"
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
blocked_times = []
|
|
|
|
blocked_times = []
|
|
|
|
for shift in self.fallback_shifts.all():
|
|
|
|
for shift in self.fallback_shifts.all():
|
|
|
|
blocked_times.append(
|
|
|
|
blocked_times.append(
|
|
|
|
Q(start_at__gte=(shift.start_at + shift.duration))
|
|
|
|
Q(start_at__gte=(shift.start_at + shift.duration))
|
|
|
|
| Q(end_at__lte=shift.start_at)
|
|
|
|
| Q(end_at__lte=shift.start_at)
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
# easy part: enough free shifts for everyone:
|
|
|
|
# easy part: enough free shifts for everyone:
|
|
|
|
assigned_shift_count = 0
|
|
|
|
assigned_shift_count = 0
|
|
|
|
for _ in range(shift_count):
|
|
|
|
for _ in range(shift_count):
|
|
|
@ -79,15 +78,12 @@ class TeamMember(models.Model):
|
|
|
|
Q(start_at__gte=(shift.start_at + shift.duration))
|
|
|
|
Q(start_at__gte=(shift.start_at + shift.duration))
|
|
|
|
| Q(end_at__lte=shift.start_at)
|
|
|
|
| Q(end_at__lte=shift.start_at)
|
|
|
|
)
|
|
|
|
)
|
|
|
|
# blocked_times.append(Q(start_at__gte=(shift.start_at+shift.duration)) | LessThan(F('start_at')+F('duration'), shift.start_at, output_field=DateTimeField()))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# there is a chance that even if qota*teammembers team members are activatet, there are still unasigned shifts
|
|
|
|
# there is a chance that even if quota*teammembers team members are activated, there are still unassigned shifts
|
|
|
|
# this happens if there are shifts with multiple people left, as we cannot assign multiple slots for
|
|
|
|
# this happens if there are shifts with multiple people left, as we cannot assign multiple slots for
|
|
|
|
# the same shift to one member.
|
|
|
|
# the same shift to one member.
|
|
|
|
# for now we will just reduce the quota a bit to calculate for these cases.
|
|
|
|
# for now we will just reduce the quota a bit to calculate for these cases.
|
|
|
|
|
|
|
|
|
|
|
|
# if len(shifts) >= (shift_count - 1):
|
|
|
|
|
|
|
|
# return
|
|
|
|
|
|
|
|
shifts_needed = shift_count - assigned_shift_count
|
|
|
|
shifts_needed = shift_count - assigned_shift_count
|
|
|
|
# this is not done very often so we can do this kinda inefficient but readable and maintainable:
|
|
|
|
# this is not done very often so we can do this kinda inefficient but readable and maintainable:
|
|
|
|
# for each missing shift, get the team member who has the most shifts in our bucket and take one of them at random
|
|
|
|
# for each missing shift, get the team member who has the most shifts in our bucket and take one of them at random
|
|
|
@ -96,11 +92,11 @@ class TeamMember(models.Model):
|
|
|
|
while shifts_needed > 0:
|
|
|
|
while shifts_needed > 0:
|
|
|
|
# this is a bit more complex and uses subqueries and id lists because we want to reuse the query selector for events.
|
|
|
|
# this is a bit more complex and uses subqueries and id lists because we want to reuse the query selector for events.
|
|
|
|
# maybe there is a good way to transform q-expressions to add prefixes to fields but for now this has to work
|
|
|
|
# maybe there is a good way to transform q-expressions to add prefixes to fields but for now this has to work
|
|
|
|
canidate_shift_ids = Event.objects.filter(bucket_selector).values(
|
|
|
|
candidate_shift_ids = Event.objects.filter(bucket_selector).values(
|
|
|
|
"shift_ptr_id"
|
|
|
|
"shift_ptr_id"
|
|
|
|
)
|
|
|
|
)
|
|
|
|
relevant_assignments = FallbackAssignment.objects.filter(
|
|
|
|
relevant_assignments = FallbackAssignment.objects.filter(
|
|
|
|
shift_id__in=canidate_shift_ids
|
|
|
|
shift_id__in=candidate_shift_ids
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
# get teammembers sorted by the most shifts in the relevant bucket
|
|
|
|
# get teammembers sorted by the most shifts in the relevant bucket
|
|
|
@ -109,7 +105,7 @@ class TeamMember(models.Model):
|
|
|
|
relevant_fallback_count=Count(
|
|
|
|
relevant_fallback_count=Count(
|
|
|
|
"fallback_shifts",
|
|
|
|
"fallback_shifts",
|
|
|
|
distinct=True,
|
|
|
|
distinct=True,
|
|
|
|
filter=Q(fallback_shifts__id__in=canidate_shift_ids),
|
|
|
|
filter=Q(fallback_shifts__id__in=candidate_shift_ids),
|
|
|
|
),
|
|
|
|
),
|
|
|
|
overall_fallback_count=Count("fallback_shifts"),
|
|
|
|
overall_fallback_count=Count("fallback_shifts"),
|
|
|
|
)
|
|
|
|
)
|
|
|
@ -127,7 +123,7 @@ class TeamMember(models.Model):
|
|
|
|
output_field=models.DateTimeField(),
|
|
|
|
output_field=models.DateTimeField(),
|
|
|
|
),
|
|
|
|
),
|
|
|
|
)
|
|
|
|
)
|
|
|
|
.filter(team_member_id=member.id, shift_id__in=canidate_shift_ids)
|
|
|
|
.filter(team_member_id=member.id, shift_id__in=candidate_shift_ids)
|
|
|
|
.exclude(
|
|
|
|
.exclude(
|
|
|
|
shift_id__in=FallbackAssignment.objects.filter(
|
|
|
|
shift_id__in=FallbackAssignment.objects.filter(
|
|
|
|
team_member_id=self.pk
|
|
|
|
team_member_id=self.pk
|
|
|
|