from django.db import models from django.utils import timezone from apps.core.models import TimeStampedModel, UUIDModel class Facility(UUIDModel, TimeStampedModel): name = models.CharField(max_length=200) def __str__(self) -> str: return self.name class ProcessingLine(UUIDModel, TimeStampedModel): facility = models.ForeignKey(Facility, on_delete=models.CASCADE, related_name="lines") name = models.CharField(max_length=200) def __str__(self) -> str: return f"{self.facility.name} / {self.name}" class Holiday(UUIDModel): date = models.DateField(unique=True) class FacilitySchedule(UUIDModel, TimeStampedModel): """Per-facility, per-date availability: capacity for the day and days off. If a schedule row exists for a date and ``is_open`` is False, no slots are generated for that day. ``capacity``/``start_time``/``end_time`` override the global Reservation settings for that date. """ facility = models.ForeignKey(Facility, on_delete=models.CASCADE, related_name="schedules") date = models.DateField(db_index=True) is_open = models.BooleanField(default=True) capacity = models.PositiveIntegerField(default=2) start_time = models.TimeField(null=True, blank=True) end_time = models.TimeField(null=True, blank=True) note = models.CharField(max_length=200, blank=True) class Meta: ordering = ["-date"] constraints = [ models.UniqueConstraint(fields=["facility", "date"], name="uniq_facility_schedule_date") ] def __str__(self) -> str: return f"{self.facility.name} {self.date}" class Slot(UUIDModel): facility = models.ForeignKey(Facility, on_delete=models.CASCADE, related_name="slots") line = models.ForeignKey( ProcessingLine, on_delete=models.CASCADE, related_name="slots", null=True, blank=True ) date = models.DateField(db_index=True) start_time = models.TimeField() end_time = models.TimeField() capacity = models.PositiveIntegerField(default=1) max_weight_kg = models.DecimalField(max_digits=12, decimal_places=2, null=True, blank=True) booked_count = models.PositiveIntegerField(default=0) blocked = models.BooleanField(default=False) class Meta: constraints = [ models.UniqueConstraint(fields=["facility", "line", "date", "start_time"], name="uniq_slot_time") ] def is_available(self) -> bool: return ( not self.blocked and self.booked_count < self.capacity and self.date >= timezone.localdate() ) class Reservation(UUIDModel, TimeStampedModel): class Status(models.TextChoices): PENDING = "pending", "Pending approval" CONFIRMED = "confirmed", "Confirmed" REJECTED = "rejected", "Rejected" ARRIVED = "arrived", "Arrived" WEIGHED = "weighed", "Weighed" CANCELLED = "cancelled", "Cancelled" EXPIRED = "expired", "Expired" title = models.CharField(max_length=200, blank=True) customer = models.ForeignKey("accounts.Customer", on_delete=models.CASCADE, related_name="reservations") slot = models.ForeignKey(Slot, on_delete=models.PROTECT, related_name="reservations") status = models.CharField(max_length=16, choices=Status.choices, default=Status.PENDING) notes = models.TextField(blank=True) approved_by = models.ForeignKey( "accounts.User", null=True, blank=True, on_delete=models.SET_NULL, related_name="approved_reservations" ) approved_at = models.DateTimeField(null=True, blank=True) class Meta: ordering = ["-created_at"] def __str__(self) -> str: return self.title or f"Reservation {self.id}"