from django.db import models from core.models.profile import TeamProfile from core.models.account import Account, AccountAddress from core.models.project import Project from core.models.base import BaseModel class ProjectScope(BaseModel): """Scope of work definition for a project""" name = models.CharField(max_length=255) account = models.ForeignKey( Account, on_delete=models.PROTECT, related_name='project_scopes', blank=True, null=True, ) project = models.ForeignKey(Project, on_delete=models.PROTECT, related_name='project_scopes') account_address = models.ForeignKey( AccountAddress, on_delete=models.PROTECT, related_name='project_scopes', null=True, blank=True, ) description = models.TextField(blank=True) is_active = models.BooleanField(default=True) class Meta: ordering = ['name'] verbose_name = "Project Scope" verbose_name_plural = "Project Scopes" indexes = [ models.Index(fields=['project', 'is_active']), models.Index(fields=['account_address', 'is_active']), ] constraints = [ # Ensure only one active scope per project/address combination (when address present) models.UniqueConstraint( fields=['project', 'account_address'], condition=models.Q(is_active=True, account_address__isnull=False), name='unique_active_project_scope_per_address', ), ] def __str__(self): project_label = str(self.project_id) if self.project_id else "Unassigned Project" return f"{self.name} - {project_label}" class ProjectScopeCategory(BaseModel): """Category of work definition for a project""" name = models.CharField(max_length=255) scope = models.ForeignKey(ProjectScope, on_delete=models.CASCADE, related_name='project_areas') order = models.PositiveIntegerField(default=0) class Meta: ordering = ['scope', 'order', 'name'] verbose_name = "Project Scope Category" verbose_name_plural = "Project Scope Categories" indexes = [ models.Index(fields=['scope', 'order']), ] def __str__(self): return f"{self.scope.name} - {self.name}" class ProjectScopeTask(BaseModel): """Specific task definition for a project""" category = models.ForeignKey(ProjectScopeCategory, on_delete=models.CASCADE, related_name='project_tasks') description = models.TextField() checklist_description = models.TextField() order = models.PositiveIntegerField(default=0) estimated_minutes = models.PositiveIntegerField(null=True, blank=True) class Meta: ordering = ['category', 'order'] verbose_name = "Project Scope Task" verbose_name_plural = "Project Scope Tasks" indexes = [ models.Index(fields=['category', 'order']), ] def __str__(self): return f"{self.category.name}: {self.description[:50]}..." class ProjectScopeTaskCompletion(BaseModel): """Record of a task template being completed during a project""" task = models.ForeignKey(ProjectScopeTask, on_delete=models.PROTECT, related_name='completions') project = models.ForeignKey(Project, on_delete=models.PROTECT, related_name='task_completions') account = models.ForeignKey(Account, on_delete=models.PROTECT, related_name='task_completions', null=True, blank=True) account_address = models.ForeignKey(AccountAddress, on_delete=models.PROTECT, null=True, blank=True, related_name='project_task_completions') completed_by = models.ForeignKey(TeamProfile, on_delete=models.PROTECT) completed_at = models.DateTimeField() notes = models.TextField(blank=True)