Add Dispatch_V0.1.1
This commit is contained in:
230
Dispatch_V0.1.1/application/document_flow_service.py
Normal file
230
Dispatch_V0.1.1/application/document_flow_service.py
Normal 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"
|
||||
)
|
||||
Reference in New Issue
Block a user