Pular para o conteúdo principal

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:

  1. Calcular um valor de frete sugerido a partir do carrinho e da tabela de fretes por região.
  2. Aplicar ajustes manuais de desconto e acréscimo (campos do header).
  3. Incluir o valor final do frete no total do pedido (apenas Dalla).
  4. 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ão CODREG especí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.
  • VLRFRETECALC funciona 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:

CampoTipo (prático)Por que importa
AD_CODEMPDESTstring/numberDefine a “empresa destino” usada para buscar frete em regionFreights
AD_ENTREGAstringA regra só considera AD_ENTREGA === 'E'
AD_DTENTREGAstringPrecisa estar preenchida (não pode ser '') e também compõe a chave de agrupamento
PESOBRUTOnumber/stringPeso unitário bruto
QTDNEGnumber/stringQuantidade negociada
VLRUNITnumber/stringValor unitário, base para cálculo percentual
CODGRUPOPRODstringGrupo; 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):

CampoTipoEfeito
VLRFRETEnumber/stringUsado 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
PERCTFRETEnumber/stringPercentual aplicado quando a regra permite frete percentual

C) Header (ajustes manuais): salesCreate.header

Campos relevantes:

CampoTipoInterpretação
AD_VLRDESCFRETEstring/numberdesconto manual aplicado no frete final
AD_VLRACRESCFRETEstring/numberacréscimo manual aplicado no frete final
CODEMPobject {value,label}lido, repassado ao cálculo (mantido para expansão/compat.)
TIPFRETEobject {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:

  • CODEMP e TIPFRETE estã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 para permissions?.VLRTON na 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:

  1. A função clona o produto:
const product = JSON?.parse?.(JSON?.stringify?.(products?.[key]))

Isso evita, em teoria, mutação do estado redux.

  1. 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:

  1. Somente existe frete percentual se a tabela de frete permitir e estiver configurada:
    • PERCPRODFRETE === 'S' e PERCTFRETE preenchido.
  2. 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).
  3. O grupo 103005001 é tratado como caso específico: mesmo sendo 103005, ele pode cair no caminho do percentual.
  4. 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 em productsExeedingLimit, senão em products
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 com VLRTON + um valor de referência do excedente.
  • + 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.PERCTFRETE preenchido
  • 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.ts
    • getCalculatedShipping
    • calculateShippingValue

Uso em tela/resumo/rodapé

  • src/screens/nova-venda/components/Summary/NewSaleSummary.tsx

    • useSelector(getCalculatedShipping)
  • src/screens/nova-venda/components/Footer/Bar/FooterBar.tsx

    • useSelector(getCalculatedShipping)

Uso no cálculo do total do pedido

  • src/controllers/sale/create/products.sales.create.controller.ts
    • selector getTotalValue (adiciona shippingDalla.VLRFRETE ao total quando Dalla)

UI (campos ocultos)

  • src/screens/nova-venda/components/Header/NewSaleHeader.tsx
    • oculta VLRFRETECALC e VLRFRETE quando permission.isDalla()

Tipagem

  • src/store/types/sale-create.type.ts
    • propriedade VLRFRETECALC