from app.core.time_utils import utc_now from typing import Any from uuid import uuid4 from sqlalchemy import or_, text from sqlalchemy.exc import OperationalError, SQLAlchemyError from app.db.mock_database import SessionMockLocal from app.db.mock_models import Order, User, Vehicle from app.services.domain.common import is_legacy_schema_issue from app.services.domain.credit_service import validar_cliente_venda from app.services.domain.tool_errors import raise_tool_http_error from app.services.integrations.events import ORDER_CANCELLED_EVENT, ORDER_CREATED_EVENT from app.services.integrations.service import publish_business_event_safely from app.services.orchestration.technical_normalizer import normalize_cpf from app.services.user.mock_customer_service import hydrate_mock_customer_from_cpf # Responsabilidade: regra de pedido. def _get_vehicle_for_update(db, vehicle_id: int) -> Vehicle | None: return ( db.query(Vehicle) .filter(Vehicle.id == vehicle_id) .with_for_update() .first() ) def _get_active_order_for_vehicle(db, vehicle_id: int) -> Order | None: return ( db.query(Order) .filter(Order.vehicle_id == vehicle_id) .filter(Order.status != "Cancelado") .first() ) def _acquire_vehicle_reservation_lock(db, vehicle_id: int, timeout_seconds: int = 5) -> str | None: lock_name = f"orquestrador:vehicle_reservation:{vehicle_id}" try: acquired = db.execute( text("SELECT GET_LOCK(:lock_name, :timeout_seconds)"), {"lock_name": lock_name, "timeout_seconds": timeout_seconds}, ).scalar() except (OperationalError, SQLAlchemyError): return None if int(acquired or 0) != 1: raise_tool_http_error( status_code=409, code="vehicle_reservation_busy", message="Outro atendimento esta finalizando a reserva deste veiculo. Tente novamente.", retryable=True, field="vehicle_id", ) return lock_name def _release_vehicle_reservation_lock(db, lock_name: str | None) -> None: if not lock_name: return try: db.execute( text("SELECT RELEASE_LOCK(:lock_name)"), {"lock_name": lock_name}, ) except (OperationalError, SQLAlchemyError): pass async def listar_pedidos( user_id: int | None = None, cpf: str | None = None, status: str | None = None, limite: int = 20, ) -> list[dict[str, Any]]: cpf_norm = normalize_cpf(cpf) if cpf else None db = SessionMockLocal() try: user = None if user_id is not None: user = db.query(User).filter(User.id == user_id).first() if user and not cpf_norm: cpf_norm = normalize_cpf(user.cpf) query = db.query(Order) if user_id is not None and cpf_norm: query = query.filter( or_( Order.user_id == user_id, (Order.user_id.is_(None) & (Order.cpf == cpf_norm)), ) ) elif user_id is not None: query = query.filter(Order.user_id == user_id) elif cpf_norm: query = query.filter(Order.cpf == cpf_norm) else: raise_tool_http_error( status_code=400, code="order_list_missing_identity", message="Preciso identificar o cliente para listar os pedidos.", retryable=True, field="cpf", ) normalized_status = str(status or "").strip() if normalized_status: query = query.filter(Order.status == normalized_status) safe_limit = max(1, min(int(limite or 20), 50)) pedidos = query.order_by(Order.created_at.desc()).limit(safe_limit).all() if user_id is not None and pedidos: attached_legacy_orders = False for pedido in pedidos: if pedido.user_id is None: pedido.user_id = user_id attached_legacy_orders = True if attached_legacy_orders: db.commit() for pedido in pedidos: db.refresh(pedido) return [ { "numero_pedido": pedido.numero_pedido, "user_id": pedido.user_id, "cpf": pedido.cpf, "vehicle_id": pedido.vehicle_id, "modelo_veiculo": pedido.modelo_veiculo, "valor_veiculo": pedido.valor_veiculo, "status": pedido.status, "motivo": pedido.motivo_cancelamento, "data_cancelamento": pedido.data_cancelamento.isoformat() if pedido.data_cancelamento else None, "created_at": pedido.created_at.isoformat() if pedido.created_at else None, } for pedido in pedidos ] finally: db.close() async def cancelar_pedido( numero_pedido: str, motivo: str, user_id: int | None = None, ) -> dict[str, Any]: db = SessionMockLocal() try: query = db.query(Order).filter(Order.numero_pedido == numero_pedido) if user_id is not None: query = query.filter(Order.user_id == user_id) pedido = query.first() if not pedido and user_id is not None: legado = ( db.query(Order) .filter(Order.numero_pedido == numero_pedido) .filter(Order.user_id.is_(None)) .first() ) if legado: legado.user_id = user_id db.commit() db.refresh(legado) pedido = legado if not pedido: raise_tool_http_error( status_code=404, code="order_not_found", message=( "Pedido nao encontrado para este usuario." if user_id is not None else "Pedido nao encontrado na base ficticia." ), retryable=True, field="numero_pedido", ) if pedido.status.lower() == "cancelado": return { "numero_pedido": pedido.numero_pedido, "user_id": pedido.user_id, "status": pedido.status, "motivo": pedido.motivo_cancelamento, "data_cancelamento": pedido.data_cancelamento.isoformat() if pedido.data_cancelamento else None, } pedido.status = "Cancelado" pedido.motivo_cancelamento = motivo pedido.data_cancelamento = utc_now() db.commit() db.refresh(pedido) result = { "numero_pedido": pedido.numero_pedido, "user_id": pedido.user_id, "status": pedido.status, "motivo": pedido.motivo_cancelamento, "data_cancelamento": pedido.data_cancelamento.isoformat() if pedido.data_cancelamento else None, } await publish_business_event_safely(ORDER_CANCELLED_EVENT, result) return result finally: db.close() async def realizar_pedido( cpf: str, vehicle_id: int, user_id: int | None = None, ) -> dict[str, Any]: cpf_norm = normalize_cpf(cpf) db = SessionMockLocal() reservation_lock_name: str | None = None try: vehicle = db.query(Vehicle).filter(Vehicle.id == vehicle_id).first() if not vehicle: raise_tool_http_error( status_code=404, code="vehicle_not_found", message="Veiculo nao encontrado no estoque.", retryable=True, field="vehicle_id", ) valor_veiculo = float(vehicle.preco) modelo_veiculo = str(vehicle.modelo) try: await hydrate_mock_customer_from_cpf(cpf=cpf_norm, user_id=user_id) except ValueError as exc: if str(exc) == "cpf_already_linked": raise_tool_http_error( status_code=409, code="cpf_already_linked", message="Este CPF ja esta vinculado a outro usuario.", retryable=True, field="cpf", ) raise_tool_http_error( status_code=400, code="invalid_cpf", message="CPF invalido para realizar o pedido.", retryable=True, field="cpf", ) avaliacao = await validar_cliente_venda(cpf=cpf_norm, valor_veiculo=valor_veiculo) if not avaliacao.get("aprovado"): raise_tool_http_error( status_code=400, code="credit_not_approved", message=( "Cliente nao aprovado para este valor. " f"Limite disponivel: R$ {avaliacao.get('limite_credito', 0):.2f}." ), retryable=False, field="cpf", ) reservation_lock_name = _acquire_vehicle_reservation_lock(db, vehicle_id) try: locked_vehicle = _get_vehicle_for_update(db, vehicle_id) except (OperationalError, SQLAlchemyError) as exc: db.rollback() if not is_legacy_schema_issue(exc): raise locked_vehicle = db.query(Vehicle).filter(Vehicle.id == vehicle_id).first() if not locked_vehicle: raise_tool_http_error( status_code=404, code="vehicle_not_found", message="Veiculo nao encontrado no estoque.", retryable=True, field="vehicle_id", ) existing_order = None try: existing_order = _get_active_order_for_vehicle(db, vehicle_id) except (OperationalError, SQLAlchemyError) as exc: if not is_legacy_schema_issue(exc): raise db.rollback() if existing_order: raise_tool_http_error( status_code=409, code="vehicle_already_reserved", message="Este veiculo ja esta reservado e nao aparece mais no estoque disponivel.", retryable=True, field="vehicle_id", ) numero_pedido = f"PED-{utc_now().strftime('%Y%m%d%H%M%S')}-{uuid4().hex[:6].upper()}" pedido = Order( numero_pedido=numero_pedido, user_id=user_id, cpf=cpf_norm, vehicle_id=locked_vehicle.id, modelo_veiculo=modelo_veiculo, valor_veiculo=valor_veiculo, status="Ativo", ) db.add(pedido) try: db.commit() db.refresh(pedido) except (OperationalError, SQLAlchemyError) as exc: db.rollback() if not is_legacy_schema_issue(exc): raise db.execute( text( "INSERT INTO orders (numero_pedido, user_id, cpf, status) " "VALUES (:numero_pedido, :user_id, :cpf, :status)" ), { "numero_pedido": numero_pedido, "user_id": user_id, "cpf": cpf_norm, "status": "Ativo", }, ) db.commit() result = { "numero_pedido": numero_pedido, "user_id": user_id, "cpf": cpf_norm, "vehicle_id": locked_vehicle.id, "modelo_veiculo": modelo_veiculo, "status": "Ativo", "status_veiculo": "Reservado", "valor_veiculo": valor_veiculo, "aprovado_credito": True, } await publish_business_event_safely(ORDER_CREATED_EVENT, result) return result result = { "numero_pedido": pedido.numero_pedido, "user_id": pedido.user_id, "cpf": pedido.cpf, "vehicle_id": pedido.vehicle_id, "modelo_veiculo": pedido.modelo_veiculo, "status": pedido.status, "status_veiculo": "Reservado", "valor_veiculo": pedido.valor_veiculo, "aprovado_credito": True, } await publish_business_event_safely(ORDER_CREATED_EVENT, result) return result finally: _release_vehicle_reservation_lock(db, reservation_lock_name) db.close()