0017 — Compras sem chave natural: surrogate PK + replace-por-período¶
Status: Aprovado · Responsável: Gustavo Madruga · Atualizado em: 2026-07-29 · Decidido em: 2026-07-29
Contexto¶
O processador de Compras (spec 008,
migração V16) grava o fato bi_compra (item de NF de compra combustível). Todo o
resto do repo usa o padrão diff-aware: INSERT … ON CONFLICT (chave_natural) DO
UPDATE … WHERE IS DISTINCT FROM + DELETE seletivo, que preserva id/xmin em linhas
inalteradas e evita checkpoints PowerSync desnecessários (spec 003).
Esse padrão exige uma chave natural estável. Em bi_compra a candidata óbvia
(cnpj, numero_nota, produto) colide em dados reais: no arquivo real, as NFs
802962/1, 803823/1 e 806243/1 aparecem 2× cada (mesmo fornecedor, mesma nota,
mesmo produto, quantidades/valores diferentes — item de nota fracionado). O arquivo do
ERP não traz discriminador de linha. Sem chave natural única não há ON CONFLICT.
Decisão¶
bi_compra usa PK surrogate uuidv7() e nenhuma UNIQUE de linha. A
idempotência/correção fica no replace-por-período: numa transação,
DELETE FROM bi_compra WHERE data_nf BETWEEN <min> AND <max> (intervalo de Data NF do
arquivo) seguido de INSERT de todas as linhas combustível (CompraRepository). O
upsert de bi_fornecedor (chave natural cnpj limpa, mantém diff-aware) roda antes,
para satisfazer a FK cnpj_fornecedor → bi_fornecedor(cnpj).
O skip do reenvio idêntico continua vindo do checksum SHA-256 do xls_processamento
(mecanismo type-agnostic já existente), então o DELETE+INSERT — que recicla id do
período — só dispara em arquivo corrigido, não no reenvio igual.
Consequências¶
- Diverge conscientemente do diff-aware do resto do repo; é a exceção documentada
(ver a cobertura de DELETE no cap. 4 do livro).
bi_fornecedornão diverge. - Premissa: um arquivo é o conjunto completo do seu período. Se um mês vier partido
em 2 arquivos, o 2º apaga o 1º (sem chave natural não dá
DELETE … AND NOT EXISTSpor linha). A confirmar com quem exporta o ERP; a v1 assume completo. - Correção de período recicla
idUUID das linhas daquele período → um checkpoint PowerSync no período. Aceito: só ocorre em arquivo corrigido (o checksum barra o reenvio idêntico), e não há discriminador de linha para fazer melhor. - Contrato com o cliente Flutter (
bi-comercial, painel 015) e sync rules (../powersync/) já assumem esse schema — colunas alinhadas.
Alternativas consideradas¶
- Chave natural
(cnpj, numero_nota, produto)+ diff-aware: descartada — colide em dados reais (3 casos no arquivo de amostra), quebraria oON CONFLICT. - Adicionar um índice de linha sintético (ex. ordinal na NF) à chave: o arquivo não fornece ordinal estável entre reuploads; um contador de parse não é determinístico entre exportações. Descartado — daria falsa estabilidade.
- Hash da linha inteira como chave: duas linhas idênticas de propósito (item fracionado com mesmos valores) colidiriam de novo; e qualquer mudança de valor criaria linha nova (churn). Descartado.
Correção 2026-09-14: o ponteiro para o
CLAUDE.md/HOWTO-DEPLOY.mdpassou para a página dona do fato; os dois arquivos deixaram de carregar esse conteúdo.