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

108 lines
4.0 KiB
Python

# 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]}..."