61 lines
1.4 KiB
Python
61 lines
1.4 KiB
Python
from __future__ import annotations
|
|
|
|
from datetime import datetime, UTC
|
|
|
|
from app.storage.models import StoredTask
|
|
|
|
|
|
class InvalidTransitionError(Exception):
|
|
"""Raised when task workflow transition is invalid."""
|
|
|
|
|
|
def start_task(task: StoredTask) -> StoredTask:
|
|
"""
|
|
Transition task from backlog → in_progress.
|
|
|
|
Rules:
|
|
- Idempotent: if already started, do not change started_at
|
|
- Cannot start a task that is already done
|
|
"""
|
|
# already done → invalid
|
|
if task.done_at is not None:
|
|
raise InvalidTransitionError("invalid_transaction")
|
|
|
|
# already started → idempotent (no change)
|
|
if task.started_at is not None:
|
|
return task
|
|
|
|
return StoredTask(
|
|
id=task.id,
|
|
title=task.title,
|
|
created_at=task.created_at,
|
|
started_at=datetime.now(UTC),
|
|
done_at=task.done_at,
|
|
)
|
|
|
|
|
|
def complete_task(task: StoredTask) -> StoredTask:
|
|
"""
|
|
Transition task from in_progress → done.
|
|
|
|
Rules:
|
|
- Allowed only if task is started
|
|
- Idempotent: if already done, do not change done_at
|
|
"""
|
|
# not started → invalid
|
|
if task.started_at is None:
|
|
raise InvalidTransitionError("invalid_transaction")
|
|
|
|
# already done → idempotent
|
|
if task.done_at is not None:
|
|
return task
|
|
|
|
return StoredTask(
|
|
id=task.id,
|
|
title=task.title,
|
|
created_at=task.created_at,
|
|
started_at=task.started_at,
|
|
done_at=datetime.now(UTC),
|
|
)
|
|
|