"""Order and fulfilment routes.
Moved from app.admin_dashboard in v4.7.2. The handlers still reuse
legacy helpers to keep this refactor behavior-preserving.
"""
from fastapi import APIRouter
from urllib.parse import quote
import app.admin_dashboard as legacy
from app.admin_dashboard import * # noqa: F401,F403
router = APIRouter()
@router.get("/orders", response_class=HTMLResponse)
@router.get("/encomendas", response_class=HTMLResponse)
async def orders_page(status: Optional[str] = None, q: Optional[str] = None):
opportunities = list_opportunities(status="all", q=q, limit=300)
order_stages = {"PAYMENT_CONFIRMED", "ORDER_PREPARATION", "SHIPPED", "WON"}
if status:
if status == "preparing":
stages = {"PAYMENT_CONFIRMED", "ORDER_PREPARATION"}
elif status == "shipped":
stages = {"SHIPPED", "WON"}
else:
stages = order_stages
else:
stages = order_stages
orders = [opp for opp in opportunities if str(opp.get("stage") or "") in stages]
rows = ""
for opp in orders:
oid = str(opp.get("id") or "")
stage = str(opp.get("stage") or "")
customer = opp.get("customer_name") or opp.get("customer_email") or "Cliente"
product = opp.get("product_interest") or opp.get("title") or "—"
state_chip = opportunity_stage_badge(stage)
next_step = {
"PAYMENT_CONFIRMED": "Criar/preparar encomenda",
"ORDER_PREPARATION": "Preparar material e envio",
"SHIPPED": "Acompanhar entrega",
"WON": "Concluída",
"NO_INTEREST": "Sem interesse",
}.get(stage, "Acompanhar")
rows += f"""
| #{esc(oid[:8])} |
{esc(customer)} {esc(opp.get('customer_email') or '')} |
{esc(product)} |
{state_chip} |
{esc(next_step)} |
{esc(opp.get('updated_at') or '—')} |
Abrir |
"""
if not rows:
rows = '| Ainda não existem encomendas em preparação/envio. |
'
body = f"""
{kpi_card('A preparar', sum(1 for o in opportunities if str(o.get('stage')) in {'PAYMENT_CONFIRMED','ORDER_PREPARATION'}), '/orders', 'pagas/preparação', 'bi-box-seam')}
{kpi_card('Enviadas', sum(1 for o in opportunities if str(o.get('stage')) == 'SHIPPED'), '/orders?status=shipped', 'em trânsito', 'bi-truck', 'cf-kpi-tone-green')}
{kpi_card('Pipeline', len(opportunities), '/opportunities', 'oportunidades', 'bi-funnel')}
{kpi_card('Tarefas operações', '→', '/tasks?status=pending&route=operacoes', 'abrir fila', 'bi-list-check')}
Encomendas e preparação
Derivado das oportunidades com pagamento confirmado/preparação/envio.
| Encomenda | Cliente | Produto | Estado | Próximo passo | Atualizada | |
{rows}
"""
return layout("Encomendas", "O que falta preparar, enviar ou acompanhar?", body, "orders")
@router.get("/orders/{opportunity_id}", response_class=HTMLResponse)
async def order_detail_page(opportunity_id: str):
opp = get_opportunity(opportunity_id)
if not opp:
return layout("Encomenda não encontrada", "Encomendas", 'Encomenda não encontrada.', "orders")
stage = str(opp.get("stage") or "")
tasks = list_opportunity_tasks(opportunity_id, limit=20)
opportunity_items = list_opportunity_items(opportunity_id)
material_rows = ""
for item in opportunity_items:
material_rows += f"""
| {esc(item.get('product_name') or 'Produto')} SKU/Odoo {esc(item.get('sku') or '—')} Jasmin {esc(item.get('jasmin_sales_item') or '—')} |
{esc(item.get('quantity') or '1')} |
{money_html(item.get('unit_price'))} |
{item_status_badge(item.get('status'))} |
"""
if not material_rows:
material_rows = f'| Sem produtos definidos. Adicionar na oportunidade. |
'
return_to = f"/orders/{opportunity_id}"
task_rows = ""
for task in tasks:
task_href = f"/tasks/{esc(task.get('id'))}?return_to={quote(return_to, safe='')}"
task_rows += f'{esc(action_label(task.get("action_code")))} · {status_badge(task.get("status"))}'
if not task_rows:
task_rows = 'Sem tarefas associadas.'
body = f"""
← Voltar a encomendas
Encomenda
{esc(opp.get('title') or 'Encomenda')}
Material
Produtos aceites/em preparação derivados da oportunidade.
| Produto | Qtd. | Preço | Estado |
{material_rows}
"""
return layout(str(opp.get("title") or "Encomenda"), "Preparação e envio", body, "orders")