Pedidos — spec do fluxo atual + problemas de confiabilidade
Levantamento completo (jul/2026) do fluxo de criação de pedidos: mecânica, steps, precificação (tabelas/coleções/grades), todas as fórmulas de total em produção e os achados de confiabilidade — base para o redesign da tela de pedidos. Companheiro de DASHBOARD.md. Destino: documentação viva no sistema.
PARTE 1 — Como funciona hoje (mecânica e steps)
1.1 Entrada
- Listagem
/orders→ cards colapsáveis (OrderCards/OrderCard). "+ Novo" e "Editar" abrem o mesmo painel lateral (RightPanel1200px, dynamic import) comFormOrders. - Em edição,
FormOrdersrefazGET /orders/:idpara buscar o pedido completo (o card só tem dados parciais). Não existe rota própria/orders/[id].
1.2 O formulário (OrderForm.tsx) — tela única longa, 3 steps por disabled
[HeaderInfo sticky: nº, total, chips de status, ações Gerar/Faturar/E-mail/PDF]
Step 1 → Cliente (obrigatório)
Step 2 → Representada (desabilitado até ter cliente) + Coleção + Tabela de preço (opcionais)
Step 3 → Itens (desabilitado até ter representada)
[OrderSummary: métricas + campos de detalhes]
[OrderDetailsModal: comercial/entrega — vendedor, transportadora, condição pgto, datas, frete, bonificação...]
[Rodapé sticky: Gerar pedido / Atualizar + Cancelar]
- Cascatas: trocar cliente → zera representada; trocar representada → zera coleção+tabela e apaga todos os itens; trocar coleção → zera tabela.
- Validações zod (
order.schema.ts): clientId, representedId, ≥1 item, issueDate.orderNumberobrigatório só em edição. Não valida: status operacional quando CONCLUÍDO, contato/endereço/vendedor, nada de preço. - Não existe rascunho (draft) — botão comentado no código. Ou salva inteiro ou perde tudo.
- Ações WhatsApp/Imprimir/Duplicar/Cancelar existem só como UI (handlers não conectados — no-op).
1.3 Adição de itens (ProductsSection + ProductModal)
- Autocomplete paginado de produtos filtrado por representada + coleção + tabela.
- Ao escolher produto, abre modal: chama
POST /products/calculate-product-prices→ preço base + preço de cada variação de grade, cada um com sua origem (priceSourceType). - Grade: grid "quantidade por variação"; cada variação com qty>0 vira 1 item separado no pedido (1 linha = 1 grade). Variações inativas em edição aparecem "(inativa)" e preservam preço histórico.
- Descontos/acréscimos: array
adjustments[]só em % (não existe R$ fixo na prática); campo novo em branco auto-adicionado. - Na tabela do form, itens são agrupados por produto+tabela; subtotal por grupo.
1.4 Ciclo de vida (backend)
statusPrincipal:EM_ORCAMENTO → CONCLUIDO(viaPOST /orders/:id/generate; valida apenas "tem itens").billingStatus:NAO_FATURADO → FATURADO(via/invoice, all-or-nothing; "PARCIALMENTE_FATURADO" só surge como efeito colateral de editar pedido faturado). Sem faturamento parcial real.OrderStatusconfigurável (nex_status_pedido) = rótulo cosmético sem regra de negócio; pode divergir do estado real (ex.: rótulo "Cancelado" num pedido CONCLUIDO).- PDF: 2 views (
visao-clientesem preços de custo detalhados /visao-representadacom bruto/líquido) via Puppeteer + templates Handlebars no banco. - E-mail: envia PDF pro cliente e pra representada. ⚠️ CC hardcoded
valdeirsbs@gmail.comno código; templates de documento possivelmente não seedados (send-email pode dar 404).
PARTE 2 — Modelo de precificação
2.1 Estruturas
| Conceito | Tabela | Chaves |
|---|---|---|
| Tabela de preço | nex_tabela_preco | representada (obrigatória), coleção (opc), condição pgto (opc), validade, ativo |
| Item de tabela | nex_tabela_preco_item | produto + combinação de grade (null = preço base), vl_preco numeric(15,4) |
| Coleção | nex_colecao | representada + tipo + ano (unique); datas de venda. Filtra produtos e tabelas |
| Condição pgto | nex_condicao_pagamento | pc_desconto + pc_acrescimo — existem mas NUNCA são aplicados em nenhum cálculo |
| Preço do produto | nex_produto.vl_preco_tabela (listPrice) + nex_preco_combinacao (preço por variação) | fallback final |
2.2 Resolução de preço unitário (ProductPriceService.calculatePrice, backend)
Cadeia de 4 níveis, retorna no primeiro match:
- TABLE_GRADE — preço da variação na tabela selecionada
- TABLE_BASE — preço base do produto na tabela
- PRODUCT_GRADE — preço da variação no cadastro do produto
- PRODUCT_BASE —
listPricedo produto (ou 0)
Não entram: quantidade (sem preço por volume), condição de pagamento, desconto de tabela como conceito.
2.3 Cálculo do líquido (SÓ no frontend — _lib/utils.ts:78 calcLiquidPrice)
líquido = base − base·(Σ%descontos/100) + base·(Σ%acréscimos/100) → piso 0 → toFixed(2)
- Aditivo sobre a mesma base (não em cascata); a coluna
nr_ordemdos ajustes é ignorada. - O backend grava o que o front mandar —
netPrice = unitLiquidPricerecebido, sem recalcular nem validar (orders.service.ts:234/595).
PARTE 3 — TOTAIS: todas as fórmulas e por que você não confia (com razão)
As 4 fórmulas em produção
| Onde | Fórmula | Frete | Bonif. |
|---|---|---|---|
Form (calcOrderTotals, utils.ts:142) | Σ(unitLiquidPrice × qty*) + frete | ✔ | ✘ |
Listagem (OrderCard.calculateTotal:68) | Σ(quantity × netPrice da API) + freightValue | ✔ | ✘ |
Dashboard (stats.ts orderTotal) | idêntica à listagem | ✔ | ✘ |
PDF/E-mail (getOrderTemplateData:958) | totalOrder = Σ(qty × netPrice) + frete, mas os templates imprimem netValue (SEM frete) | ✘ no impresso | ✘ |
* no form, se o item tem variations, a qty vem da soma das variações e o campo qty é ignorado (frágil).
Os 12 achados que minam a confiança (arquivo:linha)
- 🔴 Backend não valida nem recalcula preço — grava
netPrice/unitBasePricecomo chegam (orders.service.ts:234-237, 595-600). Fórmula real dos totais vive só no cliente; qualquer bug/adulteração no front persiste no banco. - 🔴 Condição de pagamento é fantasma: os % de desconto/acréscimo dela nunca são aplicados, E o
paymentConditionIdenviado nunca é persistido (create/update não leem o campo) —cd_condicao_pagamentofica sempre null; o PDF sempre mostra condição vazia. - 🔴 Bonificação morta: usuário digita
bonusValueno modal de detalhes, mas o form deleta o campo do payload antes de salvar (OrderForm.tsx:208) e nenhum total a considera. Colunavl_bonificacaoexiste e fica sempre null. - 🔴 PDF soma percentuais como reais:
totalDiscounts/totalAdditionssomamadjustments.value(que é %) como se fosse R$ (orders.service.ts:975-981). - 🔴 Total impresso no PDF ignora o frete (templates usam
netValue, nãototalOrder) — o cliente recebe um total diferente do que o form mostrou. - 🟠 Filtro de tabela de preço quebrado: front manda
representativeId, controller lêrepresentadaId→ filtro ignorado → aparecem tabelas de TODAS as representadas no seletor do pedido (PriceTableAutocomplete.tsx:20 vs price-tables.controller.ts:29). - 🟠 Trocar tabela/coleção no meio do pedido NÃO reprecifica itens já lançados — pedido mistura preços de tabelas diferentes sem aviso.
- 🟠 Duas semânticas de cálculo no código:
calcLiquidPrice(aditiva, em uso) vscalcLineNet(multiplicativa em cascata, órfã) — utils.ts:10 vs :78. Se alguém "reaproveitar" a errada, muda resultado. - 🟠 Sem transação em create/update (nenhum queryRunner no módulo) → falha no meio deixa pedido sem itens ou itens sem ajustes.
- 🟠
orderNumberpor MAX+1 sem lock → colisão possível em concorrência; e o PATCH comdetailssobrescreve com|| null→ salvar detalhes sem orderNumber zera o número do pedido (orders.service.ts:538-548). - 🟡 DTOs sem pisos: qty e preço aceitam 0 e negativos (
@IsNumbersem@Min);generatesó valida "tem itens" (sem preço, sem representada, sem endereço).
Extras: escalas decimais mistas (tabela 4 casas vs pedido 2 casas), colunas legadas mortas (pc_desconto_1, vl_desconto_2, pc_acrescimo_1, vl_acrescimo_2), ação órfã getRepresentadaPriceTablesAction apontando para endpoint inexistente, form-data não retorna paymentConditions/collections/priceTables (cada um busca de um jeito).
PARTE 4 — Agenda do redesign (a discutir após sua leitura)
Decisões de arquitetura que os achados exigem
- Motor de precificação server-side: backend recalcula
netPricee totais a partir de base+ajustes e REJEITA divergência (mata os achados 1, 4, 5). Total canônico persistido ou calculado num único serviço usado por form/listagem/PDF/dashboard. - Definir a semântica oficial: ajustes aditivos ou em cascata? condição de pagamento aplica % automaticamente? bonificação entra no total ou sai da UI? frete no total impresso?
- Fluxo/UX: manter painel único com steps 1-2-3 melhorados vs. wizard real com rascunho (auto-save) — mobile-first, começar pelo produto vs. pelo cliente (Mercos começa pelo cliente; campo aberto para inovar: catálogo → carrinho).
- Reprecificação: ao trocar tabela/coleção, oferecer "reprecificar itens" com diff visual (antes/depois).
- Confiabilidade: transação + lock no orderNumber + pisos nos DTOs + fix do filtro representadaId.
- Documentação viva no sistema: rota
/docs(ou modal de ajuda) renderizando os .md denexrep-web/docs/— regras de preço e fluxo acessíveis pra dev e usuário.
Correções independentes do redesign (podem ir antes, são bugs)
- Fix filtro
representadaId(1 linha no controller ou no front). - Persistir
paymentConditionId. - Remover CC hardcoded do e-mail.
@Minnos DTOs de qty/preço.- PATCH details sem sobrescrever orderNumber/issueDate com null.