fix: use webhook variation data as fallback for importation queue - #174
fix: use webhook variation data as fallback for importation queue#174luizabrancaglion wants to merge 2 commits into
Conversation
/produto.obter.php never returns variacoes for parent products, so the queuing condition was always false and new variations added in Tiny were never enqueued for scheduledSync to process. tinyProduct.variacoes (from the webhook dados spread) already carries the full variation list in unwrapped format. Use it as fallback, normalizing to the wrapped format the forEach expects. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
Revisei o diff rastreando o caminho completo — Resumo: o desenho do fix está certo e o caminho antigo fica intacto (quando
Premissas do PR — o que consegui confirmar
Confirmei também que não há loop de auto-enfileiramento: a variação enfileirada ( 🔴 Crítico — o enfileiramento roda numa promise flutuante, depois da function já ter terminadoA estrutura é pré-existente, mas o PR a transforma no mecanismo central do fix, justo no caminho (PubSub) onde a janela de risco é maior. O job devolvido ao
Cenário concreto: loja de baixo tráfego, instância fria. Lojista salva o pai no Tiny → webhook processado, PATCH ok, log Agravante no mesmo bloco: se o enfileiramento falhar de verdade (Store API 5xx), o 🟠 Alto — todo webhook do pai re-enfileira TODAS as variações, não só as novasO fallback usa a lista completa Antes do PR (aceitando a premissa 1) esse bloco nunca rodava no fluxo do webhook. Depois dele, cada save do pai no Tiny (título, preço, descrição — qualquer coisa que dispare Cenário concreto: pai com 60 variações, lojista ajusta só o título → 60 entradas na fila → ~120 chamadas ao Tiny em sequência → bloqueio por volume ( Não consegui confirmar se webhooks 🟡 Menores
Patch sugerido — mesmo objetivo, sem os três problemasTudo dentro do mesmo bloco ( // normaliza AS DUAS fontes: obter.php (encapsulado) e webhook (flat)
const parseVariacoes = (list) => (Array.isArray(list) ? list : [])
.map((v) => (v && v.variacao ? v.variacao : v))
.filter((variacao) => variacao && (variacao.codigo || variacao.sku))
const variacoes = parseVariacoes(
(Array.isArray(produto.variacoes) && produto.variacoes.length)
? produto.variacoes
: (tinyProduct && tinyProduct.variacoes)
)
if (!variacoes.length) {
return promise
}
// ATENÇÃO: `product` aqui está sombreado pelo callback do parseProduct (linha 247);
// as variações JÁ cadastradas estão em `payload.product` (linha 131)
const currentSkus = (payload.product && Array.isArray(payload.product.variations))
? payload.product.variations.map(({ sku }) => sku)
: []
// encadeado no job: o enfileiramento termina ANTES da function resolver
return promise.then((apiRes) => {
if (!productId) {
productId = apiRes && apiRes.response &&
apiRes.response.data && apiRes.response.data._id
}
const pendingSkus = variacoes
.map(({ codigo, sku }) => String(codigo || sku))
.filter((codigo) => {
// nunca enfileirar o próprio pai nem o que já está vinculado
return codigo !== String(produto.codigo) && !currentSkus.includes(codigo)
})
if (!pendingSkus.length) {
return apiRes
}
return getAppData({ appSdk, storeId, auth })
.then((appData) => {
let skus = appData.__importation && appData.__importation.skus
if (!Array.isArray(skus)) {
skus = []
}
let isQueuedVariations = false
pendingSkus.forEach((codigo) => {
const skuAndId = productId ? `${codigo};:${productId}` : codigo
if (!skus.includes(codigo) && !skus.includes(skuAndId)) {
isQueuedVariations = true
skus.push(skuAndId)
}
})
if (!isQueuedVariations) {
return null
}
return updateAppData({ appSdk, storeId, auth }, {
__importation: {
...appData.__importation,
skus
}
})
})
// falha de enfileiramento não pode derrubar um PATCH que já foi aplicado
.catch(logger.error)
.then(() => apiRes)
})O que cada pedaço resolve:
Não mexi na estratégia do PR: a fonte de dados, a normalização e o formato Opcional, fora deste PR: o Menor 4 (nenhum retry periódico para ✅ QA manual — obrigatório antes do mergeSem cobertura automatizada, nada aqui se valida sem teste manual. Todos com
Os cenários 1, 2 e 3 são os decisivos: o 1 diz se resolve, o 2 se resolve de forma confiável, o 3 quanto custa. Onde eu posso estar errado, e vale conferir antes de tudo: (a) um payload real do Com o patch acima (principalmente o encadeamento do enfileiramento) e os cenários 1-3 verdes, não vejo impedimento para o merge. Review gerada com Claude Code (modelo Fable), conferida linha a linha contra o código. |
Follow-up: fui atrás das duas premissas que deixei em aberto. Uma delas não se sustenta — e isso muda o veredito.Também corrijo abaixo dois pontos que eu escrevi a mais no comentário anterior. 🔴 A premissa central do PR é falsa:
|
tipoVariacao |
variacoes no retorno |
Array.isArray() |
|---|---|---|
P (pai) |
array com 5 itens, encapsulado {variacao:{…}} |
true |
V (variação) |
string vazia "" |
false |
N (simples) |
string vazia "" |
false |
Consequência direta: a condição antiga Array.isArray(produto.variacoes) && produto.variacoes.length não era sempre false — ela é verdadeira justamente no cenário-alvo. O ternário do PR cai sempre no primeiro ramo e o fallback _tinyVars é inalcançável ali. O patch é no-op no caso que se propõe a corrigir.
O bug relatado é real, mas o mecanismo descrito não o explica — então o commit, do jeito que está, grava no histórico uma causa raiz que a doc contradiz. Quem debugar isso daqui a seis meses vai partir de uma premissa errada.
✅ A outra premissa se confirma, e a normalização está correta
Baixei o exemplo oficial de payload (api-docs/files/webhook-produto.json, "Exemplo de produto pai"): variacoes vem flat, cada item com codigo, id, preco, estoqueAtual, grade. A normalização v.variacao ? v : { variacao: v } é coerente com a convenção que o próprio repo já codifica em parsers/product-to-ecomplus.js:78 (isProduct = tipo === 'produto') e :179-181.
Um detalhe que valida a decisão de extrair só o codigo: o grade diverge entre as duas fontes — array de {chave, valor} no webhook, objeto {"Cor": "Azul"} no obter. O parser trata os dois (:193-219), mas qualquer tentativa futura de montar a variação direto do payload do webhook depende disso. O PR não cai nessa armadilha.
↩️ Duas correções ao meu comentário anterior
- O agravante do webhook de estoque não procede. A doc do
tipo=estoquelista sótipoEstoque,saldo,idProduto,sku,skuMapeamento,skuMapeamentoPai— não hávariacoes. A amplificação da fila que levantei só pode ocorrer em webhooktipo=produto, isto é, em edição de cadastro, não em giro de estoque. O achado 🟠 continua de pé, com alcance menor do que eu descrevi. - O "Menor 2" fica mais fraco. No payload de produto os itens de
variacoestrazemcodigo; oskué chave do payload de estoque. O cenário"undefined;:parentId"é bem menos provável nesse caminho — mantenho o.filtercomo defesa barata, mas não como achado.
🔍 O que os logs de produção mostram
Numa janela de ~25 min de logs das functions, nenhuma ocorrência do formato sku;:productId e nenhuma fila __importation/skus sendo processada — só ___importation (três underscores, a do scheduledSync). Amostra pequena e sem nenhuma adição de variação no período, então não prova nada sozinha; mas é consistente com o mecanismo de enfileiramento não estar disparando na operação normal.
Onde isso deixa o PR
Sugiro segurar o merge e reinvestigar a causa raiz antes de decidir o que entra. O patch atual não é perigoso — é inócuo no caminho descrito.
A hipótese que sobe para primeira posição é justamente o 🔴 do comentário anterior: se a condição do bloco de enfileiramento era verdadeira o tempo todo, então a explicação mais provável para "nada era enfileirado" é o updateAppData rodando na promise flutuante, depois do cb já ter resolvido a function — perda silenciosa, intermitente, sem log. Isso bate com o sintoma relatado e é independente deste PR.
Ordem que eu seguiria:
- Reproduzir o cenário 1 do QA numa loja com
update_productativo, olhando o log em tempo real: o bloco de enfileiramento é alcançado? OupdateAppDatacompleta? - Se confirmar a promise flutuante, aproveitar esta branch: novo commit com o encadeamento (item 3 do patch que propus), descrição reescrita, e a discussão fica aqui mesmo.
- Se apontar outra coisa, fechar este PR e abrir o correto.
O encadeamento do enfileiramento vale por si só de qualquer forma — com ou sem o fallback, aquele bloco não deveria estar rodando fora do job.
Verificação feita chamando a API v2 real numa conta de teste e conferindo contra a doc oficial (tiny.com.br/api-docs).
Causa raiz encontrada — e não é o que este PR corrigeRastreei duas reproduções reais em produção no Cloud Logging (uma de hoje, outra de 23/07) e baixei o código que está de fato rodando. O resultado fecha a investigação. 1. O enfileiramento funciona — a fila é populada e consumida corretamenteSequência real, produto pai com 3 variações novas adicionadas no Tiny (SKUs anonimizados, O bloco de enfileiramento rodou, gravou O produto pai terminou com o SKU e o nome da última variação processada, sem nenhuma Consequência para este PR: a condição 2. Por que o guard do
|
| valor | |
|---|---|
updateTime de todas as functions (gen1 e gen2, appv2 inclusive) |
2025-07-25T20:36Z |
lib/integration/import-product.js deployado |
314 linhas |
ocorrências de variations.json |
0 |
ocorrências de product.sku !== produto.codigo |
0 |
ocorrências de variacaoData |
0 |
O código em produção é o da release 4.2.0, de julho de 2025. Nem o f67f8ae (09/07) nem o f7842ae (17/07) chegaram lá — e este PR, se mergeado, também não chegaria.
Os workflows Deploy de 2026-07-10 e 2026-07-23 aparecem como success no Actions, com o step "Run deploy" verde, mas as functions não foram atualizadas. Os logs desses runs já expiraram, então a causa do falso-positivo ainda não está determinada — as hipóteses mais prováveis são o secret FIREBASE_PROJECT_ID apontando para outro projeto, ou o client.deploy() do scripts/firebase-deploy.js resolvendo sem efeito (o FIREBASE_TOKEN/firebase login:ci foi descontinuado pelo Google, e functions.config foi removido do firebase-tools recente).
O que fazer
- Consertar o deploy primeiro. É o bloqueador de tudo: qualquer correção de código é inócua enquanto o pipeline reportar sucesso sem publicar. Vale um step de verificação no workflow que compare a versão publicada com a do commit.
- Redeployar o
f7842aee reproduzir o cenário. É bem possível que o bug relatado simplesmente desapareça — o guard existe justamente para transformar esses três PATCH emPOST /products/<pai>/variations.json. - Só então avaliar o que ainda falta. Com o guard rodando, dá para ver se o
variacaoDataé montado (oproduto.variacoesde uma variação vem como string vazia no obter, então tudo depende doproduto.grade— vale conferir com payload real). - Fechar este PR. A premissa não se sustenta e o patch é no-op. A branch pode ser reaproveitada para o encadeamento do enfileiramento (o 🔴 do primeiro comentário), que continua valendo por si só, mas é uma mudança de escopo — melhor abrir outro PR depois que o deploy estiver saudável.
Nada disso tira o mérito de ter caçado o sintoma certo, @luizabrancaglion — o caminho que você mapeou (webhook → fila → vinculação) é exatamente o certo. O que não dava para ver do código era que a produção estava rodando outra versão.
Diagnóstico feito com Cloud Logging (janelas exatas das duas reproduções) e com o source deployado baixado da API do Cloud Functions. Identificadores de loja e produto omitidos por serem dados de lojista.
|
Complemento: refiz o deploy hoje (re-run do workflow no mesmo commit) para confirmar ao vivo a hipótese do comentário anterior. Ficou verde de novo e não publicou nada — Com o log fresco deu para ver onde morre: o step dura 3 segundos, não emite nenhuma linha do Isso mantém a recomendação: este PR não muda nada em produção enquanto o deploy não for consertado, e a premissa dele já estava descartada de qualquer forma. |
Three bugs in parseProduct when tinyProduct.anexos is an empty array: 1. TypeError at line 289: images[0] is undefined when anexos=[] but variation.picture_id=0 was always set - adds null guard 2. picture_id: 0 sent to API even when no images exist - now only set when tinyProduct.anexos is non-empty 3. variation.price set to NaN (null in JSON) when preco is undefined (Tiny v1.0.1 webhook variacoes) - added isNaN guard Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Problema
Após
413b1aa, o guard de variação e o POST estão corretos — mas variações novas adicionadas no Tiny nunca chegavam a ser vinculadas ao produto pai no E-Com Plus.Causa raiz: O bloco de enfileiramento (linha 268) verificava
produto.variacoes, que vem do/produto.obter.php. Esse endpoint nunca retorna o arrayvariacoespara produtos pai — a condição era semprefalse, nada era enfileirado, oscheduledSyncnão processava e a variação nunca era vinculada.O payload do webhook já carrega a lista completa de variações em
tinyProduct.variacoes(via spread dedados), no formato não-encapsulado[{codigo, grade, ...}].Correção
Resolve
_variacoesQueuepreferindo o resultado do/produto.obter.php(encapsulado, quando presente) e usandotinyProduct.variacoesnormalizado como fallback. OforEache toda a lógica downstream permanecem iguais.Fluxo completo após este PR
__importation.skuscom;:parentId← este fixscheduledSyncprocessa cada SKU → guard de413b1aadetectaproduct.sku !== produto.codigo→ POST/products/:id/variations.json→ variação vinculada ✓Como testar
scheduledSync)#<storeId> POST /products/.../variations.json <sku-variacao>🤖 Generated with Claude Code