nexus-5/core/models/scope.py
2026-01-26 11:09:40 -05:00

127 lines
5.0 KiB
Python

from django.db import models
from django_choices_field import TextChoicesField
from core.models.account import Account, AccountAddress
from core.models.base import BaseModel
from core.models.enums import TaskFrequencyChoices
from core.models.profile import TeamProfile
from core.models.service import Service
class Scope(BaseModel):
"""Scope of work definition for an account address"""
name = models.CharField(max_length=255)
account = models.ForeignKey(Account, on_delete=models.PROTECT, related_name='scopes')
account_address = models.ForeignKey(AccountAddress, on_delete=models.PROTECT, related_name='scopes',
null=True, blank=True)
description = models.TextField(blank=True)
is_active = models.BooleanField(default=True)
class Meta:
ordering = ['name']
verbose_name = "Scope"
verbose_name_plural = "Scopes"
indexes = [
models.Index(fields=['account', 'is_active']),
models.Index(fields=['account_address', 'is_active']),
]
constraints = [
models.UniqueConstraint(
fields=['account_address'],
condition=models.Q(is_active=True, account_address__isnull=False),
name='unique_active_scope_per_address'
)
]
def __str__(self):
return f"{self.name} (account_id={self.account_id})"
class Area(BaseModel):
"""Area within a scope (e.g., Kitchen, Restrooms, Lobby)"""
name = models.CharField(max_length=100)
scope = models.ForeignKey(Scope, on_delete=models.CASCADE, related_name='areas')
order = models.PositiveIntegerField(default=0)
class Meta:
ordering = ['scope', 'order', 'name']
verbose_name = "Area"
verbose_name_plural = "Areas"
indexes = [
models.Index(fields=['scope', 'order']),
]
def __str__(self):
return f"{self.name} (scope_id={self.scope_id})"
class Task(BaseModel):
"""Individual task template within an area"""
area = models.ForeignKey(Area, on_delete=models.CASCADE, related_name='tasks')
description = models.TextField()
checklist_description = models.TextField(blank=True)
frequency = TextChoicesField(
choices_enum=TaskFrequencyChoices,
default=TaskFrequencyChoices.AS_NEEDED,
help_text="How often the task should be performed"
)
order = models.PositiveIntegerField(default=0)
is_conditional = models.BooleanField(default=False, help_text="Task marked 'where applicable'")
estimated_minutes = models.PositiveIntegerField(null=True, blank=True)
class Meta:
ordering = ['area', 'order']
verbose_name = "Task"
verbose_name_plural = "Tasks"
indexes = [
models.Index(fields=['area', 'order']),
models.Index(fields=['frequency']),
]
def __str__(self):
return f"{self.description[:50]}... (area_id={self.area_id})"
class TaskCompletion(BaseModel):
"""Record of a task template being completed during a service visit"""
task = models.ForeignKey(Task, on_delete=models.PROTECT, related_name='completions')
service = models.ForeignKey(Service, on_delete=models.PROTECT, related_name='task_completions')
account_address = models.ForeignKey(AccountAddress, on_delete=models.PROTECT, null=True, related_name='task_completions')
completed_by = models.ForeignKey(TeamProfile, on_delete=models.PROTECT)
completed_at = models.DateTimeField()
notes = models.TextField(blank=True)
# Autopopulated for efficient monthly/quarterly queries
year = models.PositiveIntegerField(editable=False)
month = models.PositiveIntegerField(editable=False)
class Meta:
ordering = ['-completed_at']
verbose_name = "Task Completion"
verbose_name_plural = "Task Completions"
indexes = [
models.Index(fields=['service']),
models.Index(fields=['task', 'year', 'month']),
models.Index(fields=['completed_by', 'completed_at']),
models.Index(fields=['account_address']),
]
constraints = [
# Prevent the same task being completed multiple times in the same service
models.UniqueConstraint(
fields=['service', 'task'],
name='unique_task_per_service'
)
]
def save(self, *args, **kwargs):
"""Autopopulate year/month from service date"""
# Backfill account_address from service if missing
if self.account_address_id is None and self.service_id and hasattr(self.service, 'account_address_id'):
self.account_address_id = getattr(self.service, 'account_address_id', None)
if self.service_id and hasattr(self.service, 'date'):
self.year = self.service.date.year
self.month = self.service.date.month
super().save(*args, **kwargs)
def __str__(self):
return f"TaskCompletion (task_id={self.task_id}, service_id={self.service_id})"