Add Dispatch_V0.1.1

This commit is contained in:
2026-04-29 08:18:54 +04:00
commit a7ede6ded4
404 changed files with 39167 additions and 0 deletions

View File

@@ -0,0 +1,230 @@
# -*- 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"
)