23 KiB
Story 3.3: Incluir vl_acrescimo no MD5 de Change-Detection
Status: done
Story
Como sistema,
quero que mudanças isoladas de vl_acrescimo num pedido disparem re-sync do registro local,
para que ajustes de acréscimo no ERP (reabertura, correção manual server-side) sejam propagados ao dispositivo.
Acceptance Criteria
-
Dado que um pedido já sincronizado em
pedido_consultatemvl_acrescimo = 87.50emd5 = H1Quando o ERP alterar apenas oacrev(Gerente) outx_acrescimo(SIG) do registro e o próximoselectAllPedConsultarodar Então omd5retornado pelo PG difere deH1,PedidoConsultaDB.saveAlldetecta a divergência e disparaupdateErp, gravando o novovl_acrescimoe o novomd5empedido_consulta -
Dado que o MD5 server-side inclui o campo de acréscimo no hash (
acrevpara Gerente;tx_acrescimopara SIG quando coluna existe) Quando o mesmo pedido for lido em duas execuções consecutivas deselectAllPedConsultasem alteração no ERP Então omd5é idêntico nas duas execuções — não há loop de "diferente/igual" entre pedidos já em dia -
Dado que pedidos pré-Epic 3 no ERP têm
acrev = 0(Gerente) outx_acrescimo = 0(SIG) após aplicação doDEFAULT 0Quando o primeiroselectAllPedConsultaapós o deploy de Story 3.3 rodar Então esses pedidos aparecem como "atualizados" (contadortt[2]incrementa pois o novo termo no MD5 altera o hash),vl_acrescimogravado permanece0.0, sem crash, sem travamento — comportamento aceitável e idempotente -
Dado que
Global.sistema == SISTEMA_SIGe a colunatx_acrescimoainda não existe emsig.pedidos(hasColunaAcrescimoSig() == false) Quando oselectAllPedConsultaSIG construir o MD5 Então o MD5 é calculado sem o termoCOALESCE(a.tx_acrescimo, 0.0), a query executa normalmente, e o comportamento de re-sync permanece idêntico ao de pré-Story 3.3 até o DDL ser aplicado
Escopo desta história
SIM:
- Adicionar
|| COALESCE(A.acrev, 0.0)ao final do hash MD5 no SELECT Gerente deselectAllPedConsulta() - Adicionar condicionalmente
|| COALESCE(a.tx_acrescimo, 0.0)ao hash MD5 no SELECT SIG, reutilizandotemAcrescSigConsulta(já calculado em Story 3.2)
NÃO cobre:
- Cálculo de MD5 no lado do app (o app nunca calcula MD5 local — apenas armazena o hash retornado pelo PG; ver "Como o change-detection funciona de fato" abaixo)
PedidoConsultaDB.saveAll— já comparamd5stored vsped.getMd5()e disparaupdateErpdesde versões anteriores (nenhuma mudança necessária)- DDL server-side —
acrevem Gerente etx_acrescimoem SIG são gerenciados pelo DBA; app continua tolerante (Stories 3.1/3.2) pedido_consulta.md5no SQLite — coluna já existe e é populada porinsert/updateErpemPedidoConsultaDB
Pré-condições verificadas
- MD5 é calculado apenas no PG: grep confirma que
MD5(...)aparece só em PedidoPGSQL.java:628 (Gerente) e PedidoPGSQL.java:658 (SIG). Não existe cálculo de MD5 no app — o hash armazenado é o que o PG retornou no sync anterior. temAcrescSigConsultajá é declarada em PedidoPGSQL.java:619 e populada viahasColunaAcrescimoSig()em PedidoPGSQL.java:653 (Story 3.2). Reutilizar sem duplicar.acrevem Gerente já é lido pelo SELECT após Story 3.2 (linha 630:COALESCE(a.acrev, 0.0) as acrev). A coluna certamente existe emgerente.pedidos(confirmado em Story 3.1). Safe para incluir no MD5 sem tolerância.tx_acrescimoem SIG é lido condicionalmente (linhas 661–663). A condiçãoif (temAcrescSigConsulta)vale tanto para a projeção quanto para o MD5.PedidoConsultaDB.saveAllcompara MD5 e disparaupdateErpem PedidoConsultaDB.java:60-67 —updateErpjá gravavl_acrescimo(linha 183) emd5(linha 177). Fluxo completo funciona sem alterações nessa classe.dbVersaopermanece 43 — esta story não altera schema SQLite.COALESCE(x, 0.0)produz string estável nomd5()do PostgreSQL (concatenação via||com cast implícito para text); padrão idêntico ao já usado paraid_formapag,id_pauta,obs.
Tasks / Subtasks
-
Task 1: Incluir
acrevno MD5 do SELECT Gerente (AC: 1, 2, 3)- 1.1 — Em PedidoPGSQL.java:629, alterar a linha que termina com
A.total),:// ANTES: sql.append(" COALESCE(B.id_formapag,0) || COALESCE(A.obs,'') || COALESCE(C.id_pauta,0) || A.total),");// 12 // DEPOIS: sql.append(" COALESCE(B.id_formapag,0) || COALESCE(A.obs,'') || COALESCE(C.id_pauta,0) || A.total || COALESCE(A.acrev, 0.0)),");// 12A coluna
acrevjá existe emgerente.pedidos(confirmado em Story 3.1).COALESCE(..., 0.0)protege contraNULLhistórico e garante hash estável para pedidos sem acréscimo. Não alterar a linha 630 (projeçãoCOALESCE(a.acrev, 0.0) as acrev) — ela é o SELECT do valor paravlAcrescimo, não do termo do MD5, e já foi feita em Story 3.2.
- 1.1 — Em PedidoPGSQL.java:629, alterar a linha que termina com
-
Task 2: Incluir
tx_acrescimocondicionalmente no MD5 do SELECT SIG (AC: 1, 2, 3, 4)- 2.1 — Em PedidoPGSQL.java:658-659, quebrar o append da linha do MD5 em três partes para permitir inserção condicional:
// ANTES: sql.append(" MD5(num_ped_sar || id_pedido || numero || A.tipo || situa || data || clien || COALESCE((SELECT data_emissao FROM sig.pedidos WHERE id_empresa=A.id_empresa AND tipo='E' AND numer_ped_vinc=A.numero ORDER BY data_emissao DESC LIMIT 1),A.data) ||"); sql.append(" COALESCE(id_formapag,0) || COALESCE(A.obs,'') || COALESCE(C.id_pauta,0)) as md5,");// 12 // DEPOIS: sql.append(" MD5(num_ped_sar || id_pedido || numero || A.tipo || situa || data || clien || COALESCE((SELECT data_emissao FROM sig.pedidos WHERE id_empresa=A.id_empresa AND tipo='E' AND numer_ped_vinc=A.numero ORDER BY data_emissao DESC LIMIT 1),A.data) ||"); sql.append(" COALESCE(id_formapag,0) || COALESCE(A.obs,'') || COALESCE(C.id_pauta,0)"); if (temAcrescSigConsulta) { sql.append(" || COALESCE(a.tx_acrescimo, 0.0)"); } sql.append(") as md5,");// 12Quando
temAcrescSigConsulta = false, o SQL resultante é idêntico ao pré-Story 3.3 — garantindo AC 4 (tolerância total à ausência de DDL). Quandotrue, o hash passa a depender detx_acrescimo, disparando re-sync em alterações server-side isoladas (AC 1).
- 2.1 — Em PedidoPGSQL.java:658-659, quebrar o append da linha do MD5 em três partes para permitir inserção condicional:
Dev Notes
Como o change-detection funciona de fato
Apesar do epic mencionar "app calcular o MD5 local", a inspeção do código confirma que não há cálculo de MD5 no lado do app. O fluxo real é:
- Sync anterior:
selectAllPedConsultaretornamd5calculado pelo PG;PedidoConsultaDB.insert/updateErparmazena esse hash empedido_consulta.md5. - Sync atual:
selectAllPedConsultaretorna novomd5do PG;saveAllcompara com o hash stored (PedidoConsultaDB.java:60). - Decisão:
md5 == null→ pedido novo →insertmd5.equals(ped.getMd5())→ inalterado →continue(skip)!equals→ alterado →updateErp(grava novos dados e novo hash)
Portanto, a consistência requerida pelo AC 2 é a consistência da fórmula MD5 do PG entre syncs — não entre app e servidor. Isso significa que, enquanto o código do SELECT permanecer o mesmo, o hash será determinístico para o mesmo conteúdo do pedido.
Por que acrev (não acrep) no MD5 Gerente
acrep é a taxa percentual (ex: 2.5). acrev é o valor monetário do acréscimo (ex: 87.50). Como vl_acrescimo no SQLite armazena o valor monetário (Story 2.3), e como acrev é o que Story 3.2 já lê para popular Pedido.vlAcrescimo em Gerente, faz sentido incluir apenas acrev no hash — mudar acrep sem mudar acrev implicaria inconsistência no próprio PG que o app não precisa detectar. Além disso, para todo pedido gravado via Story 3.1, acrep e acrev mudam juntos (são ambos derivados de tx_acrescimo + subtotal na hora do INSERT) — redundância desnecessária no hash.
Por que tx_acrescimo (não valor calculado) no MD5 SIG
Em SIG, o valor do acréscimo é derivado server-side (total × tx_acrescimo / 100). O único campo armazenado é tx_acrescimo. Se o valor calculado mudar por alteração de total (já no MD5) ou de tx_acrescimo (novo termo), ambos os casos ficam cobertos. Não é necessário incluir o cálculo completo — incluir só tx_acrescimo basta.
Snapshot do SQL resultante (para validação rápida no logcat)
Gerente (sempre):
MD5(A.num_ped_sar || A.id_pedido || A.numero || A.tipo || A.situa || A.data || A.clien || COALESCE(A.dtemi,NOW()) ||
COALESCE(B.id_formapag,0) || COALESCE(A.obs,'') || COALESCE(C.id_pauta,0) || A.total || COALESCE(A.acrev, 0.0)),
SIG com DDL aplicado (temAcrescSigConsulta = true):
MD5(num_ped_sar || id_pedido || numero || A.tipo || situa || data || clien || COALESCE((SELECT ...),A.data) ||
COALESCE(id_formapag,0) || COALESCE(A.obs,'') || COALESCE(C.id_pauta,0) || COALESCE(a.tx_acrescimo, 0.0)) as md5,
SIG sem DDL (temAcrescSigConsulta = false):
MD5(num_ped_sar || id_pedido || numero || A.tipo || situa || data || clien || COALESCE((SELECT ...),A.data) ||
COALESCE(id_formapag,0) || COALESCE(A.obs,'') || COALESCE(C.id_pauta,0)) as md5,
Impacto operacional no primeiro sync após deploy (AC 3)
Como o MD5 passa a incluir acrev/tx_acrescimo, todos os pedidos em pedido_consulta terão hash diferente no primeiro selectAllPedConsulta após o deploy:
- Gerente: todos os pedidos existentes serão marcados como "alterados" (
tt[2]++) —updateErprodará para cada um;vl_acrescimoserá gravado com o valor deacrev(pode ser0para pedidos pré-Epic 3, ou valor real para pedidos enviados via Story 3.1). - SIG com DDL já aplicado: idem —
vl_acrescimorecalculado viatotal × tx_acrescimo / 100. - SIG sem DDL: nenhum impacto — MD5 não muda; re-sync não dispara; comportamento pré-Story 3.3 preservado. Quando DDL for aplicado, o próximo sync disparará re-sync de todos os pedidos SIG de uma só vez.
Isso é aceitável e idempotente: após o re-sync, o hash estabiliza e os syncs seguintes operam normalmente.
Arquivo a modificar
| Arquivo | Caminho | O que muda |
|---|---|---|
PedidoPGSQL.java |
src/br/com/jcsinformatica/sarandroid/postgres/ |
selectAllPedConsulta(): append ` |
NÃO modificar:
PedidoConsultaDB.java—saveAlljá compara einsert/updateErpjá gravam (Stories 2.3/2.4)Pedido.java— VO completo (Story 2.3)DatabaseHelper.java— schema SQLite não muda;dbVersaopermanece 43hasColunaAcrescimoSig()— já existe (Story 3.1); reutilizar
Regras críticas do projeto (aplicáveis)
- Sem Kotlin — somente Java puro
- Sem Gradle — projeto Eclipse ADT
- Sem JARs novos — nenhuma dependência adicional
- Sem testes automatizados — validação manual via dispositivo/emulador
- SQL por
StringBuilder— padrão projeto-wide mantido - Thread background para I/O —
selectAllPedConsultajá roda emAsyncTaskdaComunicaActivity ConnectionManager.closeAll(st, rs)— já chamado em PedidoPGSQL.java:761; não alterar
Inteligência de histórias anteriores
- Story 3.2: declarou
temAcrescSigConsultaem PedidoPGSQL.java:619 e populou-o viahasColunaAcrescimoSig()em linha 653 — Story 3.3 apenas lê essa variável dentro do mesmo método; nenhum novo estado. - Story 3.1: criou
hasColunaAcrescimoSig()(linhas 1196–1212), com log de warning em caso de exceção. Disponível para uso; não alterar. - Story 2.3:
Pedido.vlAcrescimocom getter/setter epedido.vl_acrescimoem SQLite (DatabaseHelper.onUpgradev43 linha 713). - Story 2.4:
PedidoConsultaDB.selectFulllêvl_acrescimo(linha 296) — tela de consulta já exibe corretamente após re-sync. - Padrão de MD5 existente: o hash Gerente já incluía
A.totalenquanto o hash SIG não incluíaA.total— mantenha essa assimetria. Apenas adicionar o novo termo ao final, sem reorganizar a ordem dos demais termos (alterar a ordem invalidaria o hash de pedidos pré-deploy, gerando re-sync em massa desnecessário já capturado pelo novo termo).
Verificação manual após implementação
-
Gerente — mudança isolada de
acrevdispara re-sync:- Selecionar um pedido em
gerente.pedidosjá sincronizado no app (registro existe empedido_consulta) UPDATE gerente.pedidos SET acrev = acrev + 10.00 WHERE id_pedido = <X>direto no PG- Rodar ComunicaActivity
- Verificar logcat:
INSERT PEDIDO CONSULTAou log deupdateErppara o pedido;pedido_consulta.vl_acrescimoatualizado;pedido_consulta.md5atualizado
- Selecionar um pedido em
-
Gerente — sync repetido sem alteração não dispara re-sync:
- Rodar ComunicaActivity duas vezes seguidas, sem mudar o PG entre rodadas
- Contador
tt[1](skip) deve cobrir todos os pedidos na segunda execução
-
SIG com DDL aplicado — mudança isolada de
tx_acrescimo:- Pedido em
sig.pedidoscomtx_acrescimo = 2.5,total = 1000.00→ app temvl_acrescimo = 25.00 UPDATE sig.pedidos SET tx_acrescimo = 3.0 WHERE id_pedido = <X>direto no PG- Rodar ComunicaActivity →
pedido_consulta.vl_acrescimo = 30.00; hash atualizado
- Pedido em
-
SIG sem DDL — tolerância total:
- Com
tx_acrescimoausente emsig.pedidos(ambiente sem DDL) - Rodar ComunicaActivity → nenhum crash; log
SQL PEDIDO CONSULTAmostra MD5 sem o termotx_acrescimo; syncs consecutivos têm contadorestt[0]/tt[1]/tt[2]idênticos ao pré-Story 3.3
- Com
-
Primeiro sync após deploy — re-sync em massa aceitável (AC 3):
- Dispositivo com
pedido_consultapopulado pré-Story 3.3 (md5sem termo de acréscimo) - Fazer upgrade do app → rodar ComunicaActivity
- Esperado:
tt[2](alterados) cobre todos os pedidos Gerente e SIG-com-DDL já sincronizados; execução conclui sem erro; syncs subsequentes normalizam paratt[1](skip)
- Dispositivo com
-
Log para debug:
Log.d("SQL PEDIDO CONSULTA = ", sql.toString())em PedidoPGSQL.java:679 — conferir presença/ausência do termo no MD5 conforme sistema e DDL
Git intelligence (commits recentes)
f2cf45dStory 2.4:pedido_consulta.vl_acrescimo+ guardstatus >= STATUS_ENVIADOem consulta — campo já gravado e lido no SQLite3ff26a7Story 2.3:Pedido.vlAcrescimo+PedidoDB/PedidoConsultaDBpreparados- Story 3.1 (uncommitted):
PedidoPGSQL.insert/insertSig/save/hasColunaAcrescimoSig— padrões a seguir - Story 3.2 (uncommitted):
selectAllPedConsultaleitura inbound +temAcrescSigConsulta— variável a reutilizar nesta story
References
- Epics:
_bmad-output/planning-artifacts/epics.md#story-33-incluir-vl_acrescimo-no-md5-de-change-detection - FR17: MD5 de
pedido_consultaincluivl_acrescimono cálculo de hash - FR16: Sync tolera ausência das colunas de acréscimo no PostgreSQL
PedidoPGSQL.java:619— declaraçãotemAcrescSigConsultaPedidoPGSQL.java:628-629— MD5 Gerente (alvo da Task 1)PedidoPGSQL.java:653— inicializaçãotemAcrescSigConsulta = hasColunaAcrescimoSig()PedidoPGSQL.java:658-659— MD5 SIG (alvo da Task 2)PedidoPGSQL.java:731— leituraped.setMd5(rs.getString("md5"))PedidoPGSQL.java:1196-1212—hasColunaAcrescimoSig()(não alterar)PedidoConsultaDB.java:54-67— comparação stored vs new MD5 emsaveAll(não alterar)PedidoConsultaDB.java:141— gravação demd5eminsert(não alterar)PedidoConsultaDB.java:177— gravação demd5emupdateErp(não alterar)- Memory:
project_pg_sync_acrescimo_schema.md— assimetria Gerente vs SIG confirmada
Dev Agent Record
Agent Model Used
claude-opus-4-7 (create-story workflow)
Debug Log References
N/A — projeto sem infraestrutura de testes automatizados; validação manual via dispositivo/emulador conforme roteiro em "Verificação manual após implementação".
Completion Notes List
- Story 3.3 implementada em 2026-04-16.
- Task 1 — Gerente MD5: PedidoPGSQL.java:629 — linha do MD5 Gerente (após
|| A.total) agora apenda|| COALESCE(A.acrev, 0.0). Única linha alterada no branch Gerente; sem impacto na projeção deacrev(linha 630) já introduzida em Story 3.2. AC 1, 2 e 3 satisfeitos para Gerente: alterações emacrevno PG mudam o hash, disparandoupdateErpemPedidoConsultaDB.saveAll. - Task 2 — SIG MD5 condicional: PedidoPGSQL.java:658-663 — append da linha de MD5 SIG refatorado em três partes. Corpo do hash agora fecha com
COALESCE(C.id_pauta,0)sem parêntese;if (temAcrescSigConsulta)apenda|| COALESCE(a.tx_acrescimo, 0.0);") as md5,"fecha oMD5(...). ReutilizatemAcrescSigConsultapopulado em linha 653 (Story 3.2) — nenhum novo estado. - AC 4 (tolerância SIG sem DDL) preservado por construção: quando
temAcrescSigConsulta = false, o blocoifnão executa e o SQL resultante é byte-for-byte idêntico ao pré-Story 3.3. Verificado via leitura do arquivo após edição. - PedidoConsultaDB não modificado —
saveAll(linha 54-67) já comparamd5stored vs retornado e disparaupdateErp;insert/updateErpjá gravammd5evl_acrescimodesde Stories 2.3/2.4. dbVersaopermanece 43 — sem alteração de schema SQLite.- Sem testes automatizados: projeto não possui infraestrutura (conforme CLAUDE.md). Validação via logic trace + inspeção do SQL gerado pelo
Log.d("SQL PEDIDO CONSULTA = ", sql.toString())na linha 679 durante validação manual (roteiro em "Verificação manual após implementação" no corpo da story). - Nenhum import adicionado — todas as classes e métodos necessários já estão no arquivo.
- AC 1 satisfeito: hash Gerente inclui
acrev; hash SIG incluitx_acrescimoquando coluna existe → mudanças isoladas disparamupdateErp. - AC 2 satisfeito: fórmula MD5 determinística; hash estável entre syncs quando conteúdo não muda.
- AC 3 satisfeito: primeiro sync pós-deploy marcará todos pedidos Gerente/SIG-com-DDL como alterados (hash novo ≠ hash stored);
updateErpgravavl_acrescimo = 0.0para pedidos pré-Epic 3 (acrev/tx_acrescimo default0). Idempotente. - AC 4 satisfeito:
temAcrescSigConsulta = false→ SQL SIG idêntico ao pré-Story 3.3; nenhum impacto em ambientes sem DDL.
File List
src/br/com/jcsinformatica/sarandroid/postgres/PedidoPGSQL.java
Review Findings
- [Review][Defer] Ramo Gerente adiciona
A.acrevao MD5 sem guard equivalente ahasColunaAcrescimoSig()[PedidoPGSQL.java:629] — deferred, premissa verificada: Story 3.1 confirmou via inspeção de código quegerente.pedidos.acrevexiste desde antes do Epic 3 (estava na lista de colunas do INSERT sendo gravada como 0); não é cenário hipotético. AdicionarhasColunaAcrescimoGerente()seria over-engineering contra condição que não pode ocorrer no ambiente real. - [Review][Defer] Colisão teórica por concatenação MD5 sem delimitador entre
A.totaleCOALESCE(A.acrev, 0.0)[PedidoPGSQL.java:629] — deferred, risco pré-existente e teórico: padrão de concatenação sem separador já existia em todo o MD5 Gerente/SIG original (num_ped_sar || id_pedido, etc.). Adicionar delimitador só ao novo termo seria inconsistente; refatorar o MD5 inteiro para usar separador é mudança arquitetural fora do escopo de Story 3.3 e forçaria segundo re-sync em massa. - [Review][Defer] Hash pode variar entre versões do PostgreSQL por mudanças em
extra_float_digits/float4out(formatação textual deREAL) [PedidoPGSQL.java:629,661] — deferred, risco pré-existente:A.total(REAL) já estava no MD5 Gerente com a mesma exposição; mitigação real seriato_char(..., 'FM999999990.00')em TODOS os termos numéricos do hash — refatoração fora do escopo. Upgrade/mudança de config do PG já impactaria sync atual. - [Review][Defer]
pedido_consulta.md5TEXT sem NOT NULL →setMd5(null)geraria literal"'null'"e loop de update infinito — deferred, pré-existente: caminho requer falha doMD5()server-side (extremamente improvável); schema definido em Story 2.3 não por esta. - [Review][Dismiss]
Pedido.vlAcrescimopotencialmenteDoubleboxed (NPE em SIG sem DDL) — falso positivo: Story 2.3 Dev Notes e Story 3.1 linha 50 confirmam primitivodoublecom default0.0. - [Review][Dismiss] Alias case
a.tx_acrescimovsA.maiúsculo — segue exatamente o padrão já estabelecido em Story 3.2 [PedidoPGSQL.java:662] na projeção do mesmo campo; PG é case-insensitive em identificadores não-quoted. - [Review][Dismiss] Cast de
0.0(double) vsacrev(REAL) produzindo texto diferente — verificado:gerente.pedidos.acrevé REAL (confirmado porsetDoubleem Story 3.1 linha 183); mesmo tipo em ambos os lados doCOALESCE. - [Review][Dismiss] Comentário
// 13-16em SIG não atualizado — correto por design:tx_acrescimoé coluna condicional (aparece apenas quandotemAcrescSigConsulta=true), mesmo padrão já usado em Story 3.2 linha 666 (//17dentro doif). - [Review][Dismiss] Filtro
AND A.total>0mascara pedidos com total=0 e acrev>0 — pré-existente, fora de escopo; cenário semanticamente inconsistente (acréscimo sobre zero).
Change Log
- 2026-04-16: Story 3.3 criada pelo create-story workflow.
- 2026-04-16: Story 3.3 implementada.
PedidoPGSQL.selectAllPedConsulta(): MD5 Gerente agora inclui|| COALESCE(A.acrev, 0.0)(sempre); MD5 SIG inclui condicionalmente|| COALESCE(a.tx_acrescimo, 0.0)viaif (temAcrescSigConsulta)— reutilizando a variável já populada em Story 3.2 sem duplicar a queryinformation_schema. Completa FR17 do Epic 3: mudanças isoladas de acréscimo no ERP agora disparam re-sync dopedido_consultalocal. Status → review. - 2026-04-16: Code review executado (Blind Hunter + Edge Case Hunter + Acceptance Auditor). Acceptance Auditor aprovou. 4 achados classificados como defer (todos pré-existentes ou premissas documentadas); 5 dismisses. Nenhum patch necessário. Status → done.