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.
199 lines
6.5 KiB
Python
199 lines
6.5 KiB
Python
from datetime import datetime
|
|
import hashlib
|
|
import re
|
|
from typing import Any, Dict, List, Optional
|
|
|
|
from fastapi import HTTPException
|
|
|
|
from app.db.mock_database import SessionMockLocal
|
|
from app.db.mock_models import Customer, Order, ReviewSchedule, Vehicle
|
|
|
|
|
|
def normalize_cpf(value: str) -> str:
|
|
"""Normaliza CPF removendo qualquer caractere nao numerico."""
|
|
return re.sub(r"\D", "", value or "")
|
|
|
|
|
|
def _parse_float(value: Any, default: float = 0.0) -> float:
|
|
"""Converte entradas numericas/textuais para float com fallback padrao."""
|
|
if value is None:
|
|
return default
|
|
if isinstance(value, (int, float)):
|
|
return float(value)
|
|
text = str(value).replace("R$", "").replace(" ", "")
|
|
text = text.replace(".", "").replace(",", ".") if "," in text else text
|
|
try:
|
|
return float(text)
|
|
except Exception:
|
|
return default
|
|
|
|
|
|
def _stable_int(seed_text: str) -> int:
|
|
"""Gera inteiro deterministico a partir de um texto usando hash SHA-256."""
|
|
digest = hashlib.sha256(seed_text.encode("utf-8")).hexdigest()
|
|
return int(digest[:16], 16)
|
|
|
|
|
|
async def consultar_estoque(
|
|
preco_max: Optional[float] = None,
|
|
categoria: Optional[str] = None,
|
|
ordenar_preco: Optional[str] = None,
|
|
limite: Optional[int] = None,
|
|
) -> List[Dict[str, Any]]:
|
|
"""Consulta veiculos no estoque com filtros opcionais e ordenacao por preco."""
|
|
db = SessionMockLocal()
|
|
try:
|
|
query = db.query(Vehicle)
|
|
|
|
if preco_max is not None:
|
|
query = query.filter(Vehicle.preco <= preco_max)
|
|
if categoria:
|
|
query = query.filter(Vehicle.categoria == categoria.lower())
|
|
|
|
if ordenar_preco in ("asc", "desc"):
|
|
query = query.order_by(Vehicle.preco.asc() if ordenar_preco == "asc" else Vehicle.preco.desc())
|
|
|
|
if limite is not None:
|
|
try:
|
|
limite = max(1, int(limite))
|
|
query = query.limit(limite)
|
|
except (TypeError, ValueError):
|
|
pass
|
|
|
|
rows = query.all()
|
|
return [
|
|
{
|
|
"id": row.id,
|
|
"modelo": row.modelo,
|
|
"categoria": row.categoria,
|
|
"preco": _parse_float(row.preco),
|
|
}
|
|
for row in rows
|
|
]
|
|
finally:
|
|
db.close()
|
|
|
|
|
|
async def validar_cliente_venda(cpf: str, valor_veiculo: float) -> Dict[str, Any]:
|
|
"""Avalia aprovacao de compra com base em score, limite e restricao do cliente."""
|
|
cpf_norm = normalize_cpf(cpf)
|
|
db = SessionMockLocal()
|
|
try:
|
|
cliente = db.query(Customer).filter(Customer.cpf == cpf_norm).first()
|
|
|
|
if cliente:
|
|
score = int(cliente.score)
|
|
limite = _parse_float(cliente.limite_credito, 0.0)
|
|
restricao = bool(cliente.possui_restricao)
|
|
nome = cliente.nome
|
|
else:
|
|
entropy = _stable_int(cpf_norm)
|
|
score = int(300 + (entropy % 550))
|
|
limite = float(30000 + (entropy % 150000))
|
|
restricao = entropy % 7 == 0
|
|
nome = "Cliente Simulado"
|
|
|
|
aprovado = (not restricao) and (valor_veiculo <= limite)
|
|
return {
|
|
"aprovado": aprovado,
|
|
"cpf": cpf_norm,
|
|
"nome": nome,
|
|
"score": score,
|
|
"limite_credito": limite,
|
|
"possui_restricao": restricao,
|
|
"valor_veiculo": valor_veiculo,
|
|
}
|
|
finally:
|
|
db.close()
|
|
|
|
|
|
async def avaliar_veiculo_troca(modelo: str, ano: int, km: int) -> Dict[str, Any]:
|
|
"""Calcula valor estimado de troca usando depreciacao por ano e quilometragem."""
|
|
ano_atual = datetime.now().year
|
|
idade = max(0, ano_atual - ano)
|
|
base = 80000.0
|
|
valor = base * (0.85 ** idade) - (km * 0.03)
|
|
valor = max(5000.0, valor)
|
|
return {
|
|
"modelo": modelo,
|
|
"ano": ano,
|
|
"km": km,
|
|
"valor_estimado_troca": round(valor, 2),
|
|
}
|
|
|
|
|
|
async def agendar_revisao(placa: str, data_hora: str) -> Dict[str, Any]:
|
|
"""Cria ou reaproveita agendamento de revisao a partir de placa e data/hora."""
|
|
try:
|
|
dt = datetime.fromisoformat(data_hora.replace("Z", "+00:00"))
|
|
except ValueError:
|
|
raise HTTPException(
|
|
status_code=400,
|
|
detail="data_hora invalida. Use formato ISO 8601, por exemplo: 2026-03-10T09:00:00-03:00",
|
|
)
|
|
|
|
entropy = hashlib.md5(f"{placa}:{data_hora}".encode("utf-8")).hexdigest()[:8].upper()
|
|
protocolo = f"REV-{dt.strftime('%Y%m%d')}-{entropy}"
|
|
|
|
db = SessionMockLocal()
|
|
try:
|
|
existente = db.query(ReviewSchedule).filter(ReviewSchedule.protocolo == protocolo).first()
|
|
if existente:
|
|
return {
|
|
"protocolo": existente.protocolo,
|
|
"placa": existente.placa,
|
|
"data_hora": existente.data_hora.isoformat(),
|
|
"status": existente.status,
|
|
}
|
|
|
|
agendamento = ReviewSchedule(
|
|
protocolo=protocolo,
|
|
placa=placa.upper(),
|
|
data_hora=dt,
|
|
status="agendado",
|
|
)
|
|
db.add(agendamento)
|
|
db.commit()
|
|
db.refresh(agendamento)
|
|
|
|
return {
|
|
"protocolo": agendamento.protocolo,
|
|
"placa": agendamento.placa,
|
|
"data_hora": agendamento.data_hora.isoformat(),
|
|
"status": agendamento.status,
|
|
}
|
|
finally:
|
|
db.close()
|
|
|
|
|
|
async def cancelar_pedido(numero_pedido: str, motivo: str) -> Dict[str, Any]:
|
|
"""Cancela pedido existente e registra motivo e data de cancelamento."""
|
|
db = SessionMockLocal()
|
|
try:
|
|
pedido = db.query(Order).filter(Order.numero_pedido == numero_pedido).first()
|
|
if not pedido:
|
|
raise HTTPException(status_code=404, detail="Pedido nao encontrado na base ficticia.")
|
|
|
|
if pedido.status.lower() == "cancelado":
|
|
return {
|
|
"numero_pedido": pedido.numero_pedido,
|
|
"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,
|
|
"status": pedido.status,
|
|
"motivo": pedido.motivo_cancelamento,
|
|
"data_cancelamento": pedido.data_cancelamento.isoformat() if pedido.data_cancelamento else None,
|
|
}
|
|
finally:
|
|
db.close()
|