"""Журнал действий по задачам (ТЗ 3.20): кто, что и когда.
Единственная точка записи в `task_events` — и для REST, и для MCP: иначе «кто
закрыл задачу» отвечалось бы по-разному в двух путях. Запись идёт в ту же
транзакцию, что и само действие; коммитит вызывающий.
Заголовок хранится снапшотом: запись должна читаться после удаления задачи.
"""
from sqlalchemy.orm import Session
from app.actor import Actor
from app.models import Task, TaskEvent
KIND_CREATED = "created"
KIND_ELIGIBLE = "eligible_changed"
KIND_CLAIMED = "claimed"
KIND_RELEASED = "released"
KIND_COMPLETED = "completed"
KIND_ACCEPTED = "accepted"
KIND_REJECTED = "rejected"
KIND_STATUS = "status_changed"
KIND_DELETED = "deleted"
MAX_TITLE = 500
MAX_NAME = 100
MAX_COMMENT = 2000
MAX_KIND = 24
def status_transition(old: str, new: str) -> str:
"""Переход статуса машинной записью («to_do→in_progress»).
Коды, а не слова: интерфейс подпишет их своим языком, а журнал не хранит
переводов — записи должны читаться одинаково и через год.
"""
return f"{old}→{new}"
def _event(
task: Task,
kind: str,
actor: Actor,
comment: str | None,
*,
task_id: int | None,
project_id: int | None,
) -> TaskEvent:
return TaskEvent(
user_id=task.user_id,
task_id=task_id,
task_title=task.title[:MAX_TITLE],
project_id=project_id,
kind=kind[:MAX_KIND],
actor_kind=actor.kind,
actor_name=actor.name[:MAX_NAME],
via=actor.via,
comment=comment[:MAX_COMMENT] if comment else None,
)
def log_event(
db: Session, task: Task, kind: str, actor: Actor, comment: str | None = None
) -> TaskEvent:
"""Записать действие по задаче (создание, статус, взятие, закрытие, приёмка…)."""
event = _event(task, kind, actor, comment, task_id=task.id, project_id=task.project_id)
db.add(event)
return event
def log_deleted(db: Session, task: Task, actor: Actor) -> TaskEvent:
"""Запись об удалении: задача уже уходит, ссылку не оставляем — только снапшот.
(FK с ON DELETE SET NULL обнулил бы её сам, но в SQLite внешние ключи в
тестах не проверяются — журнал остался бы ссылаться на несуществующую задачу.)
"""
event = _event(task, KIND_DELETED, actor, None, task_id=None, project_id=task.project_id)
db.add(event)
return event