Files
Dispatch/Dispatch_V0.1.1/application/document_flow_service.py
2026-04-29 08:18:54 +04:00

231 lines
9.6 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# -*- coding: utf-8 -*-
# hub/ticket/application/document_flow_service.py
"""Application-сервис генерации документов Ticket."""
from __future__ import annotations
from typing import Callable
from domain import TicketDocumentSnapshot, TicketTaskSnapshot, parse_location_parts
from domain.task import TicketTask
from domain.ticket_constants import STATE_CONFIRMATION, STATE_IN_PROGRESS
from state import TicketDocumentRepository, TicketStateApi
class DocumentFlowService:
"""Канонический document-flow Ticket поверх state и файлового репозитория."""
def __init__(
self,
state: TicketStateApi,
repository: TicketDocumentRepository | None = None,
):
self._state = state
self._repository = repository or TicketDocumentRepository()
def create_diagnostic_report(
self,
task_id: int,
initial_cause: str,
actual_cause: str,
) -> TicketDocumentSnapshot:
task = self._get_task_or_raise(task_id)
self._ensure_in_progress(task, "Диагностический отчёт")
self._ensure_specialist_assigned(task)
if task.diagnostic_report_signed:
raise ValueError("Диагностический отчёт уже подписан.")
if not initial_cause.strip() or not actual_cause.strip():
raise ValueError("Заполните первичное и вторичное заключения.")
payload = self._base_payload(task)
payload.update(
{
"initial_cause": initial_cause.strip(),
"actual_cause": actual_cause.strip(),
}
)
document = self._save_document(
task=task,
document_type="diagnostic",
title=f"Диагностический отчёт по задаче #{task.sequence_number or task.task_id}",
summary=actual_cause.strip(),
payload=payload,
content_builder=self._render_diagnostic_report,
)
self._state.sign_report(task.task_id, "diagnostic")
return document
def create_repair_report(
self,
task_id: int,
work_done: str,
used_parts: str,
recommendations: str,
) -> TicketDocumentSnapshot:
task = self._get_task_or_raise(task_id)
self._ensure_in_progress(task, "Ремонтный отчёт")
self._ensure_specialist_assigned(task)
if task.repair_report_signed:
raise ValueError("Ремонтный отчёт уже подписан.")
if not work_done.strip():
raise ValueError("Заполните поле 'Выполненные работы'.")
payload = self._base_payload(task)
payload.update(
{
"work_done": work_done.strip(),
"used_parts": used_parts.strip(),
"recommendations": recommendations.strip(),
}
)
document = self._save_document(
task=task,
document_type="repair",
title=f"Ремонтный отчёт по задаче #{task.sequence_number or task.task_id}",
summary=work_done.strip(),
payload=payload,
content_builder=self._render_repair_report,
)
self._state.sign_report(task.task_id, "repair")
return document
def create_acceptance_report(
self,
task_id: int,
work_description: str,
executor_signature: str,
customer_signature: str,
) -> TicketDocumentSnapshot:
task = self._get_task_or_raise(task_id)
if task.state_code != STATE_CONFIRMATION:
raise ValueError("Акт приёмки доступен только в состоянии подтверждения.")
if task.acceptance_report_signed:
raise ValueError("Акт приёмки уже подписан.")
if not task.diagnostic_report_signed or not task.repair_report_signed:
raise ValueError("Сначала подпишите диагностический и ремонтный отчёты.")
if not work_description.strip():
raise ValueError("Заполните описание выполненных работ.")
if not executor_signature.strip() or not customer_signature.strip():
raise ValueError("Укажите подписи исполнителя и заказчика.")
payload = self._base_payload(task)
payload.update(
{
"work_description": work_description.strip(),
"executor_signature": executor_signature.strip(),
"customer_signature": customer_signature.strip(),
}
)
document = self._save_document(
task=task,
document_type="acceptance",
title=f"Акт приёмки по задаче #{task.sequence_number or task.task_id}",
summary=work_description.strip(),
payload=payload,
content_builder=self._render_acceptance_report,
)
task_data = TicketTask.from_snapshot(task).to_record()
task_data["acceptance_report_signed"] = True
self._state.update_task(task_data)
return document
def list_documents(
self,
document_type: str | None = None,
) -> list[TicketDocumentSnapshot]:
"""Вернуть отсортированный список документов Ticket."""
return self._repository.list_documents(document_type)
def _save_document(
self,
task: TicketTaskSnapshot,
document_type: str,
title: str,
summary: str,
payload: dict[str, str],
content_builder: Callable[[dict[str, str]], str],
) -> TicketDocumentSnapshot:
document = self._repository.save_document(
task=task,
document_type=document_type,
title=title,
summary=summary,
content=content_builder(payload),
payload=payload,
)
if document is None:
raise ValueError("Не удалось сохранить документ Ticket.")
return document
def _get_task_or_raise(self, task_id: int) -> TicketTaskSnapshot:
task = self._state.get_task(task_id)
if task is None:
raise ValueError(f"Задача #{task_id} не найдена.")
return task
@staticmethod
def _ensure_in_progress(task: TicketTaskSnapshot, document_name: str) -> None:
if task.state_code != STATE_IN_PROGRESS:
raise ValueError(f"{document_name} можно подписать только в состоянии 'В работе'.")
@staticmethod
def _ensure_specialist_assigned(task: TicketTaskSnapshot) -> None:
if not task.assigned_specialist.strip():
raise ValueError("Сначала назначьте специалиста.")
@staticmethod
def _base_payload(task: TicketTaskSnapshot) -> dict[str, str]:
institution, room, device = parse_location_parts(task.location)
return {
"task_id": str(task.sequence_number or task.task_id),
"institution": institution,
"room": room,
"device": device,
"location": task.location,
"specialist": task.assigned_specialist,
}
@staticmethod
def _render_diagnostic_report(payload: dict[str, str]) -> str:
return (
f"ДИАГНОСТИЧЕСКИЙ ОТЧЁТ #{payload['task_id']}\n\n"
f"Учреждение: {payload.get('institution', '')}\n"
f"Оборудование: {payload.get('device', '')}\n"
f"Кабинет: {payload.get('room', '')}\n"
f"Специалист: {payload.get('specialist', '')}\n\n"
"Первичное заключение:\n"
f"{payload.get('initial_cause', '')}\n\n"
"Вторичное заключение:\n"
f"{payload.get('actual_cause', '')}\n"
)
@staticmethod
def _render_repair_report(payload: dict[str, str]) -> str:
return (
f"РЕМОНТНЫЙ ОТЧЁТ #{payload['task_id']}\n\n"
f"Учреждение: {payload.get('institution', '')}\n"
f"Оборудование: {payload.get('device', '')}\n"
f"Кабинет: {payload.get('room', '')}\n"
f"Специалист: {payload.get('specialist', '')}\n\n"
"Выполненные работы:\n"
f"{payload.get('work_done', '')}\n\n"
"Использованные запчасти:\n"
f"{payload.get('used_parts', '')}\n\n"
"Рекомендации:\n"
f"{payload.get('recommendations', '')}\n"
)
@staticmethod
def _render_acceptance_report(payload: dict[str, str]) -> str:
return (
f"АКТ ПРИЁМКИ #{payload['task_id']}\n\n"
f"Учреждение: {payload.get('institution', '')}\n"
f"Оборудование: {payload.get('device', '')}\n"
f"Кабинет: {payload.get('room', '')}\n"
f"Специалист: {payload.get('specialist', '')}\n\n"
"Описание выполненных работ:\n"
f"{payload.get('work_description', '')}\n\n"
"Исполнитель:\n"
f"{payload.get('executor_signature', '')}\n\n"
"Заказчик:\n"
f"{payload.get('customer_signature', '')}\n"
)