# -*- 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" )