Files
2026-04-29 08:18:54 +04:00

169 lines
6.8 KiB
Python

# -*- coding: utf-8 -*-
# hub/ticket/domain/task.py
"""Доменная сущность задачи Ticket и сериализация её состояния."""
from __future__ import annotations
from dataclasses import dataclass
from datetime import datetime
from typing import Any, Mapping
from .ticket_types import TicketTaskSnapshot
def _normalize_datetime(value: Any) -> datetime | None:
"""Преобразовать строку или datetime в datetime."""
if value is None or value == "":
return None
if isinstance(value, datetime):
return value
if not isinstance(value, str):
return None
try:
if "T" in value:
return datetime.fromisoformat(value.replace("Z", "+00:00"))
return datetime.strptime(value, "%Y-%m-%d %H:%M:%S")
except ValueError:
return None
def _normalize_color(value: Any) -> str:
"""Нормализовать представление цвета до hex-строки."""
if isinstance(value, str) and value.startswith("#") and len(value) >= 4:
return value
name_getter = getattr(value, "name", None)
if callable(name_getter):
try:
normalized = name_getter()
if isinstance(normalized, str) and normalized.startswith("#"):
return normalized
except Exception as _exc:
return "#FFFFFF"
return "#FFFFFF"
def _normalize_int(value: Any, default: int | None = None) -> int | None:
"""Нормализовать число, сохранив валидное значение 0."""
if value is None or value == "":
return default
try:
return int(value)
except (TypeError, ValueError):
return default
@dataclass(slots=True)
class TicketTask:
"""Каноническая доменная задача Ticket."""
task_id: int
location: str
state_code: int
state_name: str
action_text: str = ""
color_hex: str = "#FFFFFF"
created_at: datetime | None = None
completed_at: datetime | None = None
refused_from_state: int | None = None
refusal_reason: str = ""
assigned_specialist: str = ""
specialist_photo: str = ""
diagnostic_report_signed: bool = False
repair_report_signed: bool = False
acceptance_report_signed: bool = False
sequence_number: int = 0
@classmethod
def from_record(cls, record: Mapping[str, Any]) -> TicketTask | None:
"""Создать задачу из записи файлового хранилища."""
raw_task_id = record.get("button_id")
if raw_task_id is None:
return None
try:
task_id = int(raw_task_id)
except (TypeError, ValueError):
return None
return cls(
task_id=task_id,
location=str(record.get("location", "")),
state_code=_normalize_int(record.get("state"), 1) or 0,
state_name=str(record.get("state_name", "")),
action_text=str(record.get("action", "")),
color_hex=_normalize_color(record.get("color", "#FFFFFF")),
created_at=_normalize_datetime(record.get("created_time")) or datetime.now(),
completed_at=_normalize_datetime(record.get("completed_time")),
refused_from_state=_normalize_int(record.get("refused_from_state")),
refusal_reason=str(record.get("refusal_reason", "")),
assigned_specialist=str(record.get("assigned_specialist", "")),
specialist_photo=str(record.get("specialist_photo", "")),
diagnostic_report_signed=bool(record.get("diagnostic_report_signed", False)),
repair_report_signed=bool(record.get("repair_report_signed", False)),
acceptance_report_signed=bool(record.get("acceptance_report_signed", False)),
sequence_number=_normalize_int(record.get("sequence_number"), 0) or 0,
)
@classmethod
def from_snapshot(cls, snapshot: TicketTaskSnapshot) -> TicketTask:
"""Создать доменную сущность из snapshot."""
return cls(
task_id=snapshot.task_id,
location=snapshot.location,
state_code=snapshot.state_code,
state_name=snapshot.state_name,
action_text=snapshot.action_text,
color_hex=snapshot.color_hex,
created_at=snapshot.created_at,
completed_at=snapshot.completed_at,
refused_from_state=snapshot.refused_from_state,
refusal_reason=snapshot.refusal_reason,
assigned_specialist=snapshot.assigned_specialist,
specialist_photo=snapshot.specialist_photo,
diagnostic_report_signed=snapshot.diagnostic_report_signed,
repair_report_signed=snapshot.repair_report_signed,
acceptance_report_signed=snapshot.acceptance_report_signed,
sequence_number=snapshot.sequence_number,
)
def to_record(self) -> dict[str, Any]:
"""Преобразовать доменную сущность в запись для JSON."""
return {
"button_id": self.task_id,
"location": self.location,
"state": self.state_code,
"action": self.action_text,
"state_name": self.state_name,
"color": self.color_hex,
"created_time": self.created_at,
"completed_time": self.completed_at,
"refused_from_state": self.refused_from_state,
"refusal_reason": self.refusal_reason,
"assigned_specialist": self.assigned_specialist,
"specialist_photo": self.specialist_photo,
"diagnostic_report_signed": self.diagnostic_report_signed,
"repair_report_signed": self.repair_report_signed,
"acceptance_report_signed": self.acceptance_report_signed,
"sequence_number": self.sequence_number,
}
def to_snapshot(self) -> TicketTaskSnapshot:
"""Вернуть неизменяемый снимок задачи для внешних слоёв."""
return TicketTaskSnapshot(
task_id=self.task_id,
location=self.location,
state_code=self.state_code,
state_name=self.state_name,
action_text=self.action_text,
color_hex=self.color_hex,
created_at=self.created_at,
completed_at=self.completed_at,
refused_from_state=self.refused_from_state,
refusal_reason=self.refusal_reason,
assigned_specialist=self.assigned_specialist,
specialist_photo=self.specialist_photo,
diagnostic_report_signed=self.diagnostic_report_signed,
repair_report_signed=self.repair_report_signed,
acceptance_report_signed=self.acceptance_report_signed,
sequence_number=self.sequence_number,
)