"""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')}
Limpar

Encomendas e preparação

Derivado das oportunidades com pagamento confirmado/preparação/envio.
{rows}
EncomendaClienteProdutoEstadoPróximo passoAtualizada
""" 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')}

    {opportunity_stage_badge(stage)} Ver oportunidade

    Material

    Produtos aceites/em preparação derivados da oportunidade.
    {material_rows}
    ProdutoQtd.PreçoEstado

    Checklist

    """ return layout(str(opp.get("title") or "Encomenda"), "Preparação e envio", body, "orders")