Precificação de Pedidos — Semântica Oficial
Fonte de verdade do cálculo de preços do Verto. O motor vive no backend (
nexrep-api/src/modules/orders/pricing/order-pricing.util.ts) e é espelhado no frontend (nexrep-web/src/app/(private)/orders/_lib/pricing.ts) apenas para preview instantâneo — o valor persistido é sempre o recalculado pelo servidor. A paridade entre os dois é garantida pelo fixture compartilhadopricing-cases.jsonrodando nos testes dos dois repositórios.
Versão do motor
PRICING_ENGINE_VERSION = "1.0". Todo save de pedido grava ds_versao_motor
(ex.: "1.0:ADITIVO") e dt_recalculo_preco. Pedidos anteriores ao motor têm
"legado". Qualquer mudança de fórmula exige incrementar a versão — é o
que permite reproduzir cálculos históricos.
1. Resolução do preço BASE unitário (cadeia de 4 níveis)
Dado produto + contexto (representada, coleção, tabela, variação de grade), o
ProductPriceService.calculatePrice retorna no primeiro nível que existir:
| Ordem | Origem (priceSourceType) | Significado |
|---|---|---|
| 1 | TABLE_GRADE | preço da variação na tabela selecionada |
| 2 | TABLE_BASE | preço base do produto na tabela |
| 3 | PRODUCT_GRADE | preço da variação no cadastro do produto |
| 4 | PRODUCT_BASE | preço de lista do produto (vl_preco_tabela) |
A UI exibe a origem em cada item: ✓ Tabela X (níveis 1-2) vs
⚠ Cadastro do produto (níveis 3-4, fallback).
2. Preço LÍQUIDO unitário (descontos/acréscimos do item)
Ajustes são percentuais (0–100, até 4 casas) em nex_pedido_item_ajuste.
O modo de combinação é configurável por empresa em
Configurações → Pedidos → Cálculo de Preços (nex_configuracao.order_calc_mode):
ADITIVO (default — comportamento histórico)
Todos os percentuais aplicam sobre a MESMA base:
líquido = base × (1 − Σdescontos/100 + Σacréscimos/100)
Exemplo: base 100, desc 10% + 5%, acr 3% → 100 × (1 − 0,15 + 0,03) = 88,00
CASCATA
Cada percentual aplica sobre o resultado anterior, na ordem dos ajustes
(nr_ordem):
líquido = base × Π(1 − d/100) × Π(1 + a/100)
Exemplo: base 100 → −10% = 90,00 → −5% = 85,50 → +3% = 88,07
Regras comuns: piso 0 (nunca negativo); arredondamento ROUND_HALF_UP com 2 casas, aplicado UMA única vez no resultado final (nunca em passos intermediários).
3. Totais do pedido
subtotal = Σ (quantidade × líquido unitário) [mercadoria]
ajuste_condicao = subtotal × (pc_acrescimo − pc_desconto)/100 [condição de pagamento]
total = subtotal + ajuste_condicao + frete
- A condição de pagamento aplica seus percentuais automaticamente, como linha visível do pedido — sobre a mercadoria, nunca sobre o frete. A condição deve pertencer à representada do pedido (validado no backend).
- O frete entra no total (inclusive no PDF).
- Persistidos em
nex_pedido:vl_subtotal,vl_ajuste_condicao,vl_total. Listagem, dashboard, PDF e e-mail leem esses campos — uma fonte, quatro telas, mesmo número.
4. ⚠ Bonificação — PENDÊNCIA DE NEGÓCIO
vl_bonificacao é persistida e exibida como linha informativa, mas NÃO entra
em nenhum total. Definição em aberto (decidir com o negócio):
- É mercadoria-brinde (registra, não soma)?
- É abatimento em R$ (total −= bonificação)?
- Deve ser removida?
Até a definição, o PDF imprime "Bonificação (não somada ao total)".
5. Autoridade e auditoria
- O backend recalcula o líquido de cada item a partir de base+ajustes no create/update e ignora o valor enviado pelo cliente (divergência > R$ 0,01 gera warn no log — indica front desatualizado ou payload adulterado).
- Na geração do pedido: recálculo oficial +
js_snapshot_comercialcom nomes (representada/coleção/tabela/condição), percentuais da condição naquele momento, versão do motor e resumo de origens de preço (priceSourcesSummary). Mudanças posteriores de cadastro não afetam o snapshot. - Pedido FATURADO é imutável: itens não podem ser alterados (estorne o faturamento antes).
POST /orders/pricing-previewcalcula sem persistir — alimenta o diff visual ao trocar contexto comercial e a duplicação com preços atuais.
6. Escalas e formatos
| Campo | Escala |
|---|---|
vl_liquido, vl_base, vl_subtotal, vl_total, vl_frete | numeric(15,2) |
nex_tabela_preco_item.vl_preco | numeric(15,4) |
nex_pedido_item_ajuste.vl_ajuste (percentual!) | numeric(15,4) |
pc_desconto/pc_acrescimo da condição | numeric(5,2) |
Decimais trafegam como string na API; o front converte com Number() e exibe
via Intl.NumberFormat("pt-BR", { style: "currency", currency: "BRL" }).