FJFrigo · Relatórios · menu 3-8 Indicadores — 386 e 389

Funil de Compras e Preços e Fornecedores do Almoxarifado

O caminho da solicitação de orçamento até a compra, com os tempos de cada trecho (386), e o preço pago, os fornecedores e a economia da cotação (389): de onde vem cada número e como conferir.

Telas: Funil de Compras do Almoxarifado (386) e Preços e Fornecedores do Almoxarifado (389)Origem: desenho novo, sem legadoSaída: painel em tela e XLS (sem PDF)Grava: nada

Bloqueia

Recusa e não gera. HTTP 200 com statuscode 400 e a frase indicada.

Aviso

Comportamento que surpreende sem ser erro — origem da maioria dos chamados de "o número não bate". Parte sai como painel de avisos na tela.

Ligações pelos itens

O funil segue a SO pelos itens da CAN, do orçamento, da SC e da compra — as mesmas ligações do 24E.

Pessoa = quem lançou o orçamento

O comprador está vazio em todo o central; a tabela por usuário usa idusuario_incluiu do orçamento.

A

386 — Funil de Compras do Almoxarifado

O que mede e como

A unidade é a solicitação de orçamento (24A) do período (pela almoxarifado_solicitacao_orcamento.data), seguida até hoje, mesmo depois do período. Desenho novo, sem legado. Etapas: SO → Autorizada → CAN (24D) → Orçamento (246) → Orçamento autorizado → SC (241) → Compra (242). Cada etapa vale pela primeira data em que a SO chega nela (compra parcial já conta).

Ligações (as do 24E, pelos itens)

EtapaComo a SO chega lá
Autorizadaautorizado = 1 ou idusuario_autorizou preenchido. Não há data da autorização (data_gerente/data_administrador vazios em 1.958 de 1.958): conta, mas não tem tempo.
CANalmoxarifado_can_solicitacoes → almoxarifado_can com status <> 'CANCELADO'. Data = data_reuniao. A CAN é opcional: filial.controla_can_almox não diz quem usa (em central, abr–jun/2026, 1.835 de 1.979 SOs de filiais "sem CAN" passaram por CAN).
OrçamentoPelo item da CAN (almoxarifado_orcamento_can_itens) ou pelo item da SO (almoxarifado_orcamento_solicitacao_orcamento_itens, produto ou serviço); orçamento vivo (excluido = 'F'). Data = almoxarifado_orcamento.data; autorização = data_hora_autorizou.
SCPelos itens (almoxarifado_solicitacao_compra_orcamento_itens, produto e serviço) e pelo cabeçalho almoxarifado_solicitacao_compra_orcamento (só 8 de 1.625 orçamentos no trimestre — sozinho, o funil caía a zero na SC).
Compraalmoxarifado_compra_solicitacao_compra e o idalmoxarifado_solicitacao_compra do item da compra (produto e serviço). Data = almoxarifado_compra.data_entrada.

Regras de leitura

Reprovada na CAN: CAN com itens e todos decisao_compra = 'REPROVADO', sem orçamento — sai como "reprovada", não como parada.

Aberta = autorizada, não reprovada e sem compra. Parada na etapa = a última etapa alcançada; "desde" = a data mais recente já alcançada (há CAN com data_reuniao antes da própria SO). "Paradas há mais de 30 dias" conta as abertas com mais de 30 dias desde essa data.

Tempos em dias de calendário, não horas: SO e SC gravam só a data (meia-noite) e a autorização do orçamento grava hora — por hora, a SC do mesmo dia parecia anterior e o trecho Autorizado → SC media 760 de 1.592. Trecho com a ordem trocada fica fora da estatística. Mediana e p90 pelo método do "mais próximo" (sem interpolar: é sempre um tempo que aconteceu).

Trechos: SO → CAN, → Orçamento (da CAN, ou da SO quando não houve CAN), Orçamento → autorizado, Autorizado → SC (do orçamento, se não há data de autorização), SC → compra e Total (SO → compra).

Pessoa = quem lançou o orçamento (almoxarifado_orcamento.idusuario_incluiu → usuario.login): o idcomprador está vazio em todo orçamento e SC de central e em 1.342 de 1.958 SOs. Só entram na tabela por usuário as SOs que têm orçamento.

Fora: o aviso de recebimento (24F): 17 registros, o último de 06/2025, e só liga à compra por documento + fornecedor.

Tela e arquivo

SaídaConteúdo
IndicadoresSolicitações; não autorizadas; reprovadas na CAN; viraram compra; autorizadas; Da solicitação à compra (mediana, p90, média); trecho mais lento; abertas e paradas há mais de 30 dias.
GráficosFunil (clique numa etapa abre as abertas paradas nela); tempo de cada trecho (mediana e p90); mês a mês (mediana de cada trecho pelo mês da solicitação; destaque quando a mediana passa do dobro da geral no trecho); por filial; por usuário.
ListaAs abertas, mais antigas primeiro: solicitação, data, setor, parada em, desde, dias, orçamento, quem lançou o orçamento. 500 linhas na tela (o endpoint de cliques traz até 2.000); o XLS traz todas.
XLSUma linha por SO (situação, etapa atual, dias na etapa, datas e números de CAN/orçamento/SC/compra, quem lançou, dias de cada trecho). Não há PDF.
B

386 — Filtros e opções

Com o valor que a tela abre
CampoPadrãoEfeito
Período de / até90 dias atrás até hojePela data da solicitação. Faixa meio-aberta (aso.data >= de e < até + 1 dia).
Filial (multi)Todas do usuárioaso.idfilial (a filial da solicitação).
Setor (multi)Todosalmoxarifado_setor da solicitação.
C

389 — Preços e Fornecedores do Almoxarifado

O que mede e como

Três perguntas sobre as compras de fornecedor do almoxarifado: o preço subiu ou caiu, quem fornece (e se há fornecedor único), e o que a cotação (orçamento) economizou. Desenho novo, sem legado.

Compra de fornecedor

tipo_faturamento.tipo_operacao em PAINEL_ALMOX_OPERACOES_COMPRA (padrão 101,131: compra de insumos — inclusive importação e NF de produtor — e de serviços; mesma codificação em central e fjfrigo_concepcion). Fora: 171 transferência, 311 retorno, 551 ajuste/outras entradas, 501 cancelamento, 111 manutenção de imobilizado, 121 compra de imobilizado (Paraguai). Produto imobilizado fora. Valor = almoxarifado_compra_produto.valor_total_ml, só linhas com quantidade e valor > 0; com moeda escolhida, valor_total_ml ÷ cotação do dia da entrada (sem cotação, valor zero e aviso). Pela data_entrada.

NúmeroRegra
Gasto com fornecedoresSoma do período (atual), todos os fornecedores (almoxarifado_compra.idemitente).
Variação de preçoPreço médio (valor ÷ quantidade) do item no período contra o do período anterior de mesmo tamanho, só itens comprados nos dois. Índice ponderado pela quantidade atual: Σ q·p_atual ÷ Σ q·p_anterior − 1. Impacto = q_atual · (p_atual − p_anterior).
Provável erro de unidadeVariação acima de 5× ou abaixo de 1/5 (óleo protetivo 1010289: R$ 652 → R$ 34 no 3º tri/2026): fora do índice, do impacto e das listas de altas/quedas; aparece numa lista à parte.
FornecedoresFatia de cada um no gasto; os 5 e os 10 maiores; os 20 maiores na tela (o clique abre os itens comprados dele, com o preço anterior de todos os fornecedores).
Fornecedor únicoItem com duas compras (notas) ou mais no período, todas do mesmo fornecedor. Sem esse piso, 1.544 de 1.815 itens saíam "únicos" só por terem sido comprados uma vez.
Cotaçãoalmoxarifado_orcamento_produtos de orçamento vivo, pela almoxarifado_orcamento.data. Por item de orçamento: vencedor = vencedor = 1, senão a menor oferta (regra do 24E). Economia sobre a média e sobre a maior só com 2+ ofertas; pago acima da menor = (vencedor − menor) × quantidade. Orçamento em outra moeda: moeda local pela cotação do dia dele, e dali à moeda escolhida.
Cotação com uma oferta só% dos itens de orçamento com uma única oferta (ofertas < 2).

Pessoa da cotação = quem lançou o orçamento (o comprador é vazio, ver 386). Quadros por mês e por pessoa na cotação.

Telas e arquivo: indicadores (gasto, variação de preço e impacto, fornecedores e os 10 maiores, fornecedor único, cotação com uma oferta só, economia, vencedor que não era o mais barato), variação por grupo, os 15 maiores impactos (altas e quedas), provável erro de unidade, os 20 maiores fornecedores, itens de fornecedor único. Listas com 300 linhas; o XLS traz tudo (preços item a item com "provável erro de unidade", seção fornecedores, seção cotação por pessoa). Sem PDF.

D

389 — Filtros e opções

Com o valor que a tela abre
CampoPadrãoEfeito
Período de / até90 dias atrás até hojeEntrada da compra e data do orçamento (cotação). O período anterior tem o mesmo número de dias, logo antes.
Filial (multi)Todas do usuárioalmoxarifado_compra_produto.idfilial e almoxarifado_orcamento.idfilial.
Grupo / Excluir grupoTodos / nenhumalmoxarifado_grupo do produto.
Moeda conversãoMoeda localPela cotação do dia da entrada (e do orçamento).
E

Regras, travas e avisos

O que recusa e o que surpreende

Os dois só leem. Sem regra de acesso nomeada; o alcance é o das filiais do usuário.

Bloqueia

O usuário não tem filial liberada.

386 · 389
Bloqueia

A filial N não está entre as filiais do usuário.

386 · 389
Bloqueia

Não foram encontrados dados nessas condições.

386Nenhuma solicitação de orçamento no período e filtros.

389Nenhuma compra de fornecedor e nenhum item de orçamento no período e filtros (tela e XLS).

386 · 389
Bloqueia

Nenhuma operação de compra em PAINEL_ALMOX_OPERACOES_COMPRA (Config. Globais).

QuandoA chave foi esvaziada sem valor.

389
Aviso

N item(ns) de compra sem cotação de <moeda> no dia: ficam com valor zero.

TambémN item(ns) de orçamento em moeda estrangeira sem cotação no dia: entram sem valor na economia.

389
Aviso

No 386 "não autorizadas" não tem tempo.

MotivoA autorização da solicitação não grava data: só entra na contagem; o trecho SO → CAN já parte da SO.

386
Aviso

Pouca cotação real.

Motivofjfrigo_concepcion: 99,9% dos itens cotados têm uma oferta só — lá o orçamento quase não é usado para cotar com mais de um fornecedor. Em central: 67,5%.

389
Aviso

Item comprado numa unidade diferente entre os períodos.

EfeitoBombona × litro, caixa × unidade dão variações enormes; só a faixa 5× / 1/5 é separada. Variações menores por troca de unidade entram no índice.

389
Aviso

Mediana 0 dia em vários trechos é normal.

EfeitoSO → CAN e Autorizado → SC têm mediana 0 d porque a conta é por dia de calendário e muitas datas coincidem no mesmo dia.

386
F

Configuração e permissões

Config. Globais (grupo faturamento)
ChavePadrãoQuem usaEfeito
PAINEL_ALMOX_OPERACOES_COMPRA101,131389 (também o 38E)tipo_operacao da compra de fornecedor. Vazia usa o padrão; os ids são tipos de operação (texto, ex. 101), não ids de tipo de faturamento.

O 386 não tem chaves próprias: lê as tabelas do 24A, 24D, 246, 241 e 242 como estão. Item de menu sem idOpcao: qualquer usuário logado abre; o alcance é o das filiais do usuário. Barra de filtros salvos por usuário (códigos 386 e 389).

G

Casos de teste sugeridos

Com o resultado esperado e como conferir no banco

Banco: central (BMG). Referências de 03–04/10/2026.

#OpçãoPassoEsperadoComo conferir
138601/04/2026 a 30/06/2026, todas as filiais.2.013 SOs → 1.958 autorizadas → 1.835 CAN → 1.625 orçamento → 1.609 orçamento autorizado → 1.592 SC → 1.383 compra; 16 reprovadas na CAN, 55 não autorizadas, 559 abertas. ~1,5 s.
-- solicitações de orçamento (24A) do período e quantas estão autorizadas
SELECT COUNT(*) AS sos, SUM(aso.autorizado = 1 OR aso.idusuario_autorizou IS NOT NULL) AS autorizadas
FROM almoxarifado_solicitacao_orcamento aso
WHERE aso.excluido = 'F' AND aso.data >= '2026-04-01' AND aso.data < '2026-07-01'
-- central, abr–jun/2026: 2.013 e 1.958
-- SOs do período que passaram por CAN (24D) não cancelada
SELECT COUNT(DISTINCT cs.idalmoxarifado_solicitacao_orcamento) AS sos_com_can
FROM almoxarifado_can_solicitacoes cs
JOIN almoxarifado_can ac ON ac.id = cs.idalmoxarifado_can AND COALESCE(ac.status,'') <> 'CANCELADO'
JOIN almoxarifado_solicitacao_orcamento aso ON aso.id = cs.idalmoxarifado_solicitacao_orcamento AND aso.excluido = 'F'
     AND aso.data >= '2026-04-01' AND aso.data < '2026-07-01'
WHERE COALESCE(cs.excluido,0) = 0
-- central, abr–jun/2026: 1.835
2386Mesmo caso, trechos.Medianas: SO→CAN 0 d; →orçamento 8 d (p90 30); orçamento→autorizado 1 d; autorizado→SC 0 d; SC→compra 2 d (p90 22); total 18 d (p90 47, média 23,7). O gargalo é a cotação (CAN → orçamento).Para uma SO específica, compare as datas do XLS (linha da SO) com almoxarifado_orcamento.data, almoxarifado_solicitacao_compra.data, almoxarifado_compra.data_entrada.
3386Clicar na etapa "Orçamento" do funil.Lista as abertas paradas nessa etapa (mais antigas primeiro) com dias desde a última etapa.desde = a data mais recente entre SO, CAN, orçamento, autorização e SC.
4386Uma SO com compra no mesmo dia da SC.O trecho SC → compra dá 0 (dia de calendário), não negativo; trecho com ordem trocada fica em branco.—
5386Tabela por usuário.Quem lançou o orçamento (login); "(sem usuário)" quando vazio; só SOs com orçamento.SELECT u.login, COUNT(*) FROM almoxarifado_orcamento ao LEFT JOIN usuario u ON u.id = ao.idusuario_incluiu WHERE ao.excluido = 'F' GROUP BY 1
6386Filtro de setor + filial; depois XLS.As mesmas SOs na tela e no XLS (uma linha por SO).—
738901/07/2026 a 30/09/2026, todas as filiais, moeda local.Gasto R$ 26.324.908,86; 320 fornecedores; 1.815 itens; variação de preço ≈ −1,80% (impacto ≈ −R$ 346,8 mil sobre R$ 18,9 mi comparáveis, 614 itens); os 10 maiores = 46,9% do gasto; 258 itens de fornecedor único (R$ 3,58 mi). ~6 s.
SELECT ROUND(SUM(cp.valor_total_ml), 2) AS gasto, COUNT(DISTINCT ac.idemitente) AS fornecedores, COUNT(DISTINCT cp.idalmoxarifado_produto) AS itens
FROM almoxarifado_compra_produto cp
JOIN almoxarifado_compra ac ON ac.id = cp.idalmoxarifado_compra
JOIN tipo_faturamento tf ON tf.id = ac.idtipo_faturamento AND tf.tipo_operacao IN ('101','131')   -- PAINEL_ALMOX_OPERACOES_COMPRA
JOIN almoxarifado_produto ap ON ap.id = cp.idalmoxarifado_produto AND COALESCE(ap.imobilizado,'F') <> 'T'
WHERE cp.excluido <> 'T' AND COALESCE(ac.excluido,'F') <> 'T' AND cp.quantidade > 0 AND cp.valor_total_ml > 0
  AND ac.data_entrada >= '2026-07-01' AND ac.data_entrada < '2026-10-01'
-- central, 3º tri/2026 (04/10/2026): R$ 26.324.908,86 · 320 fornecedores · 1.815 itens
8389Abrir o item óleo protetivo 1010289.Aparece em Provável erro de unidade (R$ 652 → R$ 34), fora do índice e do impacto.
-- preço médio do item no período atual e no anterior (mesmo tamanho), todos os fornecedores
SELECT (ac.data_entrada >= '2026-07-01') AS atual, SUM(cp.quantidade) AS q, SUM(cp.valor_total_ml) AS v,
       SUM(cp.valor_total_ml) / SUM(cp.quantidade) AS preco
FROM almoxarifado_compra_produto cp
JOIN almoxarifado_compra ac ON ac.id = cp.idalmoxarifado_compra
JOIN tipo_faturamento tf ON tf.id = ac.idtipo_faturamento AND tf.tipo_operacao IN ('101','131')
WHERE cp.excluido <> 'T' AND COALESCE(ac.excluido,'F') <> 'T' AND cp.quantidade > 0 AND cp.valor_total_ml > 0
  AND cp.idalmoxarifado_produto = <produto>
  AND ac.data_entrada >= '2026-04-01' AND ac.data_entrada < '2026-10-01'
GROUP BY atual
-- variação = preço atual ÷ preço anterior − 1; acima de 5x ou abaixo de 1/5 = "provável erro de unidade"
9389Bloco de cotação, 3º tri.≈ 2.801 itens cotados; 67,5% com uma oferta só; economia ≈ R$ 401 mil sobre a média (11,5%); 151 itens em que o vencedor não era o mais barato (≈ R$ 74,8 mil acima).
-- ofertas de um item de orçamento (24A/246): o vencedor tem vencedor = 1; sem vencedor vale a menor oferta
SELECT ao.id AS orcamento, aop.idalmoxarifado_produto AS produto, COUNT(*) AS ofertas,
       MAX(IF(aop.vencedor = 1, aop.valor_unitario, NULL)) AS preco_vencedor,
       MIN(aop.valor_unitario) AS menor, AVG(aop.valor_unitario) AS media, MAX(aop.valor_unitario) AS maior
FROM almoxarifado_orcamento_produtos aop
JOIN almoxarifado_orcamento ao ON ao.id = aop.idalmoxarifado_orcamento AND ao.excluido = 'F'
WHERE COALESCE(aop.excluido,'F') <> 'T' AND aop.valor_unitario > 0
  AND ao.data >= '2026-07-01' AND ao.data < '2026-10-01'
GROUP BY ao.id, aop.idalmoxarifado_produto
-- economia sobre a média = (média − vencedor) × quantidade, só com 2+ ofertas
10389Clicar num fornecedor.Lista os itens comprados dele com preço atual e o anterior de todos os fornecedores.Itens de almoxarifado_compra_produto com ac.idemitente = <fornecedor>.
11389fjfrigo_concepcion, jun–ago/2025, moeda U$.Gasto ≈ U$ 6,75 mi; 99,9% dos itens cotados com uma oferta só.—
12389Moeda de conversão sem cotação nos dias.Aviso N item(ns) de compra sem cotação de <moeda> no dia: ficam com valor zero.SELECT data FROM moeda_valores WHERE idmoeda = <moeda> AND data = '<dia>'
13386·389Usuário com uma filial; filial de fora via API.Só as dele; A filial N não está entre as filiais do usuário.—
H

Limitações, pendências e achados

O que ainda não existe ou merece atenção

Pendências: teste de tela dos dois (o backend precisa reiniciar). O 389 nasceu como 387 e foi renumerado (outras sessões tomaram o 387 e o 388 no mesmo dia).

Decisão de desenho: o funil não usa o aviso de recebimento (24F) nem o comprador (vazio em todo o central).

Achados ao ler o código (não corrigidos)

1. 386 — SO não autorizada com CAN/compra. Uma SO sem autorização que mesmo assim chegou a orçamento/compra entra no funil (cada etapa vale pela data) e também em "não autorizadas" (que conta !autorizada); as contagens de etapas e de "não autorizadas" se sobrepõem.

2. 386 — aso.excluido = 'F' estrito. O filtro da SO e do orçamento é igualdade com 'F' (não IFNULL): linha com excluido nulo sai do funil. Nas tabelas do legado a coluna é NOT NULL, mas vale conferir em tenant novo.

3. 389 — a economia da cotação usa só valor_unitario × quantidade. Qualquer outro componente da oferta (frete, prazo) não entra na comparação.

4. 389 — MAX(quantidade) na cotação. Quando o item tem ofertas com quantidades diferentes e nenhum vencedor, a quantidade usada na economia é a maior oferta, não a pedida.

5. 389 — fornecedor 0. Compra sem emitente (idemitente nulo) agrupa como fornecedor 0 e entra no ranking com o id como nome.

I

Como o erro chega na tela

Para o QA reproduzir e reportar

Toda resposta sai com HTTP 200; quem diz o que aconteceu é o statuscode no corpo — 400 para as recusas deste documento.

O arquivo é pedido como download, mas a recusa chega como mensagem: a tela mostra a frase no lugar do arquivo. Enquanto gera, o botão mostra o contador Gerando… (Ns).

Avisos (cotação faltando, foto sem produção, estorno sem nota...) não interrompem: vêm no cabeçalho X-Aviso-Sistema e a tela mostra o painel de avisos. Teste sempre olhando o painel, não só o arquivo.

Ao abrir chamado, copie o código de ocorrência de 8 caracteres dos erros inesperados. Erro de tela vai para o log em POST /cadastro/erros-front.

Nenhuma regra bate com essa busca. Tente um trecho menor da mensagem, ou limpe os filtros.