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,20 @@
# -*- coding: utf-8 -*-
# hub/ticket/application/__init__.py
"""Публичный application-контур Ticket."""
from .archive_service import ArchiveService
from .document_flow_service import DocumentFlowService
from .report_signing_service import ReportSigningService
from .specialist_assignment_service import SpecialistAssignmentService
from .task_application_service import TaskApplicationService
from .ticket_application_api import TicketApplicationApi
__all__ = [
"ArchiveService",
"DocumentFlowService",
"ReportSigningService",
"SpecialistAssignmentService",
"TaskApplicationService",
"TicketApplicationApi",
]

View File

@@ -0,0 +1,80 @@
# -*- coding: utf-8 -*-
# hub/ticket/application/archive_service.py
"""Application-сервис архивации задач Ticket."""
from __future__ import annotations
from error_logger import log_exception
from domain import ArchiveRecordSnapshot, TicketStateService
from domain.task import TicketTask
from state import ArchiveRecordRepository, TicketStateApi
from .document_flow_service import DocumentFlowService
class ArchiveService:
"""Команда архивации поверх доменного сервиса и state API."""
def __init__(
self,
state: TicketStateApi,
state_service: TicketStateService,
archive_repository: ArchiveRecordRepository | None = None,
document_service: DocumentFlowService | None = None,
):
self._state = state
self._state_service = state_service
self._archive_repository = archive_repository or ArchiveRecordRepository()
self._document_service = document_service
def archive_task(self, task_id: int):
"""Перевести задачу в архив и сохранить архивную запись."""
snapshot = self._state.get_task(task_id)
if snapshot is None:
return None
self.ensure_archive_record(snapshot)
result = self._state_service.move_task_to_archive(
task_id,
TicketTask.from_snapshot(snapshot),
)
if result.task is None:
return None
archived_snapshot = result.task.to_snapshot()
self._state.upsert_task(archived_snapshot)
return archived_snapshot
def list_archive_records(self) -> list[ArchiveRecordSnapshot]:
"""Вернуть все архивные записи из файлового хранилища."""
return self._archive_repository.list_records()
def ensure_archive_record(self, snapshot) -> None:
"""Сохранить архивную запись, если её ещё нет для данного цикла задачи."""
try:
cycle_token = self._build_cycle_token(snapshot)
if self._archive_repository.has_record(snapshot.task_id, cycle_token):
return
documents = self._collect_cycle_documents(snapshot)
self._archive_repository.save_record(snapshot, documents)
except Exception as exc:
log_exception(__name__, "ArchiveService.ensure_archive_record", exc)
def _collect_cycle_documents(self, snapshot) -> list:
"""Вернуть документы только текущего цикла задачи."""
if self._document_service is None:
return []
cycle_token = self._build_cycle_token(snapshot)
all_docs = self._document_service.list_documents()
return [
d for d in all_docs
if d.task_id == snapshot.task_id and cycle_token in d.document_id
]
@staticmethod
def _build_cycle_token(snapshot) -> str:
from datetime import datetime as _dt
if snapshot.created_at is not None:
return snapshot.created_at.strftime("%Y%m%d_%H%M%S")
return _dt.now().strftime("%Y%m%d_%H%M%S")

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

View File

@@ -0,0 +1,50 @@
# -*- coding: utf-8 -*-
# hub/ticket/application/report_signing_service.py
"""Application-сервис подписания отчётов Ticket."""
from __future__ import annotations
from domain.task import TicketTask
from domain.ticket_constants import STATE_CONFIRMATION
from state import TicketStateApi
class ReportSigningService:
"""Команды подписания отчётов поверх канонического state API."""
def __init__(self, state: TicketStateApi):
self._state = state
def sign_report(self, task_id: int, report_type: str):
"""Подписать диагностический или ремонтный отчёт."""
snapshot = self._state.get_task(task_id)
if snapshot is None:
return None
if report_type not in {"diagnostic", "repair"}:
return None
self._state.sign_report(task_id, report_type)
return self._state.get_task(task_id)
def sign_acceptance_report(self, task_id: int):
"""Подписать акт приёмки без смены доменного состояния."""
snapshot = self._state.get_task(task_id)
if snapshot is None:
return None
task_data = TicketTask.from_snapshot(snapshot).to_record()
task_data["acceptance_report_signed"] = True
return self._state.update_task(task_data)
def can_advance_to_confirmation(self, task_id: int) -> bool:
"""Проверить готовность задачи к переходу в подтверждение."""
return self._state.can_advance_to_confirmation(task_id)
def can_advance_to_completed(self, task_id: int) -> bool:
"""Проверить готовность задачи к переходу в выполненные."""
snapshot = self._state.get_task(task_id)
if snapshot is None:
return False
return (
snapshot.state_code == STATE_CONFIRMATION
and snapshot.acceptance_report_signed
)

View File

@@ -0,0 +1,42 @@
# -*- coding: utf-8 -*-
# hub/ticket/application/specialist_assignment_service.py
"""Application-сервис назначения специалистов в Ticket."""
from __future__ import annotations
from domain.task import TicketTask
from state import TicketStateApi
SPECIALIST_PHOTOS = {
"Иванов Алексей Сергеевич": "specialist1.png",
"Петрова Мария Владимировна": "specialist2.png",
"Сидоров Дмитрий Иванович": "specialist3.png",
"Козлова Анна Петровна": "specialist4.png",
"Васильев Сергей Николаевич": "specialist5.png",
"Николаева Ольга Дмитриевна": "specialist6.png",
"Фёдоров Андрей Викторович": "specialist7.png",
"Орлова Екатерина Александровна": "specialist8.png",
}
class SpecialistAssignmentService:
"""Команда назначения специалиста поверх канонического state API."""
def __init__(self, state: TicketStateApi):
self._state = state
def assign_specialist(self, task_id: int, specialist_name: str):
"""Назначить специалиста и сохранить фотографию профиля."""
snapshot = self._state.get_task(task_id)
if snapshot is None:
return None
task_data = TicketTask.from_snapshot(snapshot).to_record()
task_data["assigned_specialist"] = specialist_name
task_data["specialist_photo"] = SPECIALIST_PHOTOS.get(specialist_name, "")
return self._state.update_task(task_data)
def list_specialists(self) -> list[str]:
"""Вернуть канонический список доступных специалистов."""
return list(SPECIALIST_PHOTOS.keys())

View File

@@ -0,0 +1,321 @@
# -*- coding: utf-8 -*-
# hub/ticket/application/task_application_service.py
"""Единый application-фасад Ticket поверх state, domain и hardware gateway."""
from __future__ import annotations
from collections.abc import Mapping
from typing import Any
from PySide6.QtCore import QObject, Signal
from error_logger import log_exception
from domain import (
ArchiveRecordSnapshot,
TicketConnectionStatus,
TicketDocumentSnapshot,
TicketHardwareStatus,
TicketStateService,
TicketTaskSnapshot,
)
from domain.task import TicketTask
from domain.ticket_constants import STATE_COMPLETED, STATE_REFUSED
from services import ServiceManager, TicketHardwareGateway
from state import TicketRuntimeState
from .archive_service import ArchiveService
from .document_flow_service import DocumentFlowService
from .report_signing_service import ReportSigningService
from .specialist_assignment_service import SpecialistAssignmentService
class TaskApplicationService(QObject):
"""Канонический application facade Ticket."""
task_updated = Signal(object)
task_removed = Signal(int)
connection_changed = Signal(str, str)
active_view_changed = Signal(str)
state_loaded = Signal()
com_connection_changed = Signal(bool, str)
button_initialization_changed = Signal(bool, int)
def __init__(
self,
state: TicketRuntimeState | None = None,
hardware_gateway: TicketHardwareGateway | None = None,
state_service: TicketStateService | None = None,
specialist_service: SpecialistAssignmentService | None = None,
report_service: ReportSigningService | None = None,
archive_service: ArchiveService | None = None,
document_service: DocumentFlowService | None = None,
parent: QObject | None = None,
):
super().__init__(parent)
self._state = state or TicketRuntimeState()
self._hardware_gateway = hardware_gateway or ServiceManager(parent=self)
self._state_service = state_service or TicketStateService()
self._specialist_service = specialist_service or SpecialistAssignmentService(self._state)
self._report_service = report_service or ReportSigningService(self._state)
self._document_service = document_service or DocumentFlowService(self._state)
self._archive_service = archive_service or ArchiveService(
self._state,
self._state_service,
document_service=self._document_service,
)
self._started = False
self._connect_state_signals()
def start(self) -> None:
"""Загрузить state, синхронизировать gateway и запустить сервисы."""
if self._started:
return
self._started = True
self._state.load()
self._hardware_gateway.reset_button_states()
for task in self._state.list_tasks():
self._hardware_gateway.set_button_state(task.task_id, task.state_code)
self._hardware_gateway.set_observer(self)
self._hardware_gateway.start()
def stop(self) -> None:
"""Остановить gateway и снять observer."""
if not self._started:
return
self._started = False
self._hardware_gateway.set_observer(None)
self._hardware_gateway.stop()
def list_tasks(self) -> list[TicketTaskSnapshot]:
return list(self._state.list_tasks())
def list_active_tasks(self) -> list[TicketTaskSnapshot]:
return list(self._state.list_active_tasks())
def list_archived_tasks(self) -> list[TicketTaskSnapshot]:
return list(self._state.list_archived_tasks())
def list_archive_records(self) -> list[ArchiveRecordSnapshot]:
return self._archive_service.list_archive_records()
def get_task(self, task_id: int) -> TicketTaskSnapshot | None:
return self._state.get_task(task_id)
def handle_task_action(self, raw_action: Mapping[str, Any]) -> TicketTaskSnapshot | None:
"""Обработать аппаратное действие через доменную state-machine."""
button_id = self._normalize_int(raw_action.get("button_id"))
hardware_state = self._normalize_int(raw_action.get("hardware_state"))
if button_id is None or hardware_state is None:
return None
current_snapshot = self._state.get_task(button_id)
current_task = (
TicketTask.from_snapshot(current_snapshot)
if current_snapshot is not None
else None
)
result = self._state_service.handle_hardware_signal(
button_id,
hardware_state,
current_task,
)
if result.task is not None:
self._assign_sequence_on_terminal_state(result.task)
updated_snapshot = result.task.to_snapshot()
self._state.upsert_task(updated_snapshot)
self._hardware_gateway.set_button_state(
updated_snapshot.task_id,
updated_snapshot.state_code,
)
self._auto_save_archive_record(updated_snapshot)
return updated_snapshot
if current_snapshot is not None:
self._hardware_gateway.set_button_state(
current_snapshot.task_id,
current_snapshot.state_code,
)
return current_snapshot
def set_active_view(self, view_name: str) -> None:
self._state.active_view = view_name
def submit_new_task(self, snapshot: TicketTaskSnapshot) -> TicketTaskSnapshot | None:
"""Зарегистрировать заявку, созданную через форму Dispatch.
Назначение: добавляет в runtime-state готовый снимок задачи в
состоянии «Новая заявка» (`STATE_TODO`). Вызывает `upsert_task`,
который эмитит `task_updated` — доска подхватывает карточку без
дополнительной шины событий. Аппаратный шлюз в Dispatch
отключён (`NullHardwareGateway`), поэтому синхронизация
состояния кнопок не выполняется.
"""
if snapshot is None:
return None
self._state.upsert_task(snapshot)
return self._state.get_task(snapshot.task_id)
def allocate_new_task_id(self) -> int:
"""Выдать свободный идентификатор для заявки, созданной диспетчером."""
existing_ids = [task.task_id for task in self._state.list_tasks()]
base = max(existing_ids, default=0)
# Резервируем диапазон 1..99 за аппаратными button_id, чтобы при
# будущей реальной интеграции с COM-каналом идентификаторы заявок
# из формы не пересекались с физическими кнопками.
return max(base, 99) + 1
def assign_specialist(
self,
task_id: int,
specialist_name: str,
) -> TicketTaskSnapshot | None:
snapshot = self._specialist_service.assign_specialist(task_id, specialist_name)
return self._sync_gateway_state(snapshot)
def list_specialists(self) -> list[str]:
return self._specialist_service.list_specialists()
def sign_report(
self,
task_id: int,
report_type: str,
) -> TicketTaskSnapshot | None:
snapshot = self._report_service.sign_report(task_id, report_type)
return self._sync_gateway_state(snapshot)
def sign_acceptance_report(self, task_id: int) -> TicketTaskSnapshot | None:
snapshot = self._report_service.sign_acceptance_report(task_id)
return self._sync_gateway_state(snapshot)
def archive_task(self, task_id: int) -> TicketTaskSnapshot | None:
snapshot = self._archive_service.archive_task(task_id)
return self._sync_gateway_state(snapshot)
def refuse_task(
self,
task_id: int,
refusal_reason: str,
) -> TicketTaskSnapshot | None:
normalized_reason = str(refusal_reason or "").strip()
if not normalized_reason:
return None
current_snapshot = self._state.get_task(task_id)
current_task = (
TicketTask.from_snapshot(current_snapshot)
if current_snapshot is not None
else None
)
result = self._state_service.mark_task_as_refused(
task_id,
current_task,
normalized_reason,
)
if result.task is None:
return None
self._assign_sequence_on_terminal_state(result.task)
snapshot = result.task.to_snapshot()
self._state.upsert_task(snapshot)
self._auto_save_archive_record(snapshot)
return self._sync_gateway_state(snapshot)
def create_diagnostic_report(
self, task_id: int, initial_cause: str, actual_cause: str,
) -> TicketDocumentSnapshot:
return self._document_service.create_diagnostic_report(
task_id, initial_cause, actual_cause,
)
def create_repair_report(
self, task_id: int, work_done: str, used_parts: str, recommendations: str,
) -> TicketDocumentSnapshot:
return self._document_service.create_repair_report(
task_id, work_done, used_parts, recommendations,
)
def create_acceptance_report(
self, task_id: int, work_description: str,
executor_signature: str, customer_signature: str,
) -> TicketDocumentSnapshot:
return self._document_service.create_acceptance_report(
task_id, work_description, executor_signature, customer_signature,
)
def list_documents(
self,
document_type: str | None = None,
) -> list[TicketDocumentSnapshot]:
return self._document_service.list_documents(document_type)
def can_advance_to_confirmation(self, task_id: int) -> bool:
return self._report_service.can_advance_to_confirmation(task_id)
def can_advance_to_completed(self, task_id: int) -> bool:
return self._report_service.can_advance_to_completed(task_id)
def get_active_view(self) -> str:
return self._state.active_view
def get_gateway_status(self) -> TicketHardwareStatus:
return self._hardware_gateway.get_status()
def on_task_action(self, raw_action: Mapping[str, Any]) -> None:
"""Observer callback от hardware gateway."""
try:
self.handle_task_action(raw_action)
except Exception as exc:
log_exception(__name__, "TaskApplicationService.on_task_action", exc)
def on_gateway_status(self, status: TicketHardwareStatus) -> None:
"""Observer callback обновления статуса hardware gateway."""
if status.connection_status == TicketConnectionStatus.ERROR:
self._state.set_error(status.message)
else:
self._state.connection_status = status.connection_status
self._state.set_com_connection(
status.connection_status == TicketConnectionStatus.CONNECTED,
status.message,
)
self._state.set_button_initialization(
status.buttons_initialized,
status.button_count,
)
def on_gateway_error(self, message: str) -> None:
"""Observer callback ошибки hardware gateway."""
self._state.set_error(message)
def _connect_state_signals(self) -> None:
self._state.task_updated.connect(self.task_updated.emit)
self._state.task_removed.connect(self.task_removed.emit)
self._state.connection_changed.connect(self.connection_changed.emit)
self._state.active_view_changed.connect(self.active_view_changed.emit)
self._state.state_loaded.connect(self.state_loaded.emit)
self._state.com_connection_changed.connect(self.com_connection_changed.emit)
self._state.button_initialization_changed.connect(
self.button_initialization_changed.emit
)
def _sync_gateway_state(
self,
snapshot: TicketTaskSnapshot | None,
) -> TicketTaskSnapshot | None:
if snapshot is None:
return None
self._hardware_gateway.set_button_state(snapshot.task_id, snapshot.state_code)
return snapshot
def _assign_sequence_on_terminal_state(self, task: TicketTask) -> None:
"""Присвоить сквозной номер при переходе в Выполненные/Отказ."""
if task.state_code in {STATE_COMPLETED, STATE_REFUSED}:
task.sequence_number = self._state.next_sequence_number()
def _auto_save_archive_record(self, snapshot: TicketTaskSnapshot) -> None:
if snapshot.state_code in {STATE_COMPLETED, STATE_REFUSED}:
self._archive_service.ensure_archive_record(snapshot)
@staticmethod
def _normalize_int(value: Any) -> int | None:
try:
return int(value)
except (TypeError, ValueError):
return None

View File

@@ -0,0 +1,115 @@
# -*- coding: utf-8 -*-
# hub/ticket/application/ticket_application_api.py
"""Публичный application API модуля Ticket."""
from __future__ import annotations
from typing import Any, Mapping, Protocol, Sequence
from domain import TicketDocumentSnapshot, TicketTaskSnapshot
class TicketApplicationApi(Protocol):
"""Контракт orchestration-слоя между UI, state и сервисами."""
def start(self) -> None:
"""Инициализировать application-слой Ticket."""
def stop(self) -> None:
"""Остановить активные операции и освободить ресурсы."""
def list_tasks(self) -> Sequence[TicketTaskSnapshot]:
"""Вернуть текущий срез задач для UI."""
def list_active_tasks(self) -> Sequence[TicketTaskSnapshot]:
"""Вернуть активные задачи для UI."""
def list_archived_tasks(self) -> Sequence[TicketTaskSnapshot]:
"""Вернуть архивные задачи для UI."""
def get_task(self, task_id: int) -> TicketTaskSnapshot | None:
"""Вернуть задачу по идентификатору."""
def handle_task_action(
self,
raw_action: Mapping[str, Any],
) -> TicketTaskSnapshot | None:
"""Обработать входящее действие от аппаратного или mock-шлюза."""
def set_active_view(self, view_name: str) -> None:
"""Переключить активное представление Ticket."""
def assign_specialist(
self,
task_id: int,
specialist_name: str,
) -> TicketTaskSnapshot | None:
"""Назначить специалиста на задачу."""
def list_specialists(self) -> Sequence[str]:
"""Вернуть список доступных специалистов."""
def sign_report(
self,
task_id: int,
report_type: str,
) -> TicketTaskSnapshot | None:
"""Подписать диагностический или ремонтный отчёт."""
def sign_acceptance_report(self, task_id: int) -> TicketTaskSnapshot | None:
"""Подписать акт приёмки."""
def archive_task(self, task_id: int) -> TicketTaskSnapshot | None:
"""Перевести задачу в архив."""
def refuse_task(
self,
task_id: int,
refusal_reason: str,
) -> TicketTaskSnapshot | None:
"""Перевести задачу в отказ."""
def create_diagnostic_report(
self,
task_id: int,
initial_cause: str,
actual_cause: str,
) -> TicketDocumentSnapshot:
"""Создать и подписать диагностический отчёт."""
def create_repair_report(
self,
task_id: int,
work_done: str,
used_parts: str,
recommendations: str,
) -> TicketDocumentSnapshot:
"""Создать и подписать ремонтный отчёт."""
def create_acceptance_report(
self,
task_id: int,
work_description: str,
executor_signature: str,
customer_signature: str,
) -> TicketDocumentSnapshot:
"""Создать и подписать акт приёмки."""
def list_documents(
self,
document_type: str | None = None,
) -> Sequence[TicketDocumentSnapshot]:
"""Вернуть список документов Ticket."""
def can_advance_to_confirmation(self, task_id: int) -> bool:
"""Проверить готовность задачи к переходу в подтверждение."""
def can_advance_to_completed(self, task_id: int) -> bool:
"""Проверить готовность задачи к переходу в выполненные."""
def get_active_view(self) -> str:
"""Вернуть имя активного внутреннего представления Ticket."""
def get_gateway_status(self):
"""Вернуть последний известный статус hardware gateway."""