"""Ticket mutation service — wraps PSA provider, resolves is_rf_user flag.""" from __future__ import annotations import logging from uuid import UUID from sqlalchemy import select from sqlalchemy.ext.asyncio import AsyncSession from app.models.psa_connection import PsaConnection from app.models.psa_member_mapping import PsaMemberMapping from app.schemas.psa_tickets import ( PSAResourceSchema, PSATicketCreatedSchema, PSATicketStatusUpdateSchema, ) from app.services.psa.registry import get_provider_for_account from app.services.psa.types import TicketCreatePayload logger = logging.getLogger(__name__) async def _get_mapped_member_ids(account_id: UUID, db: AsyncSession) -> set[int]: """Return set of external_member_id ints that are mapped to RF users.""" conn_result = await db.execute( select(PsaConnection).where(PsaConnection.account_id == account_id) ) conn = conn_result.scalar_one_or_none() if not conn: return set() mappings = await db.execute( select(PsaMemberMapping).where(PsaMemberMapping.psa_connection_id == conn.id) ) return {int(m.external_member_id) for m in mappings.scalars().all() if m.external_member_id} async def list_resources( account_id: UUID, ticket_id: int, db: AsyncSession ) -> list[PSAResourceSchema]: provider = await get_provider_for_account(account_id, db) mapped_ids = await _get_mapped_member_ids(account_id, db) resources = await provider.list_resources(ticket_id) return [ PSAResourceSchema( member_id=r.member_id, member_name=r.member_name, member_identifier=r.member_identifier, is_rf_user=r.member_id in mapped_ids, ) for r in resources ] async def add_resource( account_id: UUID, ticket_id: int, member_id: int, db: AsyncSession ) -> PSAResourceSchema: provider = await get_provider_for_account(account_id, db) mapped_ids = await _get_mapped_member_ids(account_id, db) resource = await provider.add_resource(ticket_id, member_id) return PSAResourceSchema( member_id=resource.member_id, member_name=resource.member_name, member_identifier=resource.member_identifier, is_rf_user=resource.member_id in mapped_ids, ) async def remove_resource( account_id: UUID, ticket_id: int, member_id: int, db: AsyncSession ) -> None: provider = await get_provider_for_account(account_id, db) await provider.remove_resource(ticket_id, member_id) async def update_status( account_id: UUID, ticket_id: int, status_id: int, db: AsyncSession ) -> PSATicketStatusUpdateSchema: provider = await get_provider_for_account(account_id, db) # get current status before updating ticket = await provider.get_ticket(str(ticket_id)) previous_status = ticket.status_name or "" await provider.update_ticket_status(str(ticket_id), status_id) # get new status name from statuses list statuses = await provider.get_ticket_statuses(ticket.board_id or 0) new_status = next((s.name for s in statuses if s.id == status_id), str(status_id)) return PSATicketStatusUpdateSchema( ticket_id=ticket_id, previous_status=previous_status, new_status=new_status, new_status_id=status_id, ) async def create_ticket( account_id: UUID, payload: TicketCreatePayload, db: AsyncSession ) -> PSATicketCreatedSchema: provider = await get_provider_for_account(account_id, db) mapped_ids = await _get_mapped_member_ids(account_id, db) result = await provider.create_ticket(payload) return PSATicketCreatedSchema( id=result.id, summary=result.summary, board_name=result.board_name, status_name=result.status_name, priority_name=result.priority_name, company_name=result.company_name, resources=[ PSAResourceSchema( member_id=r.member_id, member_name=r.member_name, member_identifier=r.member_identifier, is_rf_user=r.member_id in mapped_ids, ) for r in result.resources ], )