You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
orquestrador/app/services/domain/order_service.py

274 lines
9.3 KiB
Python

from datetime import datetime
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.orchestration.technical_normalizer import normalize_cpf
from app.services.user.mock_customer_service import hydrate_mock_customer_from_cpf
# Responsabilidade: regra de pedido.
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 = datetime.utcnow()
db.commit()
db.refresh(pedido)
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,
}
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()
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",
)
existing_order = None
try:
existing_order = (
db.query(Order)
.filter(Order.vehicle_id == vehicle_id)
.filter(Order.status != "Cancelado")
.first()
)
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",
)
valor_veiculo = float(vehicle.preco)
modelo_veiculo = str(vehicle.modelo)
await hydrate_mock_customer_from_cpf(cpf=cpf_norm, user_id=user_id)
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",
)
numero_pedido = f"PED-{datetime.utcnow().strftime('%Y%m%d%H%M%S')}-{uuid4().hex[:6].upper()}"
if user_id is not None:
user = db.query(User).filter(User.id == user_id).first()
if user and user.cpf != cpf_norm:
user.cpf = cpf_norm
pedido = Order(
numero_pedido=numero_pedido,
user_id=user_id,
cpf=cpf_norm,
vehicle_id=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()
return {
"numero_pedido": numero_pedido,
"user_id": user_id,
"cpf": cpf_norm,
"vehicle_id": vehicle.id,
"modelo_veiculo": modelo_veiculo,
"status": "Ativo",
"status_veiculo": "Reservado",
"valor_veiculo": valor_veiculo,
"aprovado_credito": True,
}
return {
"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,
}
finally:
db.close()