Cálculo de Frete (Dalla) — VLRFRETECALC / VLRFRETE
Visão Geral
Esta regra implementa um cálculo automático de frete para a empresa Dalla, utilizado durante a criação do pedido (Nova Venda) para:
- Calcular um valor de frete sugerido a partir do carrinho e da tabela de fretes por região.
- Aplicar ajustes manuais de desconto e acréscimo (campos do header).
- Incluir o valor final do frete no total do pedido (apenas Dalla).
- Ocultar campos no UI (
VLRFRETECALC/VLRFRETE) quando a regra está ativa.
A regra é aplicada somente quando permissions.permissionByCompany.isDalla().
Resultado Gerado
A regra produz um objeto com os campos abaixo (via selector getCalculatedShipping):
VLRFRETECALC: frete calculado bruto (sem desconto/acréscimo manual).VLRFRETE: frete final aplicado ao pedido:VLRFRETE = VLRFRETECALC - AD_VLRDESCFRETE + AD_VLRACRESCFRETE
AD_VLRDESCFRETE: desconto normalizado como string.AD_VLRACRESCFRETE: acréscimo normalizado como string.CIF_FOB: definido como'F'.
Trecho real (arquivo src/controllers/sale/create/delivery.sales.create.controller.ts):
return {
VLRFRETECALC: totalShipping,
VLRFRETE: totalShipping - AD_VLRDESCFRETE + AD_VLRACRESCFRETE,
AD_VLRDESCFRETE: AD_VLRDESCFRETE?.toString() || '0',
AD_VLRACRESCFRETE: AD_VLRACRESCFRETE?.toString() || '0',
CIF_FOB: 'F',
}
Onde a regra “entra” no fluxo (Redux + UI)
1) Selector principal usado pela aplicação: getCalculatedShipping
- Fonte dos dados:
state.salesCreate.cart(carrinho)state.salesCreateUtils.regionFreights(tabela de frete por região)state.salesCreate.header(campos do header)state.salesCreate.client(cliente)getPermissions(permissões)getCodRegDalla(regiãoCODREGespecífica da Dalla)
Se não for Dalla, retorna {} e todo restante do fluxo ignora.
Trecho real:
if (!permissions.permissionByCompany.isDalla()) return {}
2) Impacto no total do pedido: getTotalValue
No selector getTotalValue (em src/controllers/sale/create/products.sales.create.controller.ts), quando Dalla e existe shippingDalla?.VLRFRETE, o frete é adicionado ao total:
if (getPermissions?.permissionByCompany?.isDalla() && shippingDalla?.VLRFRETE) {
total += shippingDalla?.VLRFRETE
totalFee += shippingDalla?.VLRFRETE
}
Ou seja:
VLRFRETEé o que afeta financeiramente o pedido.VLRFRETECALCfunciona como referência/cálculo base.
3) UI: ocultação de campos no Header
No header (src/screens/nova-venda/components/Header/NewSaleHeader.tsx), quando Dalla, os campos VLRFRETECALC e VLRFRETE são ocultados:
if (
(field.nome === 'VLRFRETECALC' || field.nome === 'VLRFRETE') &&
permission.isDalla()
) {
textFieldProps.style = { display: 'none' }
}
Isso evita edição manual direta desses campos (o frete vem da regra).
Fontes de dados e estrutura esperada
A) Carrinho: salesCreate.cart (ProductEntity)
O cálculo percorre o carrinho por Object.keys(products).
Campos relevantes por item:
| Campo | Tipo (prático) | Por que importa |
|---|---|---|
AD_CODEMPDEST | string/number | Define a “empresa destino” usada para buscar frete em regionFreights |
AD_ENTREGA | string | A regra só considera AD_ENTREGA === 'E' |
AD_DTENTREGA | string | Precisa estar preenchida (não pode ser '') e também compõe a chave de agrupamento |
PESOBRUTO | number/string | Peso unitário bruto |
QTDNEG | number/string | Quantidade negociada |
VLRUNIT | number/string | Valor unitário, base para cálculo percentual |
CODGRUPOPROD | string | Grupo; influencia agrupamento e exceções |
B) Tabela/regra de frete por região: salesCreateUtils.regionFreights
Estrutura indexada por string:
- chave principal:
${AD_CODEMPDEST}-${CODREG} - fallback:
0-${CODREG}(quando não existe frete por empresa destino)
Trecho real:
let freight = regionFreights[`${product.AD_CODEMPDEST}-${CODREG}`]
if (!freight) {
freight = regionFreights[`0-${CODREG}`]
}
Campos relevantes (RegionFreight):
| Campo | Tipo | Efeito |
|---|---|---|
VLRFRETE | number/string | Usado como “frete por peso/carga” na regra atual, entrando como freightweight |
PERCPRODFRETE | 'S' | 'N' | Controla se o frete do item pode ser percentual do produto |
PERCTFRETE | number/string | Percentual aplicado quando a regra permite frete percentual |
C) Header (ajustes manuais): salesCreate.header
Campos relevantes:
| Campo | Tipo | Interpretação |
|---|---|---|
AD_VLRDESCFRETE | string/number | desconto manual aplicado no frete final |
AD_VLRACRESCFRETE | string/number | acréscimo manual aplicado no frete final |
CODEMP | object {value,label} | lido, repassado ao cálculo (mantido para expansão/compat.) |
TIPFRETE | object {value,label} | lido e repassado ao cálculo (hoje não muda o algoritmo) |
O header é normalizado por formatNumber, que aceita vírgula como decimal:
function formatNumber(value) {
const formattedNumber = value?.toString?.()?.replace?.(',', '.')
const number = parseFloat?.(formattedNumber)
return isNaN(number) ? 0 : number
}
Algoritmo — calculateShippingValue (explicação passo a passo)
Assinatura e contrato
A função retorna um number com o frete calculado a partir do carrinho.
export const calculateShippingValue = ({
products,
regionFreights,
CODEMP,
CODREG,
TIPFRETE,
permissions,
}: { ... }): number => {
// ...
}
Observações importantes:
CODEMPeTIPFRETEestão na assinatura, mas no código atual não alteram a lógica interna.CODREGé crítico: sem ele a busca de frete por região tende a falhar.permissionsé usado parapermissions?.VLRTONna regra de excedente.
Variáveis internas relevantes
No início da função:
let totalShipping = 0
let totalWeight = 0
const keyProducts = Object.keys(products)
const groupedData = {}
let totalFreightNotGrouped = 0
totalShipping: soma “bruta” item-a-item (antes da consolidação por agrupamento).groupedData: estrutura que agrega frete/peso por grupo e data.totalFreightNotGrouped: soma do que não entra no agrupamento.
Etapa 1 — Pré-filtros (quando um item é ignorado)
Para cada product:
- A função clona o produto:
const product = JSON?.parse?.(JSON?.stringify?.(products?.[key]))
Isso evita, em teoria, mutação do estado redux.
- Filtros (ordem real):
if (!product.AD_CODEMPDEST) continue
// ...resolve freight...
if (!freight || AD_ENTREGA !== 'E' || AD_DTENTREGA == '') continue
Ou seja, o item não participa do frete quando:
- não tem empresa destino (
AD_CODEMPDEST) - não foi encontrada regra de frete (
freight) - não é entrega “E” (
AD_ENTREGA !== 'E') - não possui data de entrega (
AD_DTENTREGA == '')
Etapa 2 — Cálculo por item (percentual vs. “peso/carga”)
2.1 Peso do item
const PESO = Number(product.PESOBRUTO) * Number(product.QTDNEG)
2.2 Preparação para cálculo percentual
A regra pode forçar grupo e fazer exceções:
if (freight?.PERCPRODFRETE !== 'S') {
product.CODGRUPOPROD = '101000000'
}
if (product?.CODGRUPOPROD?.toString?.()?.includes?.('1030020')) {
product.CODGRUPOPROD = '9999999999'
}
2.3 Regra de decisão: percentual ou VLRFRETE
O cálculo decide entre:
- Percentual sobre valor do item (
freightValue) - Um valor fixo vindo de
freight.VLRFRETE(freightweight)
Trecho real (condição principal):
if (
freight.PERCPRODFRETE === 'S' &&
freight.PERCTFRETE &&
(![
'101',
'102',
'103001',
'103002',
'103003',
'103004',
'103005',
'103006',
].some((e) => product?.CODGRUPOPROD?.toString().startsWith(e)) ||
product?.CODGRUPOPROD?.toString?.().startsWith('103005001'))
) {
freightValue = (VLRUNIT * QTDNEG * freight.PERCTFRETE) / 100
} else {
freightweight = VLRFRETE
}
Leitura prática (resumo operacional)
A decisão entre frete percentual e frete fixo (VLRFRETE) pode ser lida assim:
- Somente existe frete percentual se a tabela de frete permitir e estiver configurada:
PERCPRODFRETE === 'S'ePERCTFRETEpreenchido.
- Se existir frete percentual, ele será aplicado principalmente para grupos que não são os grupos “padrão de carga” (
101,102,103001,103002,103003,103004,103005,103006). - O grupo
103005001é tratado como caso específico: mesmo sendo103005, ele pode cair no caminho do percentual. - Se a condição do percentual não for atendida, o item usa o caminho do frete fixo:
freightweight = freight.VLRFRETE.
2.4 Soma por item
totalShipping += freightValue + freightweight
Etapa 3 — Agrupamento por Grupo + Data (1000 de limite)
3.1 Chave do grupo
A chave base é:
${CODGRUPOPROD}_${AD_DTENTREGA}
Trecho real:
let grup = groupedData[`${product?.CODGRUPOPROD}_${product?.AD_DTENTREGA}`]
Mas logo em seguida ocorre a normalização para 101_<data> para vários prefixos:
if (product.CODGRUPOPROD?.toString().startsWith('101')) {
grup = `101_${product?.AD_DTENTREGA}`
} else if (product.CODGRUPOPROD?.toString().startsWith('102')) {
grup = `101_${product?.AD_DTENTREGA}`
} else if (product.CODGRUPOPROD?.toString().startsWith('103001')) {
grup = `101_${product?.AD_DTENTREGA}`
} else if (product.CODGRUPOPROD?.toString().startsWith('103002')) {
grup = `101_${product?.AD_DTENTREGA}`
} else if (product.CODGRUPOPROD?.toString().startsWith('103003')) {
grup = `101_${product?.AD_DTENTREGA}`
} else if (product.CODGRUPOPROD?.toString().startsWith('103004')) {
grup = `101_${product?.AD_DTENTREGA}`
} else if (
product.CODGRUPOPROD?.toString().startsWith('103005') &&
!product.CODGRUPOPROD?.toString().includes('103005001')
) {
grup = `101_${product?.AD_DTENTREGA}`
} else if (product.CODGRUPOPROD?.toString().startsWith('103006')) {
grup = `101_${product?.AD_DTENTREGA}`
}
Leitura prática (como pensar no agrupamento)
O agrupamento pode ser entendido como:
- A regra cria “cargas por data de entrega”, usando
AD_DTENTREGA. - Diversos grupos de produto são consolidados para um grupo único
101_<data>, formando uma carga padrão daquela data. - Em seguida, a carga padrão possui um limite base de 1000 (peso). Quando excede esse limite, o excedente adiciona um custo linear multiplicado por
VLRTON.
3.2 Estrutura do agrupamento
Quando o grupo ainda não existe, é criado:
if (grup && !groupedData?.[grup]) {
groupedData[grup] = {
group: grup,
totalWeight: 0,
totalFreight: 0,
products: [],
productsExeedingLimit: [],
}
}
3.3 Quem entra no agrupamento
Somente grupos que começam com 101, 102, 103001 ou 103005 entram na lógica de acumular e separar excedente:
if (
grup &&
['101', '102', '103001', '103005'].some((e) => grup?.toString().startsWith(e))
) {
// acumula
} else {
totalFreightNotGrouped += freightValue + freightweight
}
E dentro do agrupamento:
- soma peso e frete
- se excedeu
1000, joga emproductsExeedingLimit, senão emproducts
groupedData[grup].totalWeight += PESO
groupedData[grup].totalFreight += freightValue + freightweight
if (groupedData[grup].totalWeight > 1000) {
groupedData[grup].productsExeedingLimit.push({
CODPROD: product.CODPROD,
PESO,
freightValue: freightValue + freightweight,
})
} else {
groupedData[grup].products.push({
CODPROD: product.CODPROD,
PESO,
freightValue: freightValue + freightweight,
})
}
3.4 Fechamento do agrupamento (regra do excedente)
Ao final:
if (Object.keys(groupedData)?.length === 0) return totalShipping
let totalFreight = 0
Object.keys(groupedData).forEach((key) => {
const weight = groupedData[key]?.totalWeight
const valueExeedingLimit =
groupedData[key]?.productsExeedingLimit?.[0]?.freightValue || 0
const value = groupedData[key]?.products?.[0]?.freightValue || 0
if (weight > 1000) {
totalFreight +=
(weight - 1000) * (permissions?.VLRTON || 0.11) + valueExeedingLimit
} else {
totalFreight += value
}
})
return totalFreight + totalFreightNotGrouped
Leitura prática (fechamento do valor do frete)
No fechamento, o resultado final do frete é:
- Frete dos grupos (por data), calculado com:
- até
1000: usa o valor base do grupo. - acima de
1000: adiciona custo do excedente comVLRTON+ um valor de referência do excedente.
- até
- + Frete de itens não agrupados (
totalFreightNotGrouped).
Exemplos práticos (para validar entendimento)
Exemplo 1 — item ignorado
Se um item está com:
AD_ENTREGA = 'R'(retira)
Então ele é ignorado por:
if (!freight || AD_ENTREGA !== 'E' || AD_DTENTREGA == '') continue
Resultado: não entra no frete.
Exemplo 2 — frete percentual
Condições mínimas:
freight.PERCPRODFRETE === 'S'freight.PERCTFRETEpreenchido- grupo não começa com prefixos permitidos (ou é
103005001)
Cálculo:
freightValue = (VLRUNIT * QTDNEG * PERCTFRETE) / 100
Exemplo 3 — excedente de 1000 e uso do VLRTON
Se um grupo 101_<data> acumula 1200 de peso:
- excedente =
200 - custo excedente =
200 * (VLRTON || 0.11) - total do grupo = custo excedente +
valueExeedingLimit
Arquivos e locais que utilizam a regra (inventário)
Regra (cálculo e selector)
src/controllers/sale/create/delivery.sales.create.controller.tsgetCalculatedShippingcalculateShippingValue
Uso em tela/resumo/rodapé
-
src/screens/nova-venda/components/Summary/NewSaleSummary.tsxuseSelector(getCalculatedShipping)
-
src/screens/nova-venda/components/Footer/Bar/FooterBar.tsxuseSelector(getCalculatedShipping)
Uso no cálculo do total do pedido
src/controllers/sale/create/products.sales.create.controller.ts- selector
getTotalValue(adicionashippingDalla.VLRFRETEao total quando Dalla)
- selector
UI (campos ocultos)
src/screens/nova-venda/components/Header/NewSaleHeader.tsx- oculta
VLRFRETECALCeVLRFRETEquandopermission.isDalla()
- oculta
Tipagem
src/store/types/sale-create.type.ts- propriedade
VLRFRETECALC
- propriedade