Mapeamento
As regras abaixo são o contrato de parsing de cada um dos 9 tipos: como o
ArquivoDetector decide o tipo, de qual aba/coluna cada campo sai, quais linhas
são descartadas e em que ordem as tabelas são populadas. Nomes lógicos (MOVIMENTO,
CLIENTE…) correspondem às tabelas físicas bi_movimento, bi_cliente, etc.
(ver modelagem).
1. Detecção de tipo de arquivo¶
Ordem de verificação (a primeira que satisfazer vence):
- Aba
"Meta por vendedor"existe (ou coluna "Tipo de meta") → META_FERNANDO - 1ª linha da aba padrão tem coluna "Vinculação a Distribuidor" (com/sem acento) → POSTOS
- 1ª linha da aba padrão tem coluna "Tipo de Instalação" ou "Qualificação da Empresa" (com/sem acento) → TRR
- Nome (lower-case) contém
relatorio/relatório/rel 18/relatorio18→ RELATORIO18 - Nome (lower-case) contém
margem consolidadaoumargem_consolidada→ MARGEM_CONSOLIDADA - Nome (lower-case) contém
margem -(espaço + hífen) → MARGEM_DIA - Nome (lower-case) contém
meta→ META - Nenhuma condição → desconhecido →
BadRequestException→ HTTP 400 (não persiste)
Arquivos temporários (nome começa com ~$ ou #) → 400 antes de qualquer análise.
Bug do Python corrigido no Java
O Python fazia 'margem consolidada' or 'margem_consolidada' in nome — sempre
True (string não-vazia é truthy). O Java usa a lógica correta:
nome.contains("margem consolidada") || nome.contains("margem_consolidada").
2. Extração da data de referência¶
| Tipo | Origem da data | Exemplo | Resultado |
|---|---|---|---|
| RELATORIO18 | yyyyMMdd no fim do nome |
relatorio18_20240316.xlsx |
2024-03-16 |
| MARGEM_CONSOLIDADA | MM.YYYY no nome |
margem_consolidada_03.2024.xlsx |
2024-03-01 (dia 1) |
| MARGEM_DIA | DD.MM.YYYY no nome |
margem - 15.03.2024.xlsx |
2024-03-15 |
| POSTOS / TRR | — (data de processamento) | qualquer | LocalDate.now().minusDays(1) (ontem) |
| META / META_FERNANDO | coluna DATA no arquivo |
qualquer | do Excel |
3. Aba lida por tipo¶
| Tipo | Aba | Observação |
|---|---|---|
| POSTOS / TRR | padrão (índice 0) | detecção pela coluna de cabeçalho |
| RELATORIO18 | padrão (índice 0) | dados de NF; aba Inventário processada à parte (§6) |
| MARGEM_CONSOLIDADA | DADOS |
obrigatória — não usar índice 0 |
| MARGEM_DIA | DADOS |
obrigatória — não usar índice 0 |
| META | padrão (índice 0) | — |
| META_FERNANDO | Meta por vendedor (ou aba com "Tipo de meta") |
prioridade: aba nomeada |
4. Mapeamento de colunas Excel → banco¶
4.1 POSTOS → bi_posto¶
| Coluna Excel | Campo | Regra |
|---|---|---|
| Razão Social | razao_social |
strip |
| CNPJ | cnpj |
strip; chave natural |
| MUNICÍPIO | municipio |
strip |
| UF | uf |
strip |
| Vinculação a Distribuidor | distribuidor |
strip |
| (injetado) | data |
LocalDate.now().minusDays(1) |
Upsert por cnpj; linha com qualquer campo nulo é removida.
4.2 TRR → bi_trr¶
| Coluna Excel | Campo | Regra |
|---|---|---|
| Razão Social | razao_social |
strip |
| CNPJ | cnpj |
strip; chave natural; dedup keep-last |
| Município | municipio |
strip |
| UF | uf |
strip |
| (injetado) | data |
LocalDate.now().minusDays(1) |
4.3 RELATORIO18 → bi_filial, bi_cliente, bi_vendedor, bi_produto, bi_movimento¶
Coluna Cód duplicada
O cabeçalho tem Cód duas vezes. O pandas renomeava para Cód.1; o POI SAX
não renomeia — mapear por posição: 1ª ocorrência = Cód (cliente),
2ª ocorrência = Códp (produto).
bi_filial (upsert por cod_filial): FILIAL→cod_filial, Município→municipio, UF→uf.
bi_cliente (upsert por cod_cliente): Cód (1ª)→cod_cliente, R.Social→razao_social, CNPJ→cnpj, Município→municipio, UF→uf, Class→classe.
bi_vendedor (upsert por cod_vendedor): Cod.Vendedor→cod_vendedor, Vendedor→nome.
bi_produto (upsert por cod_produto): Cód (2ª = Códp)→cod_produto, Produto→nome.
bi_movimento (upsert por nf, cod_produto, cod_cliente, cod_filial):
| Coluna Excel | Campo | Regra especial |
|---|---|---|
| NF | nf |
numérico; descartar se inválido |
| DATA NF | data |
DATE; descartar linha se nulo |
| FILIAL | cod_filial |
TEXT |
| UF / Município | uf / municipio |
TEXT |
| CIF | cif |
CHAR(1) |
| Qtde. | quantidade |
DECIMAL; descartar se inválido |
| PV | preco |
DECIMAL; descartar se inválido |
| ADM | taxa_adm |
DECIMAL; descartar se inválido |
| FRETE | frete |
DECIMAL; descartar se inválido |
| Margem Total | margem |
DECIMAL |
| PRAZO | prazo |
INTEGER; nulo/vazio → 0 |
| Bandeira / Categoria | bandeira / categoria |
TEXT (nullable) |
| Cód (1ª) | cod_cliente |
FK bi_cliente |
| Cod.Vendedor | cod_vendedor |
FK bi_vendedor |
| Cód (2ª=Códp) | cod_produto |
FK bi_produto |
Validação: descartar linha se DATA NF nula ou se qualquer de NF, Qtde., PV, ADM, FRETE
não for numérico (logar cada descarte: NF, razão social, CNPJ, produto, linha). Ordenar por NF antes do upsert.
4.4 MARGEM_CONSOLIDADA → mesmas 5 tabelas¶
FILIAL aqui é o nome da cidade
A coluna Excel FILIAL contém o nome do município (não o código). O código
está em CÓD FILIAL.
bi_filial: CÓD FILIAL→cod_filial, FILIAL→municipio (nome da cidade), UF→uf.
bi_cliente: Cód→cod_cliente, R.Social→razao_social, CNPJ→cnpj, Município→municipio, UF→uf.
bi_vendedor: RESP→cod_vendedor, VENDEDORES→nome.
bi_produto: Códp→cod_produto, Produto→nome.
bi_movimento (sem prazo/bandeira/categoria): NF→nf, DATA→data, CÓD FILIAL→cod_filial, UF→uf, Município→municipio, CIF→cif, Qtde.→quantidade, PV→preco, ADM→taxa_adm, FRETE→frete, Margem Total→margem, Cód→cod_cliente, RESP→cod_vendedor, Códp→cod_produto.
4.5 MARGEM_DIA → como MARGEM_CONSOLIDADA, exceto¶
FILIAL→cod_filial(aqui a colunaFILIALcontém o código, não a cidade);Município→municipio.bi_movimento.dataé injetado do nome do arquivo (não há colunaDATAno Excel).- Vendedor:
VENDEDORES→nome(igual à consolidada).
4.6 META → bi_meta¶
CODIGO_VENDEDOR→cod_vendedor (strip), DATA→data, LITROS→litros (DECIMAL), MARGEM→margem (DECIMAL).
Upsert por (cod_vendedor, data): DO UPDATE SET litros = EXCLUDED.litros, margem = EXCLUDED.margem.
4.7 META_FERNANDO → bi_meta¶
Vendedor→cod_vendedor (INTEGER cast p/ TEXT), Data→data (parse dd/MM/yyyy), Valor da meta (L)→volume (DECIMAL; descartar se nulo).
Transformações:
- Agrupar por
(cod_vendedor, mês)somandovolume→litros. data= 1º dia do mês (LocalDate.of(ano, mes, 1)).- Arredondar
litrosse próximo de inteiro redondo:ordem = nº de dígitos de floor(valor);tolerancia = 0.1 * 10^(ordem-1); se|valor − round(valor)| < tolerancia→round(valor). Ex.:3.999.999,99 → 4.000.000.
Upsert por (cod_vendedor, data): DO UPDATE SET litros = EXCLUDED.litros (não atualiza margem).
5. Limpeza de bi_movimento antes do insert¶
RELATORIO18 — DELETE por período (adotado no Java)¶
DELETE FROM bi_movimento WHERE data >= :periodo_inicio AND data <= :periodo_fim
periodo_inicio = MIN(DATA NF), periodo_fim = MAX(DATA NF) do arquivo.
Por que por-período e não open-ended
O Python usava DELETE WHERE data >= min_date (aberto para frente) — só correto se
os arquivos chegam em ordem cronológica. Na API REST a ordem de upload é
não-determinística: subir Fev/2025 e depois Jan/2025 (correção) apagaria fevereiro.
O DELETE por período preserva os demais meses.
Data mínima: se periodo_inicio ≤ 10/08/2025 → status IGNORADO (não é ERRO;
log de aviso; nenhuma alteração no banco). A data-limite existe porque registros
anteriores foram migrados/corrigidos à mão — reprocessá-los sobrescreveria a correção.
A mesma guarda de data mínima vale para MARGEM_CONSOLIDADA e MARGEM_DIA.
MARGEM_CONSOLIDADA / MARGEM_DIA — sem DELETE prévio¶
Só upsert. Registros ausentes no novo arquivo permanecem (os arquivos de margem representam a visão completa do período; reupload sem mudança é no-op).
6. Aba Inventário (opcional em RELATORIO18 e MARGEM_CONSOLIDADA) → bi_custo_inventario¶
Se presente: DATA→data, VALOR→valor, TAXA_APLICACAO→taxa_aplicacao,
TOTAL_JUROS→total_juros, TOTAL_VENDA_SIMULADA→total_venda_simulada, TOTAL→total.
Upsert por data. Aba ausente → ignorar silenciosamente (sem erro).
7. Filtro de movimentos (antigo hack do Power BI) — não implementar no Java¶
O script Python 08-retirar-movimento-indesejado.py copiava registros com
cod_filial = '10' e cod_produto IN ('7','8') para uma tabela de excluídos e os
removia de MOVIMENTO. No Java esses registros permanecem em bi_movimento;
toda query de BI deve aplicar WHERE NOT (cod_filial = '10' AND cod_produto IN ('7','8')).
Documentado no Javadoc de Relatorio18Processor.
8. Deduplicação dentro do arquivo¶
| Tipo | Chave de dedup (keep-last) |
|---|---|
| POSTOS / TRR | cnpj |
| RELATORIO18 / MARGEM_CONSOLIDADA / MARGEM_DIA | (nf, cod_produto, cod_cliente, cod_filial) |
| META | sem dedup interna (o upsert resolve) |
| META_FERNANDO | (cod_vendedor, mês) via agrupamento/soma |
9. Ordem de upsert (FK-safe)¶
Para os tipos que populam múltiplas tabelas, a ordem é obrigatória (garante as FKs de bi_movimento):
bi_filial → bi_cliente → bi_vendedor → bi_produto → bi_movimento
- RELATORIO18 / MARGEM_CONSOLIDADA / MARGEM_DIA populam as 5 tabelas.
- META / META_FERNANDO populam só
bi_meta(ocod_vendedorjá deve existir). - POSTOS → só
bi_posto; TRR → sóbi_trr.
10. Validação na recepção (síncrona, antes de enfileirar)¶
- Magic bytes ZIP
0x50 0x4B 0x03 0x04(xlsx) — senão 400. - Nome não começa com
~$nem#— senão 400. - Detectar o tipo (conteúdo/nome) — desconhecido → 400.
- Calcular checksum SHA-256 e conferir deduplicação de upload.
- Persistir bytes + nome + checksum +
tipo_arquivo+ statusPENDENTE. - Enfileirar o processamento assíncrono.
11. Status do processamento¶
| Status | Quando |
|---|---|
PENDENTE |
recebido e validado; aguardando a thread |
PROCESSANDO |
thread iniciou; banco sendo alterado |
SUCESSO |
todos os upserts concluídos sem exceção |
ERRO |
exceção durante o processamento (erro_mensagem preenchido) |
IGNORADO |
tipo detectado mas período ≤ 10/08/2025 (RELATORIO18/MARGEM_); não* é falha |
Recepção: 202 → PENDENTE; 200 → arquivo com checksum já existente em SUCESSO;
400 → arquivo inválido ou tipo desconhecido (não persiste).