127 lines
5.0 KiB
Python
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})" |