intra_max_bot/app/services/application_service.py
2026-03-19 16:47:49 +03:00

166 lines
6.2 KiB
Python

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)