2
0
Fork 0

Compare commits

...

4 Commits

Author SHA1 Message Date
Luca 7242ed2edd feat(fallback): add bucket for last night shifts from 20:00 on
continuous-integration/drone/push Build is passing Details
2024-05-20 00:31:58 +02:00
Luca 23001a3de3 feat(fallback): add command for bulk team member creation
continuous-integration/drone/push Build is passing Details
2024-05-19 23:32:15 +02:00
Luca 2d90662e4c style: remove obsolete comment 2024-05-19 23:32:15 +02:00
Luca 15475e2eec feat(fallback): add comment field to TeamMember 2024-05-19 23:32:15 +02:00
7 changed files with 90 additions and 12 deletions

View File

@ -26,7 +26,6 @@ class RegisterForm(forms.Form):
name = forms.CharField( name = forms.CharField(
max_length=Helper.name.field.max_length, label="Name", widget=text_input() max_length=Helper.name.field.max_length, label="Name", widget=text_input()
) )
# actually verify phone number, lol
phone = PhoneNumberField( phone = PhoneNumberField(
max_length=Helper.phone.field.max_length, max_length=Helper.phone.field.max_length,
label="Handynummer für Benachrichtigungen", label="Handynummer für Benachrichtigungen",

View File

@ -29,9 +29,9 @@ class FallbackAssignmentInline(admin.TabularInline):
@admin.register(TeamMember) @admin.register(TeamMember)
class TeamMemberAdmin(admin.ModelAdmin): class TeamMemberAdmin(admin.ModelAdmin):
fields = ("id", "name", "url") fields = ("id", "name", "comment", "url")
readonly_fields = ("id", "url") readonly_fields = ("id", "url")
list_display = ("name", "shift_count") list_display = ("name", "comment", "shift_count")
ordering = ("name",) ordering = ("name",)
inlines = (FallbackAssignmentInline,) inlines = (FallbackAssignmentInline,)
actions = (assign_random_shifts, clear_shifts) # , reshuffle_shifts) actions = (assign_random_shifts, clear_shifts) # , reshuffle_shifts)

View File

@ -0,0 +1,42 @@
import sys
from django.core.management.base import BaseCommand, CommandError
from ...models import TeamMember
class Command(BaseCommand):
help = "Import a list of team members, optionally including their affiliations (stored in the comment field)"
def add_arguments(self, parser):
parser.add_argument(
"-d",
"--delimiter",
default=":",
help="character separating name from affiliations",
)
def handle(self, *args, **options):
try:
self._handle(*args, **options)
except KeyboardInterrupt:
self.stderr.write()
except Exception as e:
raise CommandError(e)
def _handle(self, *args, **options):
team_members = []
for line in sys.stdin.readlines():
line = line.strip()
if line == "" or line.startswith("#"):
continue
match line.split(options["delimiter"], maxsplit=1):
case [name, affiliations]:
team_members.append(
TeamMember(name=name, comment=affiliations.strip())
)
case [name]:
team_members.append(TeamMember(name=name))
TeamMember.objects.bulk_create(team_members)

View File

@ -0,0 +1,18 @@
# Generated by Django 5.0.4 on 2024-05-19 21:22
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("fallback", "0006_fallbackassignment_was_full"),
]
operations = [
migrations.AddField(
model_name="teammember",
name="comment",
field=models.CharField(default="", max_length=100),
),
]

View File

@ -1,15 +1,15 @@
import math import math
import secrets import secrets
from base64 import urlsafe_b64encode from base64 import urlsafe_b64encode
from datetime import datetime, time
from django.db.models import Count, Exists, ExpressionWrapper, Max, OuterRef, Sum from django.db.models import Count, Exists, ExpressionWrapper, Max, OuterRef, Sum
from django.db.models.fields import DateTimeField from django.db.models.fields import DateTimeField
from django.db.models.lookups import LessThan from django.db.models.lookups import LessThan
from django.utils import timezone
from shiftregister.importer.models import * from shiftregister.importer.models import *
night_shift_query = Q(start_at__hour__gte=21) | Q(start_at__hour__lte=10)
def generate_id(): def generate_id():
return int.from_bytes(secrets.token_bytes(3), byteorder="big") return int.from_bytes(secrets.token_bytes(3), byteorder="big")
@ -18,6 +18,7 @@ def generate_id():
class TeamMember(models.Model): class TeamMember(models.Model):
id = models.IntegerField(default=generate_id, editable=False, primary_key=True) id = models.IntegerField(default=generate_id, editable=False, primary_key=True)
name = models.CharField(max_length=100) name = models.CharField(max_length=100)
comment = models.CharField(max_length=100, default="")
fallback_shifts = models.ManyToManyField(Shift, through="FallbackAssignment") fallback_shifts = models.ManyToManyField(Shift, through="FallbackAssignment")
def url(self): def url(self):
@ -31,14 +32,32 @@ class TeamMember(models.Model):
) )
def assign_random_shifts(self): def assign_random_shifts(self):
needs_fallback = Q(deleted=False, calendar__needs_fallback=True)
current_tz = timezone.get_current_timezone()
# create a datetime combining the last date having fallback shifts
# after 20:00 with the time 20:00 in the current timezone
last_night = datetime.combine(
Event.objects.filter(needs_fallback, start_at__hour__gte=20)
.latest("start_at")
.start_at.astimezone(current_tz),
time(hour=20),
current_tz,
)
is_last_night = Q(start_at__gte=last_night)
is_night_shift = Q(start_at__hour__gte=21) | Q(start_at__hour__lte=10)
if self.fallback_shifts.count() != 0: if self.fallback_shifts.count() != 0:
return return
selector1 = (~night_shift_query) & Q(
deleted=False, calendar__needs_fallback=True day_shifts = ~is_night_shift & ~is_last_night & needs_fallback
) night_shifts = is_night_shift & ~is_last_night & needs_fallback
selector2 = night_shift_query & Q(deleted=False, calendar__needs_fallback=True) shit_shifts = is_last_night & needs_fallback
self._assign_from_bucket(selector1)
self._assign_from_bucket(selector2) self._assign_from_bucket(day_shifts)
self._assign_from_bucket(night_shifts)
self._assign_from_bucket(shit_shifts)
def _assign_from_bucket(self, bucket_selector): def _assign_from_bucket(self, bucket_selector):
free_bucket = ( free_bucket = (
@ -165,7 +184,7 @@ class TeamMember(models.Model):
assignment.delete() assignment.delete()
def __str__(self): def __str__(self):
return f"{self.name}" return f"{self.name}{f': {self.comment}' if self.comment else ''}"
class FallbackAssignment(models.Model): class FallbackAssignment(models.Model):