feat: safely implement database idempotency, fallback IDs, and WhatsApp marketing export
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 2m30s

This commit is contained in:
Cauê Faleiros
2026-05-22 10:54:54 -03:00
parent c77da0a9d0
commit 9e52b2e44f
3 changed files with 27 additions and 6 deletions

View File

@@ -40,6 +40,7 @@ const initDB = async () => {
`); `);
await pool.query(`ALTER TABLE orders ADD COLUMN IF NOT EXISTS pedido_id VARCHAR(100);`).catch(() => {}); await pool.query(`ALTER TABLE orders ADD COLUMN IF NOT EXISTS pedido_id VARCHAR(100);`).catch(() => {});
await pool.query(`ALTER TABLE orders ADD COLUMN IF NOT EXISTS cliente_fone VARCHAR(50);`).catch(() => {});
await pool.query(`CREATE UNIQUE INDEX IF NOT EXISTS unique_order_product ON orders (pedido_id, produto_id);`).catch(err => { await pool.query(`CREATE UNIQUE INDEX IF NOT EXISTS unique_order_product ON orders (pedido_id, produto_id);`).catch(err => {
console.error("Notice: Could not create unique index (might already exist or there are duplicates):", err.message); console.error("Notice: Could not create unique index (might already exist or there are duplicates):", err.message);
@@ -132,13 +133,25 @@ app.post('/api/data', authenticateAPIKey, async (req, res) => {
const insertQuery = ` const insertQuery = `
INSERT INTO orders ( INSERT INTO orders (
cliente_nome, data_pedido, valor_pedido, cliente_nome, data_pedido, valor_pedido,
produto_id, produto_descricao, quantidade, valor_unitario, pedido_id produto_id, produto_descricao, quantidade, valor_unitario, pedido_id, cliente_fone
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8) ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)
ON CONFLICT (pedido_id, produto_id) DO UPDATE SET
cliente_nome = EXCLUDED.cliente_nome,
data_pedido = EXCLUDED.data_pedido,
valor_pedido = EXCLUDED.valor_pedido,
produto_descricao = EXCLUDED.produto_descricao,
quantidade = EXCLUDED.quantidade,
valor_unitario = EXCLUDED.valor_unitario,
cliente_fone = EXCLUDED.cliente_fone,
created_at = CURRENT_TIMESTAMP
`; `;
for (const item of payload) { for (const item of payload) {
// Handle potential missing fields gracefully // Handle potential missing fields gracefully
const orderId = item.id || item.ID_Pedido || (item.json && item.json.body && item.json.body.id) || ''; // If there is no explicit ID, create a composite ID using Name + Date + Value to prevent squashing historical data
const fallbackId = `${item.Nome_Cliente}_${item.Data_Pedido}_${item.Valor_Pedido}`;
const orderId = item.id || item.ID_Pedido || (item.json && item.json.body && item.json.body.id) || fallbackId;
const fone = item.Fone_Cliente || item.fone || item.celular || '';
const values = [ const values = [
item.Nome_Cliente || 'Unknown', item.Nome_Cliente || 'Unknown',
@@ -148,7 +161,8 @@ app.post('/api/data', authenticateAPIKey, async (req, res) => {
item.Descricao_Produto || '', item.Descricao_Produto || '',
parseInt(item.Quantidade) || 0, parseInt(item.Quantidade) || 0,
parseFloat(item.Valor_Unitario) || 0, parseFloat(item.Valor_Unitario) || 0,
String(orderId) String(orderId),
String(fone)
]; ];
await client.query(insertQuery, values); await client.query(insertQuery, values);
} }

View File

@@ -31,13 +31,17 @@ const Clients = () => {
return orderDate >= dateRange.start && orderDate <= dateRange.end; return orderDate >= dateRange.start && orderDate <= dateRange.end;
}); });
const clientMap: Record<string, { totalSpent: number, totalItems: number, uniqueOrders: Set<string>, lastPurchase: number }> = {}; const clientMap: Record<string, { totalSpent: number, totalItems: number, uniqueOrders: Set<string>, lastPurchase: number, phone: string }> = {};
orders.forEach(order => { orders.forEach(order => {
const clientName = order.Nome_Cliente || `Cliente Desconhecido (Pedido ${order.Valor_Pedido})`; const clientName = order.Nome_Cliente || `Cliente Desconhecido (Pedido ${order.Valor_Pedido})`;
if (!clientMap[clientName]) { if (!clientMap[clientName]) {
clientMap[clientName] = { totalSpent: 0, totalItems: 0, uniqueOrders: new Set(), lastPurchase: 0 }; clientMap[clientName] = { totalSpent: 0, totalItems: 0, uniqueOrders: new Set(), lastPurchase: 0, phone: '' };
}
if (order.Fone_Cliente) {
clientMap[clientName].phone = order.Fone_Cliente;
} }
// Calculate total spent based on quantity * unit price // Calculate total spent based on quantity * unit price
@@ -54,6 +58,7 @@ const Clients = () => {
let result = Object.keys(clientMap).map(name => ({ let result = Object.keys(clientMap).map(name => ({
name, name,
phone: clientMap[name].phone,
totalSpent: clientMap[name].totalSpent, totalSpent: clientMap[name].totalSpent,
totalItems: clientMap[name].totalItems, totalItems: clientMap[name].totalItems,
orderCount: clientMap[name].uniqueOrders.size, // Grouped by unique date+value combinations orderCount: clientMap[name].uniqueOrders.size, // Grouped by unique date+value combinations
@@ -129,6 +134,7 @@ const Clients = () => {
onClick={() => { onClick={() => {
const exportData = clientsData.map(client => ({ const exportData = clientsData.map(client => ({
'Nome do Cliente': client.name, 'Nome do Cliente': client.name,
'Telefone/WhatsApp': client.phone || 'N/A',
'Total Gasto (R$)': client.totalSpent.toFixed(2).replace('.', ','), 'Total Gasto (R$)': client.totalSpent.toFixed(2).replace('.', ','),
'Produtos Comprados': client.totalItems, 'Produtos Comprados': client.totalItems,
'Total de Pedidos': client.orderCount, 'Total de Pedidos': client.orderCount,

View File

@@ -8,6 +8,7 @@ export interface OrderData {
Valor_Unitario: number; Valor_Unitario: number;
Recebido_Em?: string; Recebido_Em?: string;
ID_Pedido?: string; ID_Pedido?: string;
Fone_Cliente?: string;
} }
export interface DateRange { export interface DateRange {