Pular para conteúdo

Changelog

Todas as mudanças notáveis neste projeto são documentadas neste arquivo.

O formato é baseado em Keep a Changelog, e este projeto adere ao Versionamento Semântico.

Unreleased

2.1.0 - 2026-10-02

Adicionado

  • FluxoPagarReceber (títulos em aberto a pagar e a receber): tipo novo FLUXO_PAGAR_RECEBER, reconhecido por qualquer aba "Fluxo Pagar-Receber". A aba SemQuebras vira bi_titulo e bi_centro_custo (migration V22), com histórico por posição (a data do relatório) e atual só na mais recente — a única que o PowerSync vai sincronizar. Reimportar substitui a posição; arquivo de posição antiga entra no histórico sem virar atual; importações simultâneas são serializadas. Decisão docs/decisoes/0031-titulos-por-posicao-com-atual.md.
  • Recusa total do FluxoPagarReceber: sem a aba SemQuebras, sem data de posição ou com qualquer linha inválida, nada é gravado e o motivo lista as linhas (linha 312: vencimento '31/02/26' inválido). O total do receber diferente do "Total" da aba Grupo grava e avisa.
  • A página /xls/importar aceita o FluxoPagarReceber e espera o processamento (até 30 s): mostra a posição e o que aconteceu com ela, os totais a pagar e a receber com o vencido, e os avisos — ou a recusa, a falha técnica ou "ainda processando".

Alterado

  • Reenviar um arquivo que terminou em ERRO ou IGNORADO reprocessa o mesmo registro (202), em todos os tipos e também em POST /api/xls/processar. Antes devolvia 409 "Checksum duplicado (concorrência)" e o arquivo recusado ficava preso mesmo depois de uma correção.
  • Constituição X-Adm 2.2.2 · kit 2.0.11: pipeline com o contrato de release dentro do gate, o docs barrando o deploy na tag, o smoke no job de deploy e timeouts em todo curl; skills atualizadas; a POWERSYNC_HEALTH_URL documentada como URL pública (decisão central 0039).
  • xadm-comum-web 0.10.2: 404 de rota não casada com Authorization: Basic (scanner) deixa de abrir ERROR no GlitchTip.

Corrigido

  • Upload MARGEM_DIA não apaga mais o CNPJ, a classe, o município e a UF do cliente: o upsert de bi_cliente preserva o valor gravado quando a planilha não traz o campo.
  • Doc de mapeamento: RELATORIO18 e MARGEM_CONSOLIDADA só garantem o código da filial (município e UF são cadastro manual) e PRAZO vazio grava NULL, não 0.

Migração

  • Nada a fazer no Coolify (nenhuma env nova nem removida). A tabela nova só chega aos aparelhos depois da regra do PowerSync (repo onpetro/powersync), que sobe por último: xls → app → regra.

2.0.0 - 2026-09-17

Adicionado

  • Smoke com uma rota de cada classe de credencial: o smoke.routes do docs/app.json passa a declarar GET /api/comercial/processamentos (m2m, Bearer) e GET /processamentos (session, a tela pela sessão do seam POST /smoke/session), além do /health. Os dois são leituras sem efeito colateral; sem a credencial, o smoke confere que respondem 401/302.

Segurança

  • /test e POST /test/glitchtip exigem autenticação (sessão da view ou Bearer). Estavam @Secured(IS_ANONYMOUS) com a premissa de que a regra de view decidia antes; o SecuredAnnotationRule do micronaut-security roda primeiro e liberava o anônimo em produção.
  • GET /api/comercial/processamentos, /{id} e /{id}/logs exigem autenticação: Bearer do /api ou a sessão da view (o link de log da tela de detalhe segue funcionando). Antes eram anônimos.
  • Segredo comparado em tempo constante (MessageDigest.isEqual) no ?k= do /xls/importar e no X-Confirm-Nuke do nuke REST.
  • /xls/** e /compras/** saem do intercept-url-map, e o @Secured(IS_ANONYMOUS) do import passa da classe para os dois métodos da página: anônimo na classe abriria qualquer rota nova do controller.

Alterado

  • Constituição X-Adm 2.0.0 · kit 2.0.0. Kit re-derivado (pipeline com as guardas novas, skills, hook, Dockerfiles, kit de UI, mkdocs); docs/app.json com perfil, sem native e deploy; CLAUDE.md como roteador; docs/dev/como-rodar.md e README.md novos; decisões 0028–0030.
  • Libs da casa na corrente (decisão 0029): xadm-seguranca 0.8.0, xadm-comum-web 0.10.0, xadm-comum-storage 0.3.0 e, nos testes, xadm-comum-teste 0.4.0 — o Postgres e o Garage de teste da lib (Postgres em tmpfs) substituem as cópias locais, a @IntegracaoDocker e a DockerDisponivelCondition vêm da lib (saem as duas cópias locais), e as travas de arquitetura vêm da RegrasArquitetura, com allowlist para o SQL cru do bulk.
  • Modo S3_FILE_STORAGE=PSQL dispensa as envs do Garage. Com a xadm-comum-storage 0.3.0 o cliente S3 nasce sem credencial nesse modo e só uma chamada ao Garage a cobra; o ArquivoXlsStorage segue injetado direto no ArmazenamentoArquivoService e no StorageBackfillService. Nos modos PSQL_GARAGE e GARAGE a credencial segue obrigatória no boot.
  • mavenLocal() no build, depois do registro e filtrado para br.com.xadm: a versão da casa ainda não publicada sai do ~/.m2 sem init script.
  • Travas de teste: timeout de 15 min na task test, 2 min por teste e Hikari de teste com connection-timeout de 5 s.
  • @Valid no corpo do nuke REST (motivo até 500 caracteres); -H:IncludeLocales=pt-BR no build native; o jar base deixa de se chamar app.jar (só o shadowJar); imagens do Dockerfile JVM pinadas por digest.
  • Configuração e auditoria do nuke via micronaut-data: BiConfiguracaoRepository e NukeReplicationRepository viram @JdbcRepository, sem SQL cru pela conexão; 404/403/409 da tela de configurações e o 409 do nuke são decididos no serviço.
  • DTOs renomeados: LoteRecebimentoBody → LoteRecebimentoResponse e ProcessamentoResumoDto → ProcessamentoResumoResponse. O JSON não muda; muda o nome do schema no OpenAPI.
  • Listagens com Pageable, devolvendo o Page do micronaut-data: GET /api/comercial/processamentos e /processamentos (tela) com page base 0, size default 20 e máximo 100 (zero ou negativo cai no default) e ordenação fixa por recebido_em decrescente (o sort do cliente é ignorado). O corpo do /api é o Page como o micronaut-data o serializa — content, pageable (number, size, sort, mode) e totalSize —, sem DTO de paginação próprio: saem o {items, page, size, total} e o ProcessamentoListResponse.
  • Mudança de contrato do GET /api/comercial/admin/nuke-replication: limit/offset → page/size (mesmos limites da listagem de processamentos, ordenação fixa por iniciado_em decrescente); o corpo passa a ser o mesmo Page (content, pageable, totalSize) no lugar de {items, total, limit, offset}, e o NukeListResponse sai.
  • Package-by-feature (decisão 0030): fatias processamento, processamento.planilha, dados, importacao, powersync, configuracao, diagnostico e seguranca sobre a base comum, travadas pelo ArchUnit. O Telegram vira transporte (TelegramService) mais um notificador por feature, o recovery de boot se parte em processamento e nuke, e o ComprasImportController passa a ImportacaoXlsController. As telas de admin recebem view-model, não a entidade. Rotas, JSON, schema e envs não mudam.
  • Log por processamento em data/execucoes (app.logs.dir): a trilha que a UI exibe é dado e mora no volume /app/data, não em /app/logs (Operação no Coolify). A migration V21 regrava o log_arquivo dos processamentos antigos para o caminho novo.
  • Deploy só native. A perna jar sai do build.targets e do mapa deploy do docs/app.json: o canário 50/50 não estava efetivo (medido em 2026-09-11, 20 requisições no domínio principal = 100% native) e cada release religava uma perna jar sem tráfego. O recurso *-jar-pull do Coolify fica parado; rollback = redeploy da tag imutável <sha>-native (constituição §9, decisão central 0022).

Removido

  • Task badgeCobertura do build.gradle.kts (e o dependsOn do check): gravava um SVG em build/ que nenhum job publicava. O badge de cobertura é do etc/tests.

Corrigido

  • Nuke que falha em DROP_MONGO tenta o RESUME, como a spec 002 previa: o slot já é novo, e o PowerSync volta a subir (com strategy=coolify) em vez de ficar parado. Falha nos demais steps segue sem cleanup automático.

Migração

  • Antes do deploy, no recurso do Coolify:
  • renomeie GARAGE_ACCESS_KEY/GARAGE_SECRET_KEY para GARAGE_ACCESS_KEY_ID/GARAGE_SECRET_ACCESS_KEY (os nomes antigos seguem lidos, com WARN no boot; o application.yml não os declara mais);
  • confira que existe SENTRY_DSN (o GLITCHTIP_DSN não é mais lido — sem o SENTRY_DSN, o app sobe sem mandar erro ao GlitchTip);
  • remonte o volume da trilha de processamento de /app/logs em /app/data (o mesmo volume, só muda o ponto de montagem). Sem isso a trilha dos processamentos antigos some da UI e os novos gravam no disco efêmero do container;
  • cadastre SMOKE_TOKEN (liga o seam POST /smoke/session), com o mesmo valor do secret do repo.
  • Antes do deploy, nos secrets do repo no GitHub: SMOKE_TOKEN (o mesmo do recurso) e SMOKE_M2M_TOKEN (o valor do BI_COMERCIAL_XLS_API_TOKEN). Sem eles as rotas m2m e session do smoke viram só asserção negativa (sem credencial, 401/302) e o smoke deixa de provar a tela e o Bearer.
  • Consumidor de /api/comercial/processamentos* passa a mandar o Bearer (BI_COMERCIAL_XLS_API_TOKEN); sem ele recebe 401.
  • Consumidor das duas listagens (GET /api/comercial/processamentos e GET /api/comercial/admin/nuke-replication) lê o corpo novo: itens em content, total em totalSize, página e tamanho em pageable.number/pageable.size; o total de páginas não vem no corpo (ceil(totalSize / pageable.size)). No nuke, a query troca também limit/offset por page/size; limit/offset passam a ser ignorados.
  • As libs novas só existem no ~/.m2 até o release do xadm-commons: a CI fica vermelha até lá.

1.8.1 - 2026-09-11

Corrigido

  • Determinismo do resetSlotComPluginInvalido no CI — o assert travava a mensagem literal e reprovou 2x no runner com a suite verde local; agora trava que a mensagem nomeia o slot e tenta de novo uma vez quando a falha chega com o slot ainda existindo (transiente pré-drop sob carga). Falha persistente continua vermelha.

1.8.0 - 2026-09-11

Adicionado

  • Smoke de produção pós-deploy (constituição §9, ADR central 0031; RD local 0024). Depois do deploy o pipeline prova produção: identidade (o commit do /health é o sha desta entrega), as rotas críticas do docs/app.json, e nenhuma exceção nova no GlitchTip desde a entrega. Reprovou, reverte para a tag imutável <imagem>:<sha anterior>-<target> e confirma a reversão.

Alterado

  • XADM_COMMIT carimbado na imagem (build-arg no pipeline, ARG/ENV tarde no Dockerfile) — vira o commit do /health e o release no GlitchTip. Um identificador só atravessa pipeline, imagem, /health e tag de rollback.
  • Bump xadm-comum-web para 0.9.0 — é quem expõe o commit no /health e resolve o release a partir de XADM_COMMIT.
  • Migração da constituição 1.2.2 → 1.4.8 (RDs locais 0024+0025):
  • Login servida pela lib — bump xadm-seguranca 0.6.1 → 0.7.2, login.jte local apagado (o controller renderiza xadm/login do JAR), identidade por xadm.views.login.*; de quebra, fail-fast de token vazio (BearerTokenGuard) e o seam POST /smoke/session destravado.
  • Bootstrap do kit 5.3.3 → 5.3.8 nos paths do kit (/css/**, /js/**; vendor local removido) — a view da lib linka esses paths, e o /js/** passa a anônimo no intercept-url-map + ViewWhitelist (sem ele, 401 na tela de login).
  • Guardas novas no gate: estáticos que o layout linka, @Scheduled sem eleição (aviso) e a alternância anti-vácuo do ArchUnit aceitando assertTrue; as guardas de placeholder, micronaut-data e DDL destrutivo re-derivadas com o filtro de diretório gerado (1.3.1).
  • Kit de UI verbatim do central (kit/layout.jte) e EstaticosDoKitAnonimosTest: GET anônimo em cada estático do layout e do /login da lib (java-micronaut §UI) — sem /js/** no intercept-url-map, reprova.
  • Env do Bearer inbound renomeada para BI_COMERCIAL_XLS_API_TOKEN (seguranca §Segredos: nome pelo app, não pelo papel). Resolvida em código (BearerTokenEnv, no main) — o Micronaut não aninha placeholder; app.api-token saiu do application.yml.
  • Respostas JSON do /api/** passam a emitir toda chave (@JsonInclude(ALWAYS) nos DTOs de resposta, java-micronaut §Armadilhas): campo sem valor sai null e lista vazia sai [], em vez de a chave sumir (default NON_EMPTY do serde). Mudança aditiva no fio — cliente que já tratava ausente como vazio não muda nada.
  • A CI passa a rodar a integração (./gradlew check -PdockerTests no step Gate, timeout 25 min): sem o flag, os testes @Tag("docker") nunca rodavam na CI — cobertura órfã (ci-testes, regra estrita; a guarda "Integration no gate" reprovava).
  • Migração da constituição 1.4.8 → 1.4.10: guardas do pipeline.yml re-derivadas (a do reflect-config da JTE roda no Windows; a anti-vácuo do ArchUnit aceita assertFalse(isEmpty()); entra a de destino de proxy cravado, que aqui passa); skill /xadm-docs atualizada; hint native do SentryAppender completa (SentryOptions + Level.valueOf no reflect-config.json), para tag do logback.xml não ser ignorada em silêncio no native.
  • Migração da constituição 1.4.10 → 1.5.0: pipeline.yml re-derivado do template (a anti-vácuo do ArchUnit reconhece RegrasArquitetura.importNaoVazio(); o decide busca o histórico inteiro para ler o trailer Deploy: da tag anotada; sai o passo morto que importava fatos do integrador — nenhuma página os transcluía; o deploy segue um step por alvo, gateado pelo sucesso do próprio build); skills /xadm-docs e /x-documentar re-derivadas e /xadm-meta-audit-work instalada; o healthcheck do Postgres do docker-compose.dev.yml passa a pg_isready -h 127.0.0.1 (sem -h responde "pronto" no servidor temporário do initdb); a RD 0024 marca a rota do central-backend que cita (checa-rotas).
  • A integração roda por default no check (@IntegracaoDocker = @Tag("docker") + DockerDisponivelCondition, o idioma do bi-transporte-xls): sem Docker, cada classe é pulada com ERROR no log, nunca em silêncio. Antes o check local e o pré-flight da /xadm-release pulavam a integração calados.
  • Bump xadm-comum-web 0.9.0 → 0.9.1 — a lib embarca a hint native do SentryAppender (três entradas, com os métodos) e o 404 de negócio volta a DEBUG (só o 404 de rota não casada em requisição autenticada vai ao GlitchTip).
  • Cobertura mede só código autoral: o jacocoTestReport exclui o gerado pelo Micronaut ($Introspection, $Definition, $IntrospectionRef, $Intercepted), o serializer do serde 3.x (Serde*) e as templates JTE precompiladas.
  • Datasource sem DB_USER/DB_PASSWORD: produção sempre usou DATASOURCES_DEFAULT_* (binding do Micronaut); os placeholders DB_* do application.yml e as linhas do HOWTO-DEPLOY.md saíram.

Removido

  • -PdockerTests — a integração deixou de ser opt-in (ver Alterado).
  • reflect-config.json local (META-INF/native-image/br.com.onpetro/bi-comercial-xls/) — a xadm-comum-web 0.9.1 traz as mesmas três entradas (§Migração da lib: segunda fonte drifta).
  • API_BEARER_TOKEN — o nome antigo não é mais lido: o Bearer vem só de BI_COMERCIAL_XLS_API_TOKEN. Os dois recursos Coolify (jar e native) já têm o nome novo, com o mesmo valor, desde 2026-09-10; sem ele (ou vazio), o BearerTokenGuard recusa o boot.

Corrigido

  • Passo RESET_SLOT do nuke podia falhar com replication slot "…" is active for PID N — o pg_terminate_backend(pid) só envia o sinal e retorna na hora; o drop logo em seguida perdia a corrida com o walsender ainda soltando o slot. Agora usa a forma com timeout (pg_terminate_backend(pid, 5000), PG ≥ 14), que espera o processo sair antes do drop. Era também o flake do PostgresReplicationAdminIntegrationTest.
  • Flake no cleanup do ComprasImportControllerTest — o processamento assíncrono de um teste inseria bi_compra entre os DELETEs do seguinte (FK violada); vira um TRUNCATE atômico.
  • Seed de filiais fora do portal — docs/seed-filiais.sql (massa de dados) era publicado pelo mkdocs; foi para docs/privado/ (excluído do site, constituição §5) com o rastro atualizado.

1.7.3 - 2026-08-31

Alterado

  • Deploy via control-plane (ADR central 0026): o CI passa a disparar POST /api/ci/deploy no central-backend (resolução ancorada na imagem: registry_slug+target → recurso Coolify) em vez da ponte SSH forced-command, que ficava órfã no allowlist a cada cutover blue-green. Build off-host (GitHub Actions), Coolify só puxa a imagem pronta.

1.7.2 - 2026-08-28

Corrigido

  • Build native: o Dockerfile.native não pré-criava /app/logs (destino do app.logs.dir, log por-processamento exibido na UI) com o dono app, ao contrário do Dockerfile JVM. Sob o binário native (user app, /app do root), a criação do diretório em runtime falhava com AccessDeniedException e o processamento morria. Espelhado o mkdir+chown do JVM antes do USER app.

1.7.1 - 2026-08-27

Corrigido

  • Build native e documentação: título OpenAPI normalizado para ASCII e referências legadas de rotas/changelog alinhadas ao contrato atual (decisão 0021). A correção evita falha do micronaut-openapi no build limpo do container. Config de build GraalVM native-image (Fase B, spec 012 tarefa T6): perfil graalvmNative no build.gradle.kts (-PnativeQuick→-Ob/-Os + --gc=serial) e reflect-config.json local registrando o SentryAppender do logback (instanciado por reflexão no boot). nativeCompile compila verde. O recurso Coolify native usa a imagem pré-compilada fonte.xadm.biz/xadm/onpetro-xls:native-amd64, no domínio https://excel-native.onpetro.xadm.biz; o recurso JVM permanece em https://excel.onpetro.xadm.biz durante a validação.

1.7.0 - 2026-08-27

Adicionado

  • Deploy native em produção — recurso Coolify Docker Image excel-native.onpetro, com a imagem GraalVM fonte.xadm.biz/xadm/onpetro-xls:native-amd64 e domínio https://excel-native.onpetro.xadm.biz. O recurso JVM original permanece disponível para rollback durante a validação do cutover.

Corrigido

  • Detecção da Cota Petrobrás passou a aceitar o produto Diesel R5.

1.6.0 - 2026-08-18

Adicionado

  • Importação da Cota Petrobrás — novo tipo de planilha reconhecido automaticamente (uma aba por ano + cabeçalho POLO/PRODUTO/meses): a matriz de cotas passa a alimentar a tabela bi_cota_petrobras, sincronizada via PowerSync. Cada envio faz substituição total (a planilha é a matriz inteira do ano); um envio vazio não apaga nada, como salvaguarda. A página de importação manual (/xls/importar, antes /compras/importar — a rota antiga continua viva) passa a aceitar Compras e Cota Petrobrás, no máximo uma planilha de cota por lote. Spec .ia/011.

Alterado

  • Infra compartilhada consolidada nas libs xadm-comum-* — a infra web (RFC-7807 ProblemDetail + processor que @Replaces o Hateoas default, /health, /info, SentryInitializer e as exceções HTTP genéricas), o object storage (seam + S3 + Garage + modos de armazenamento) e os utilitários (checksum SHA-256 + conversão de data) deixaram de ser cópias locais e passam a vir de br.com.xadm:xadm-comum-web:0.4.0, xadm-comum-storage:0.1.0 e xadm-comum-util:0.1.0; o motor de auth sobe para xadm-seguranca:0.3.0. Comportamento idêntico ao anterior (paridade provada por teste de integração). Locais ficam só a whitelist de rota do app, a exceção de domínio StorageIndisponivelException (503, XLS) e o coordenador de storage. Decisões em docs/decisoes/0018-adota-xadm-seguranca.md e ADR 0019 (xadm-commons), spec 009.

Corrigido

  • Metas podiam sumir em silêncio no envio de lote — num .zip os arquivos são processados em paralelo e um arquivo dependente (ex. META, com FK em vendedor) podia commitar antes do relatório que cria o vendedor; a chave estrangeira abortava o lote daquele arquivo e a meta desaparecia, mesmo com o envio marcado como recebido. Agora, ao fechar o lote (quando o relatório mestre já commitou), os itens que falharam por FK transiente são reprocessados uma vez; a primeira falha é silenciosa e só a segunda (vendedor genuinamente ausente) dispara alarme. Migration V18. Arquivo solto (fora de lote) segue alarmando na hora.

1.5.2 - 2026-08-04

Corrigido

  • Importação de TRR voltou a falhar com "Tipo de arquivo não reconhecido" depois que a ANP renomeou a coluna "Tipo de Instalação" para "Qualificação da Empresa" no export (e removeu as colunas finais) — o detector reconhecia o TRR só pelo nome antigo da coluna. Agora aceita os dois cabeçalhos (com/sem acento), preservando compatibilidade com planilhas antigas. Sem colisão com POSTOS, que é detectado por coluna própria e tem prioridade maior.

1.5.1 - 2026-08-04

Alterado

  • Motor de autenticação extraído para a lib compartilhada xadm-seguranca — o código de auth das views (login Firebase, cookie de sessão, gate de rota, CSRF) deixou de ser cópia local e passa a vir de br.com.xadm:xadm-seguranca, a mesma usada pelos apps irmãos. Comportamento idêntico ao anterior (paridade provada por teste ponta-a-ponta); nenhuma mudança visível ao usuário. Fica local só a whitelist de rota específica do app (inclui /compras/). Nova variável opcional AUTH_ISSUER (default bi-comercial-xls) — não alterar em produção, pois muda a validação do cookie e invalida sessões em voo. Decisão em docs/decisoes/0018-adota-xadm-seguranca.md.

1.5.0 - 2026-07-29

Adicionado

  • Processador de Compras — novo tipo de arquivo reconhecido automaticamente pela aba "Compras": a Relação de Compras do ERP passa a virar as tabelas bi_fornecedor e bi_compra, sincronizadas via PowerSync para o painel de Compras do app móvel. Só combustíveis entram (filtro ONU); quantidade e valor são guardados crus (custo médio é derivado no cliente). Reenvio do mesmo arquivo é idempotente por checksum; correção de um período substitui aquele período. Decisão em docs/decisoes/0017-compras-sem-chave-natural-replace-periodo.md.
  • Página de importação manual de Compras (/compras/importar) — destino do botão "Importar XLS" do app bi-comercial (abre em nova aba). Aceita 1 ou mais .xlsx da Relação de Compras, empacota no servidor e registra quem enviou (id e nome do usuário) e a data/hora, visíveis no detalhe do processamento. É uma página aberta (sem login Google) protegida por um token compartilhado (BI_COMERCIAL_XLS_IMPORT_TOKEN): sem o token, sem a identidade do usuário, ou se o arquivo não for a Relação de Compras, o envio é recusado.

1.4.6 - 2026-07-29

Adicionado

  • Filtro por tipo de relatório na tela de processamentos: ao lado do filtro de Status, um segundo seletor mostra só os envios de um tipo (RELATORIO18, METAS, POSTOS, TRR, margens…). Os dois filtros se combinam e a lista de tipos acompanha o que o sistema reconhece. O mesmo filtro está disponível na API REST (GET /api/comercial/processamentos?tipo=…, parâmetro opcional que combina com status). A ordenação segue por data de recebimento, do mais novo para o mais antigo.

1.4.5 - 2026-07-27

Corrigido

  • Envio de lote podia falhar com "deadlock detected" quando dois arquivos do mesmo .zip eram processados em paralelo: ambos gravavam os mesmos clientes (e demais dimensões) em ordens diferentes, e o PostgreSQL abortava um dos processamentos (erro 40P01), derrubando o arquivo inteiro do lote. Agora cada gravação em lote ordena as linhas pela chave natural da tabela, o que garante ordem de bloqueio consistente entre processamentos concorrentes e elimina o deadlock. Reenviar o .zip que falhou (ou usar o botão Reprocessar) resolve os envios afetados.

1.4.4 - 2026-07-17

Adicionado

  • Botão Reprocessar nas telas internas para envios com status ERRO, que refaz o processamento a partir do arquivo já guardado — sem reenviar a planilha. Vem em três escopos: o envio, os erros do lote (.zip) dele e todos os erros do banco (na lista filtrada por ERRO). Antes o botão existia só em ambiente de desenvolvimento. Só ERRO é aceito: envio em SUCESSO, PENDENTE ou PROCESSANDO responde 409, o que torna o duplo-clique inofensivo.
  • Campo conjuntoId no detalhe do processamento (GET /api/comercial/…/{id}) — identifica o lote a que o arquivo pertence. Adição de chave; nada foi removido nem renomeado.

Corrigido

  • Todo processamento falhava em produção com AccessDeniedException ao criar /app/logs: o container roda como usuário não-root, mas /app pertencia ao root, e o app não conseguia criar o diretório de log ali. A imagem agora cria /app/logs com o dono certo. Também derrubava o log em arquivo do logback. Se o diretório for um bind mount no host, o dono precisa ser 1001:1001 (volume nomeado herda sozinho).

1.4.3 - 2026-07-16

Alterado

  • Telas internas migradas para o layout padrão X-Adm: nova paleta (azul royal no lugar do navy/burgundy), cabeçalho com marca e chip de sessão. Mudança visual — o operador vai notar; nenhum fluxo mudou.
  • /health passa a responder {status, versao}, com a versão do release lida do próprio artefato. O health check do container usa este endpoint.
  • Imagem de runtime do container passa de Alpine para Jammy (glibc), com processo não-root e limites de heap da JVM. Sem mudança de comportamento do app.
  • Gate de CI passa a imprimir a cobertura de testes.

Corrigido

  • Documentação do envio de planilhas: descrevia POST /api/comercial/processar com um .xlsx por envio e campos conjunto_id/conjunto_total. O endpoint real é POST /api/xls/processar, que recebe um .zip com as planilhas do ciclo, desde a v1.4.0 — a API não mudou agora; a doc é que estava errada. Quem integra deve conferir o manual e a referência da API.
  • Tela de detalhe exibia "Inalteradas" negativa (ex.: -98) e "Efetivas: 231 / 133 enviadas": a conta misturava o total de linhas de todas as tabelas com o de registros só da entidade principal. Nova coluna linhas_enviadas (migration V15) fecha o cálculo no mesmo escopo. Uploads anteriores à V15 exibem "—" (o dado nunca foi persistido).
  • Observabilidade lê SENTRY_DSN, com GLITCHTIP_DSN como fallback.

1.4.2 - 2026-07-07

Fixed

  • Geração da referência de API (Javadoc) no CI de docs: corrigidas as referências @link de PowerSyncProcessControlCoolify (doclint) que faziam o docs.yml falhar. Sem mudança de comportamento do app.

1.4.1 - 2026-07-07

Changed

  • Release de manutenção: reexecuta os workflows de CI/docs no Forgejo. Sem mudança de código nem de comportamento do app.

1.4.0 - 2026-07-07

Added

  • Observabilidade de erros via GlitchTip (protocolo Sentry, bug.xadm.biz): logs ERROR e exceções não-tratadas são reportados automaticamente. Nova rota de diagnóstico autenticada /test/glitchtip para smoke-test pós-deploy.
  • Documentação do projeto publicada como site MkDocs Material em docs.xadm.biz (arquitetura, etapas do projeto, decisões e as regras de negócio de processamento célula-a-célula por tipo de arquivo).

Changed

  • Contrato de erro da API REST agora é RFC 7807 (application/problem+json). O corpo de erro de /api/** mudou de {message, _links} para problem+json (status HTTP preservados) — consumidores da API devem atualizar o parsing.
  • [operação] A variável do DSN de error tracking mudou de SENTRY_DSN para GLITCHTIP_DSN — o valor já foi injetado no Coolify pela Central de Apps.
  • Padronização do pipeline de processamento de planilhas XLS.
  • Filtro de autenticação das views migrado para @ServerFilter (ordem determinística no fat jar).

Fixed

  • Default do APP_BASE_URL corrigido para https://excel.onpetro.xadm.biz (apontava para um domínio sem certificado válido; só funcionava com o env sobrescrito em produção).

1.3.1 - 2026-05-28

Fixed

  • Layout das views server-rendered padronizado. No detalhe do processamento (/processamentos/{id}) os blocos de informação deixam de usar listas de definição (<dl>) e passam a um grid responsivo de duas colunas; novo fragment pageHeader centraliza o título da tela; painel de logs ganha mais altura e quebra de linha (white-space: pre-wrap); o filtro do histórico (/processamentos) foi realinhado. Apenas apresentação — sem mudança de comportamento.

1.3.0 - 2026-05-28

Added

  • Restaurado o sinal de nuke bi_configuracao.ultimo_nuke (removido na v1.2.4). Empiricamente o sinal é necessário: sem ele o cliente Dart reusa o SQLite local após o nuke do Mongo e fica com dados stale — linhas DELETE-adas no PG entre o último sync do cliente e o nuke viram zumbis no cache (PowerSync sincroniza só deltas e não força re-download sozinho). O NukeReplicationService volta a fazer UPSERT na chave no STEP_FINALIZAR (via BiConfiguracaoRepository.marcarUltimoNuke(), construtor de volta a 6 args); clientes Dart comparam o valor pra disparar disconnectAndClear e re-sincronizar do zero. Spec 007 reativada.

1.2.4 - 2026-05-27

Removed

  • Sinal de nuke bi_configuracao.ultimo_nuke (mecanismo de disconnectAndClear no cliente). PowerSync já detecta mudança de checksum no servidor e força re-download automaticamente — o sinal manual era boilerplate desnecessário. Removidos: NukeReplicationService.marcarUltimoNuke() chamada no STEP_FINALIZAR, BiConfiguracaoRepository.marcarUltimoNuke() (método), injeção do BiConfiguracaoRepository no NukeReplicationService (construtor volta a 5 args), 2 testes do método em BiConfiguracaoRepositoryIntegrationTest. Cliente Flutter bi-comercial removeu o DatasetVersionGuard equivalente. A row ultimo_nuke permanece no seed da V12 como artefato legado. Spec 007 marcada como SUPERSEDED parcial; o resto (bi_configuracao KV, ConfigService, /admin/configuracoes, slow_query_timeout_ms) permanece ativo.

1.2.3 - 2026-05-26

Fixed

  • Recovery automático de nukes órfãos no startup: se a JVM/container morre no meio do pipeline (deploy, OOM, crash), a row em xls_nuke_replication ficava eternamente em EM_ANDAMENTO e o partial unique index bloqueava novos nukes (recovery exigia UPDATE manual via psql). Agora o StartupRecoveryService marca essas rows como ERRO automaticamente no startup, gravando "Interrompido após reinício da aplicação" no erro_mensagem e preservando o step_atual original pra debug. Novo método NukeReplicationRepository.marcarOrfaosComoErro() faz o UPDATE em massa (seguro por construção — partial unique index garante 0-1 row afetada).

1.2.2 - 2026-05-26

Fixed

  • V13 consolida o fix manual de permissões: GRANT SELECT ON bi_* retroativo para as 10 tabelas bi_* que sincronizam + ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT ON TABLES TO powersync_role para tabelas futuras criadas pelo user_onpetro. Migração idempotente; pula em dev/test onde powersync_role não existe. CLAUDE.md ganha seção "Permissões pro powersync_role" com sintoma (PG error 42501), causa e padrão de cinto de segurança (GRANT explícito logo após CREATE TABLE bi_* nova).

Changed

  • Nomenclatura de DB/user em dev alinhada com prod: POSTGRES_DB passa de onpetro_comercial → db_onpetro, POSTGRES_USER → user_onpetro. Reflete em docker-compose.dev.yml, run-dev.bat e defaults do application.yml. Dev local agora bate com o padrão db_* / user_* documentado em ../powersync/doc/postgresq-setup.md.

1.2.1 - 2026-05-26

Added

  • Tabela bi_configuracao (V12) — KV genérico sincronizado via PowerSync (stream recent_data, priority 0). Seed inicial: ultimo_nuke (sistema=TRUE) e slow_query_timeout_ms (sistema=FALSE). Spec .ia/007-bi-configuracao-sinal-nuke.spec.md.
  • Marcador ultimo_nuke atualizado automaticamente pelo NukeReplicationService no STEP_FINALIZAR — clientes Dart detectam a mudança e disparam disconnectAndClear no próximo sync, evitando SQLite zumbi pós-nuke.
  • ConfigService (@Singleton) — cache in-memory TTL 30 s com API getString/getInt/getBool/getDuration pra consumo de config no backend.
  • Tela /admin/configuracoes — CRUD de chaves KV com validação por tipo, ordem de precedência 404 > 403 > 400; rows sistema=TRUE aparecem read-only.
  • Exception ForbiddenException (HTTP 403) para chaves de sistema.
  • Card "Configurações globais" no painel /admin linkando para a nova tela.
  • Entrada - SELECT * FROM bi_configuracao em ../powersync/powersync.yaml (streams recent_data e dev). Requer redeploy do recurso PowerSync no Coolify.

Changed

  • NukeReplicationService agora recebe BiConfiguracaoRepository no construtor (6 args). Sem mudança no error path: falha do UPSERT cai no catch existente → nuke ERRO.
  • views/admin/powersync.html reorganizado: histórico de nukes agora é card Bootstrap (estilo consistente com os outros cards), título simplificado para "Administração".
  • docs/nuke-replication.md + HOWTO-DEPLOY.md documentam o atributo REPLICATION obrigatório na role do app PG e a URL interna do Coolify (http://coolify:8080) para o restart automático funcionar atrás do Traefik com cert wildcard (evita PKIX path building failed).

Fixed

  • Assets do Bootstrap (CSS + JS bundle 5.3.3) agora são selfhosted em /public/vendor/bootstrap-5.3.3/ — elimina dependência do CDN jsdelivr e problemas de integrity/CSP em ambientes restritos.
  • Favicon servido via /public/favicon.png em vez de /favicon.png path-exato, que o Micronaut ignorava silenciosamente como mapping de static-resources (gerava 404). Limpa também o entry redundante de whitelist no AuthFilter.

1.2.0 - 2026-05-26

Fixed

  • Falhas intermitentes de build no Coolify quando o download do Gradle distribution (gradle-8.10.2-bin.zip) lançava UnknownHostException para release-assets.githubusercontent.com (CDN do GitHub Releases ao qual o services.gradle.org redireciona). O Dockerfile agora usa BuildKit cache mount em /root/.gradle, persistindo o wrapper distribution e as dependências Maven entre deploys — a janela de risco do DNS lookup some na maioria dos builds e o tempo total cai significativamente.

1.1.2 - 2026-05-25

Added

  • Botão "Baixar XLS" no detalhe do processamento (/processamentos/{id}) e endpoint GET /processamentos/{id}/arquivo, fechando a lacuna deixada pelo armazenamento no Garage (v1.1.0): operadores recuperam pela tela a planilha original processada (lê do Garage ou do arquivo_bytes legado conforme o modo).

Fixed

  • Rodapé de paginação do histórico de processamentos exibia os literais page1 e totalPages no lugar dos números calculados (expressão Thymeleaf com variáveis fora de ${...}).
  • Página 2+ do histórico de processamentos retornava "Nenhum processamento encontrado" mesmo com registros existentes: o link "Próxima" emitia status= vazio e o SQL filtrava para status literalmente igual à string vazia. Empty/blank agora é normalizado para "sem filtro".

1.1.1 - 2026-05-25

Changed

  • Padronização estrutural do código entre os projetos de BI (vantroba/onpetro): convenções de teste, naming e organização de arquivos alinhados entre os repositórios irmãos.

1.1.0 - 2026-05-25

Added

  • Armazenamento do binário XLS recebido em object storage S3-compatível (Garage) via AWS SDK v2. Modo configurável app.storage.modo / S3_FILE_STORAGE: PSQL (status quo), PSQL_GARAGE (default — grava nos dois e lê do Garage, rede de segurança) ou GARAGE (só Garage, BYTEA fica NULL). Chave content-addressed {aaaa}/{mm}/{checksum}.xlsx.
  • StorageBackfillService opt-in (app.storage.backfill.enabled=true) que migra os blobs legados (arquivo_bytes) pro Garage no startup, antes do recovery. Idempotente; falha por linha não aborta as demais.
  • HTTP 503 (StorageIndisponivelException) quando o Garage está indisponível durante o upload — falha transitória, cliente reenvia.
  • Migration V11__xls_processamento_objeto_storage.sql: colunas objeto_bucket, objeto_chave, objeto_tamanho_bytes em xls_processamento.

1.0.1 - 2026-05-22

Fixed

  • Login Google: corrigido o erro 400 "Token CSRF inválido" no POST /login/callback — o token CSRF da página de login era sobrescrito pelo model global das views, deixando o <meta> sem o token.

1.0.0 - 2026-05-22

Added

  • Processamento de planilhas XLSX comerciais com detecção automática de 7 tipos (POSTOS, TRR, RELATORIO18, MARGEM_CONSOLIDADA, MARGEM_DIA, META, META_FERNANDO), parsing por streaming (SAX) e persistência em PostgreSQL.
  • API REST POST /api/comercial/processar protegida por token Bearer, com endpoints de consulta de processamentos e logs de execução.
  • Views server-rendered em /processamentos para acompanhar uploads, status e logs.
  • Conceito de lote (xls_conjunto_lote) para rastrear conjuntos de uploads relacionados.
  • Schema bi_* compatível com PowerSync (PK id UUID + chave natural UNIQUE), sincronizável com clientes mobile.
  • Upsert diff-aware: grava apenas as linhas que de fato mudaram, com as métricas linhas_efetivas/linhas_removidas de churn evitado.
  • Nuke da replicação PowerSync — tela /admin e endpoint REST que zeram o estado replicado (slot Postgres + database Mongo) e forçam full re-sync dos clientes; restart automático do PowerSync via API REST do Coolify.
  • Login Google (Firebase) nas views server-rendered, restrito a emails @xadm.com.br, com proteção CSRF nos formulários.
  • Error tracking via Bugsink (protocolo Sentry).
  • Notificações de processamento via Telegram.