from datetime import date from django.core.exceptions import ValidationError from django.db import models from apps.accounts.models import User class Site(models.Model): site_id = models.BigAutoField(primary_key=True) site_name = models.CharField(max_length=30) user = models.ForeignKey(User, on_delete=models.CASCADE, related_name="sites") created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) class Meta: db_table = "sites" ordering = ["site_name"] def __str__(self) -> str: return self.site_name class Kandang(models.Model): kandang_id = models.BigAutoField(primary_key=True) kandang_name = models.CharField(max_length=30) feed_in_button_urls = models.JSONField(default=list, blank=True) site = models.ForeignKey(Site, on_delete=models.CASCADE, related_name="kandangs") created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) class Meta: db_table = "kandang" ordering = ["kandang_name"] def __str__(self) -> str: return self.kandang_name class Flock(models.Model): flock_id = models.BigAutoField(primary_key=True) flock_name = models.CharField(max_length=30) kandang = models.ForeignKey( Kandang, on_delete=models.CASCADE, related_name="flocks", ) created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) class Meta: db_table = "flock" ordering = ["flock_name"] def __str__(self) -> str: return self.flock_name class Cycle(models.Model): STATUS_ACTIVE = "active" STATUS_CLOSED = "closed" STATUS_CHOICES = [ (STATUS_ACTIVE, "Active"), (STATUS_CLOSED, "Closed"), ] cycle_id = models.BigAutoField(primary_key=True) total_days = models.IntegerField() start_date = models.DateField() end_date = models.DateField() doc_in_weight = models.IntegerField() doc_in_count = models.IntegerField() feed_initial_balance = models.IntegerField(default=0) feed_initial_balance_date = models.DateField(null=True, blank=True) status = models.CharField( max_length=30, choices=STATUS_CHOICES, default=STATUS_ACTIVE, db_index=True ) kandang = models.ForeignKey(Kandang, on_delete=models.CASCADE, related_name="cycles") created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) class Meta: db_table = "cycles" ordering = ["-start_date"] constraints = [ models.CheckConstraint( check=models.Q(end_date__gte=models.F("start_date")), name="chk_cycles_date_range", ) ] def __str__(self) -> str: return f"Cycle #{self.pk} ({self.status})" def clean(self) -> None: if self.end_date and self.start_date and self.end_date < self.start_date: raise ValidationError({"end_date": "Must be on or after start_date"}) def resolved_status(self) -> str: """Close the cycle once dashboard data through the end date is visible.""" from apps.operations.services.visibility import visible_through_date if visible_through_date() >= self.end_date: return self.STATUS_CLOSED return self.STATUS_ACTIVE def sync_status(self) -> bool: resolved = self.resolved_status() if self.status == resolved: return False self.status = resolved return True def save(self, *args, **kwargs): self.sync_status() super().save(*args, **kwargs) @property def current_day(self) -> int: """0-based cycle age: start_date is day 0.""" from apps.operations.services.visibility import visible_through_date effective = visible_through_date() if effective < self.start_date: return 0 elapsed = (effective - self.start_date).days max_day = max(self.total_days - 1, 0) return min(max(elapsed, 0), max_day) @property def visible_through_date(self) -> date: from apps.operations.services.visibility import visible_through_date as cutoff_date return cutoff_date()