103 lines
3.6 KiB
Python
103 lines
3.6 KiB
Python
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}"
|