# Python from django.db import models, transaction from django_choices_field import TextChoicesField from core.models.base import BaseModel from core.models.account import Account, AccountAddress from core.models.enums import TaskFrequencyChoices from core.models.scope import Scope, Area, Task class ScopeTemplate(BaseModel): """Reusable blueprint for creating a Scope with Areas and Tasks""" name = models.CharField(max_length=255, unique=True) description = models.TextField(blank=True) is_active = models.BooleanField(default=True) class Meta: ordering = ['name'] verbose_name = "Scope Template" verbose_name_plural = "Scope Templates" indexes = [models.Index(fields=['is_active'])] def __str__(self): return self.name @transaction.atomic def instantiate( self, *, account: Account, account_address: AccountAddress | None = None, name: str | None = None, description: str | None = None, is_active: bool = True, ) -> Scope: """Create a Scope (and nested Areas/Tasks) from this template.""" scope = Scope.objects.create( name=name or self.name, account=account, account_address=account_address, description=description if description is not None else self.description, is_active=is_active, ) # Create Areas and Tasks preserving order area_templates = self.area_templates.all().order_by('order', 'name') for at in area_templates: area = Area.objects.create(scope=scope, name=at.name, order=at.order) task_templates = at.task_templates.all().order_by('order', 'id') tasks_to_create = [ Task( area=area, description=tt.description, checklist_description=tt.checklist_description, frequency=tt.frequency, order=tt.order, is_conditional=tt.is_conditional, estimated_minutes=tt.estimated_minutes, ) for tt in task_templates ] if tasks_to_create: Task.objects.bulk_create(tasks_to_create) return scope class AreaTemplate(BaseModel): """Reusable area definition belonging to a ScopeTemplate""" scope_template = models.ForeignKey(ScopeTemplate, on_delete=models.CASCADE, related_name='area_templates') name = models.CharField(max_length=100) order = models.PositiveIntegerField(default=0) class Meta: ordering = ['scope_template', 'order', 'name'] verbose_name = "Area Template" verbose_name_plural = "Area Templates" indexes = [models.Index(fields=['scope_template', 'order'])] def __str__(self): return f"{self.scope_template.name} - {self.name}" class TaskTemplate(BaseModel): """Reusable task definition belonging to an AreaTemplate""" area_template = models.ForeignKey(AreaTemplate, on_delete=models.CASCADE, related_name='task_templates') 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_template', 'order'] verbose_name = "Task Template" verbose_name_plural = "Task Templates" indexes = [ models.Index(fields=['area_template', 'order']), models.Index(fields=['frequency']), ] def __str__(self): return f"{self.area_template.name}: {self.description[:50]}..."