from __future__ import annotations import json from dataclasses import dataclass from app.models import ( ApplicationCreate, ApplicationRecord, ApplicationStatus, ExternalSyncStatus, ) from app.repositories.applications import ApplicationRepository from app.services.external_client import ExternalTicketClient @dataclass(slots=True) class CreateOutcome: application: ApplicationRecord | None created_local: bool sent_external: bool external_error: str | None = None class ApplicationService: def __init__( self, repo: ApplicationRepository, external_client: ExternalTicketClient, channel_local_enabled: bool = True, channel_external_enabled: bool = True, ) -> None: self._repo = repo self._external_client = external_client self._channel_local_enabled = channel_local_enabled self._channel_external_enabled = channel_external_enabled @property def channel_local_enabled(self) -> bool: return self._channel_local_enabled @property def channel_external_enabled(self) -> bool: return self._channel_external_enabled async def create_application( self, customer_id: int | None, customer_name: str, chat_id: int | None, category: str, description: str, attachments: list[dict], source: str = "bot", ) -> CreateOutcome: if not self._channel_local_enabled and not self._channel_external_enabled: raise ValueError("Both creation channels are disabled.") application: ApplicationRecord | None = None if self._channel_local_enabled: application = self._repo.create( ApplicationCreate( customer_id=customer_id, customer_name=customer_name, chat_id=chat_id, category=category, description=description, attachments_json=json.dumps(attachments, ensure_ascii=False), source=source, ) ) elif self._channel_external_enabled: # Локальный канал выключен: создаем "виртуальную" сущность без записи в БД. # Для v1 это только транспорт в внешнюю систему. application = ApplicationRecord( id=0, customer_id=customer_id, customer_name=customer_name, chat_id=chat_id, category=category, description=description, attachments_json=json.dumps(attachments, ensure_ascii=False), status=ApplicationStatus.NEW.value, external_sync_status=ExternalSyncStatus.NOT_SENT.value, external_id=None, external_error=None, source=source, created_at="", updated_at="", ) if not self._channel_external_enabled: if application and self._channel_local_enabled: self._repo.update_external_sync( application_id=application.id, sync_status=ExternalSyncStatus.DISABLED.value, external_id=None, external_error="external_channel_disabled", ) application = self._repo.get(application.id) return CreateOutcome( application=application, created_local=self._channel_local_enabled, sent_external=False, ) if not self._external_client.enabled: if application and self._channel_local_enabled: self._repo.update_external_sync( application_id=application.id, sync_status=ExternalSyncStatus.FAILED.value, external_id=None, external_error="external_url_not_configured", ) application = self._repo.get(application.id) return CreateOutcome( application=application, created_local=self._channel_local_enabled, sent_external=False, external_error="external_url_not_configured", ) assert application is not None result = await self._external_client.create_ticket(application) if result.ok: if self._channel_local_enabled and application.id > 0: self._repo.update_external_sync( application_id=application.id, sync_status=ExternalSyncStatus.SENT.value, external_id=result.external_id, external_error=None, ) application = self._repo.get(application.id) return CreateOutcome( application=application, created_local=self._channel_local_enabled, sent_external=True, ) if self._channel_local_enabled and application.id > 0: self._repo.update_external_sync( application_id=application.id, sync_status=ExternalSyncStatus.FAILED.value, external_id=None, external_error=result.error, ) application = self._repo.get(application.id) return CreateOutcome( application=application, created_local=self._channel_local_enabled, sent_external=False, external_error=result.error, ) def list_applications(self, status: str | None = None, search: str | None = None) -> list[ApplicationRecord]: return self._repo.list(status=status, search=search) def get_application(self, application_id: int) -> ApplicationRecord | None: return self._repo.get(application_id) def get_events(self, application_id: int): return self._repo.get_events(application_id) def update_status(self, application_id: int, status: str, actor: str, reason: str | None = None): return self._repo.update_status(application_id, status, actor=actor, reason=reason)