from __future__ import annotations from datetime import date, datetime, timedelta from sqlalchemy import select from ..extensions import db from ..models import TaskInstance, TaskTemplate from .badges import evaluate_task_badges from .dates import add_months, today_local def refresh_task_status(task: TaskInstance, reference_date: date | None = None) -> bool: status = task.compute_status(reference_date or today_local()) if task.status != status: task.status = status return True return False def refresh_task_statuses(tasks: list[TaskInstance]) -> None: dirty = any(refresh_task_status(task) for task in tasks) if dirty: db.session.commit() def create_task_template_and_instance(form) -> TaskInstance: template = TaskTemplate( title=form.title.data.strip(), description=(form.description.data or "").strip(), default_points=form.default_points.data, default_assigned_user_id=form.assigned_user_id.data, recurrence_interval_value=form.recurrence_interval_value.data if form.recurrence_interval_unit.data != "none" else None, recurrence_interval_unit=form.recurrence_interval_unit.data, active=form.active.data, ) db.session.add(template) db.session.flush() task = TaskInstance( task_template_id=template.id, title=template.title, description=template.description, assigned_user_id=template.default_assigned_user_id, due_date=form.due_date.data, points_awarded=template.default_points, status="open", ) refresh_task_status(task, form.due_date.data) db.session.add(task) db.session.commit() return task def update_template_and_instance(task: TaskInstance, form) -> TaskInstance: template = task.task_template template.title = form.title.data.strip() template.description = (form.description.data or "").strip() template.default_points = form.default_points.data template.default_assigned_user_id = form.assigned_user_id.data template.recurrence_interval_unit = form.recurrence_interval_unit.data template.recurrence_interval_value = ( form.recurrence_interval_value.data if form.recurrence_interval_unit.data != "none" else None ) template.active = form.active.data task.title = template.title task.description = template.description task.assigned_user_id = template.default_assigned_user_id task.points_awarded = template.default_points task.due_date = form.due_date.data refresh_task_status(task, form.due_date.data) db.session.commit() return task def _next_due_date(task: TaskInstance) -> date | None: template = task.task_template value = template.recurrence_interval_value if template.recurrence_interval_unit == "none" or not value: return None if template.recurrence_interval_unit == "days": return task.due_date + timedelta(days=value) if template.recurrence_interval_unit == "weeks": return task.due_date + timedelta(weeks=value) if template.recurrence_interval_unit == "months": return add_months(task.due_date, value) return None def ensure_next_recurring_task(task: TaskInstance) -> TaskInstance | None: next_due = _next_due_date(task) if not next_due or not task.task_template.active: return None existing = db.session.scalar( select(TaskInstance).where( TaskInstance.task_template_id == task.task_template_id, TaskInstance.due_date == next_due, ) ) if existing: return existing next_task = TaskInstance( task_template_id=task.task_template_id, title=task.task_template.title, description=task.task_template.description, assigned_user_id=task.task_template.default_assigned_user_id, due_date=next_due, points_awarded=task.task_template.default_points, status="open", ) refresh_task_status(next_task, today_local()) db.session.add(next_task) return next_task def complete_task(task: TaskInstance, completed_by_user_id: int) -> TaskInstance: if not task.completed_at: task.completed_at = datetime.utcnow() task.completed_by_user_id = completed_by_user_id task.status = "completed" ensure_next_recurring_task(task) db.session.commit() if task.completed_by_user: evaluate_task_badges(task.completed_by_user) return task