Refactor Assignment model to enforce single rota assignment per worker and update related view logic to prevent multiple assignments
This commit is contained in:
@@ -0,0 +1,16 @@
|
||||
from django.core.management.base import BaseCommand
|
||||
from django.db.models import Count
|
||||
|
||||
from rota.models import Worker
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = "List workers assigned to more than one rota (useful before migrating to OneToOne Assignment)"
|
||||
|
||||
def handle(self, *args, **options):
|
||||
qs = Worker.objects.annotate(num_rotas=Count('rotas')).filter(num_rotas__gt=1)
|
||||
if not qs.exists():
|
||||
self.stdout.write(self.style.SUCCESS('No workers assigned to more than one rota.'))
|
||||
return
|
||||
for w in qs:
|
||||
self.stdout.write(f"Worker id={w.id} name={w.name!r} assigned_rotas={w.num_rotas}")
|
||||
@@ -297,13 +297,18 @@ class Worker(models.Model):
|
||||
class Assignment(models.Model):
|
||||
"""Join model assigning a worker to a rota (can hold role / metadata)."""
|
||||
|
||||
worker = models.ForeignKey(Worker, on_delete=models.CASCADE)
|
||||
# Enforce that a Worker may only be assigned to a single RotaSchedule by
|
||||
# using a OneToOneField here. This ensures at the DB level that each
|
||||
# Worker has at most one Assignment (and therefore at most one rota).
|
||||
worker = models.OneToOneField(Worker, on_delete=models.CASCADE)
|
||||
rota = models.ForeignKey(RotaSchedule, on_delete=models.CASCADE)
|
||||
role = models.CharField(max_length=100, blank=True)
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
|
||||
class Meta:
|
||||
unique_together = ("worker", "rota")
|
||||
# With OneToOneField on worker, uniqueness for (worker, rota) is
|
||||
# implied by the worker uniqueness. Keep ordering for convenience.
|
||||
ordering = ("-created_at",)
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.worker} -> {self.rota}"
|
||||
|
||||
@@ -334,12 +334,19 @@ def worker_create(request):
|
||||
# If we created a worker and a rota_id was provided, create Assignment
|
||||
if rota is not None:
|
||||
from .models import Assignment
|
||||
# Only assign if the worker is not already assigned to any rota.
|
||||
try:
|
||||
Assignment.objects.create(worker=worker, rota=rota)
|
||||
if not worker.rotas.exists():
|
||||
Assignment.objects.create(worker=worker, rota=rota)
|
||||
else:
|
||||
# skip assigning; worker already assigned elsewhere
|
||||
pass
|
||||
except Exception:
|
||||
# best-effort: try using the M2M add as fallback
|
||||
# best-effort: try using the M2M add as fallback but still
|
||||
# respect single-rota constraint
|
||||
try:
|
||||
rota.workers.add(worker)
|
||||
if not worker.rotas.exists():
|
||||
rota.workers.add(worker)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
@@ -454,14 +461,37 @@ def worker_import(request, rota_id=None):
|
||||
except Exception:
|
||||
fte = 1.0
|
||||
|
||||
w = Worker.objects.create(name=name.strip(), email=email.strip(), site=site.strip(), grade=grade, fte=fte)
|
||||
# If an email is provided, try to find an existing worker to
|
||||
# avoid duplicates; otherwise create a new record.
|
||||
existing = None
|
||||
if email:
|
||||
try:
|
||||
existing = Worker.objects.filter(email__iexact=email.strip()).first()
|
||||
except Exception:
|
||||
existing = None
|
||||
|
||||
if existing is not None:
|
||||
w = existing
|
||||
else:
|
||||
w = Worker.objects.create(name=name.strip(), email=email.strip(), site=site.strip(), grade=grade, fte=fte)
|
||||
created.append(w)
|
||||
|
||||
# Assignment: only assign if the worker is not already assigned
|
||||
# to another rota. If already assigned, record an error and
|
||||
# skip assignment.
|
||||
if assign_to_rota and rota is not None:
|
||||
try:
|
||||
Assignment.objects.create(worker=w, rota=rota)
|
||||
if not w.rotas.exists():
|
||||
Assignment.objects.create(worker=w, rota=rota)
|
||||
else:
|
||||
# If already assigned to this rota, nothing to do.
|
||||
assigned_rota = w.rotas.first()
|
||||
if assigned_rota and assigned_rota.pk != rota.pk:
|
||||
errors.append(f"Row {i+1}: worker '{w.name}' already assigned to rota '{assigned_rota.name}'")
|
||||
except Exception:
|
||||
try:
|
||||
rota.workers.add(w)
|
||||
if not w.rotas.exists():
|
||||
rota.workers.add(w)
|
||||
except Exception:
|
||||
pass
|
||||
except Exception as exc:
|
||||
|
||||
Reference in New Issue
Block a user