From 9a9e9bedf238d51c6175eaf41d4c75fed4717213 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cau=C3=AA=20Faleiros?= Date: Mon, 11 May 2026 12:48:30 -0300 Subject: [PATCH] feat: track average, max, and min stock additions per product in csv and n8n payload --- src/controllers/webhook.controller.ts | 70 ++++++++++++++++++++++----- 1 file changed, 59 insertions(+), 11 deletions(-) diff --git a/src/controllers/webhook.controller.ts b/src/controllers/webhook.controller.ts index 1ab9cf5..924a8ed 100644 --- a/src/controllers/webhook.controller.ts +++ b/src/controllers/webhook.controller.ts @@ -158,15 +158,15 @@ export const handleTinyStockUpdate = async (req: Request, res: Response): Promis return; } - console.log('Received stock webhook from Tiny. Calculating delta...'); + console.log('Received stock webhook from Tiny. Calculating delta and stats...'); let payload = req.body || {}; if (payload && typeof payload.dados === 'string') { try { payload.dados = JSON.parse(payload.dados); } catch (e) {} } - // --- SMART MEMORY LOGIC --- + // --- SMART MEMORY & STATS LOGIC --- const memoryFile = path.join(process.cwd(), 'stock_memory.json'); - let memory: Record = {}; + let memory: Record = {}; // Load memory if it exists if (fs.existsSync(memoryFile)) { @@ -185,18 +185,51 @@ export const handleTinyStockUpdate = async (req: Request, res: Response): Promis let deltaEstoque = 0; if (idProduto) { - const estoqueAntigo = memory[idProduto]; + // Backwards compatibility for old memory format (if it was just a number) + if (typeof memory[idProduto] === 'number') { + memory[idProduto] = { + saldo: memory[idProduto], + addCount: 0, + addTotal: 0, + addMax: 0, + addMin: null + }; + } + + const prodMem = memory[idProduto] || { + saldo: undefined, + addCount: 0, + addTotal: 0, + addMax: 0, + addMin: null + }; + + const estoqueAntigo = prodMem.saldo; if (estoqueAntigo !== undefined) { - // We know what it was before, calculate the difference + // Calculate delta deltaEstoque = novoSaldo - estoqueAntigo; + + // Track statistics if stock was ADDED + if (deltaEstoque > 0) { + prodMem.addCount += 1; + prodMem.addTotal += deltaEstoque; + + if (deltaEstoque > prodMem.addMax) { + prodMem.addMax = deltaEstoque; + } + + if (prodMem.addMin === null || deltaEstoque < prodMem.addMin) { + prodMem.addMin = deltaEstoque; + } + } } else { - // First time seeing this product. Default delta to 0 so we don't alert on initial load. deltaEstoque = 0; } - // Save the new balance to memory - memory[idProduto] = novoSaldo; + // Update current balance in memory + prodMem.saldo = novoSaldo; + memory[idProduto] = prodMem; fs.writeFileSync(memoryFile, JSON.stringify(memory, null, 2)); console.log(`[Stock Tracker] Product ${idProduto}: Old=${estoqueAntigo ?? 'None'} -> New=${novoSaldo}. Delta=${deltaEstoque > 0 ? '+' : ''}${deltaEstoque}`); @@ -206,20 +239,35 @@ export const handleTinyStockUpdate = async (req: Request, res: Response): Promis const csvFile = path.join(process.cwd(), 'stock_log.csv'); const timestamp = new Date().toISOString().replace(/T/, ' ').replace(/\..+/, ''); const oldStockStr = estoqueAntigo !== undefined ? estoqueAntigo : 'NEW'; - const csvLine = `"${timestamp}","${idProduto}","${nomeProduto}","${oldStockStr}","${novoSaldo}","${deltaEstoque > 0 ? '+' : ''}${deltaEstoque}"\n`; + + const adicionado = deltaEstoque > 0 ? deltaEstoque : 0; + const vendido = deltaEstoque < 0 ? Math.abs(deltaEstoque) : 0; + + // Calculate current average + const mediaAdicionado = prodMem.addCount > 0 ? (prodMem.addTotal / prodMem.addCount).toFixed(2) : 0; + const minAdicionado = prodMem.addMin !== null ? prodMem.addMin : 0; + const maxAdicionado = prodMem.addMax; + + const csvLine = `"${timestamp}","${idProduto}","${nomeProduto}","${oldStockStr}","${novoSaldo}","${adicionado}","${vendido}","${mediaAdicionado}","${maxAdicionado}","${minAdicionado}"\n`; // Create file with headers if it doesn't exist if (!fs.existsSync(csvFile)) { - fs.writeFileSync(csvFile, '"Data","ID_Produto","Nome_Produto","Estoque_Antigo","Estoque_Novo","Quantidade_Adicionada"\n'); + fs.writeFileSync(csvFile, '"Data","ID_Produto","Nome_Produto","Estoque_Antigo","Estoque_Novo","Adicionado","Vendido","Media_Adicionado","Max_Adicionado","Min_Adicionado"\n'); } fs.appendFileSync(csvFile, csvLine); } } - // Inject the delta into the payload so n8n can easily read it + // Inject the delta and stats into the payload so n8n can easily read it if (!payload.dados) payload.dados = {}; payload.dados.delta_estoque = deltaEstoque; + if (idProduto && memory[idProduto]) { + const pm = memory[idProduto]; + payload.dados.media_adicionado = pm.addCount > 0 ? (pm.addTotal / pm.addCount).toFixed(2) : 0; + payload.dados.max_adicionado = pm.addMax; + payload.dados.min_adicionado = pm.addMin !== null ? pm.addMin : 0; + } await sendToN8n(payload, targetUrl); } catch (error) {