const { pool } = require('../db'); const PRODUCT_NAME_SQL = "NULLIF(TRIM(split_part(COALESCE(produto_descricao, 'Unknown'), ' TAMANHO', 1)), '')"; const normalizeDateParam = (value) => { if (!value) return null; const match = String(value).trim().match(/^(\d{4})-(\d{2})-(\d{2})$/); if (!match) return null; const [, yearValue, monthValue, dayValue] = match; const year = Number(yearValue); const month = Number(monthValue); const day = Number(dayValue); const date = new Date(Date.UTC(year, month - 1, day)); if ( date.getUTCFullYear() !== year || date.getUTCMonth() !== month - 1 || date.getUTCDate() !== day ) { return null; } return `${yearValue}-${monthValue}-${dayValue}`; }; const buildDateFilter = ({ start, end } = {}) => { const params = []; const filters = ['data_pedido_date IS NOT NULL']; const normalizedStart = normalizeDateParam(start); const normalizedEnd = normalizeDateParam(end); if (normalizedStart) { params.push(normalizedStart); filters.push(`data_pedido_date >= $${params.length}::date`); } if (normalizedEnd) { params.push(normalizedEnd); filters.push(`data_pedido_date <= $${params.length}::date`); } return { params, whereClause: `WHERE ${filters.join(' AND ')}` }; }; const toNumber = (value) => Number(value || 0); const getDashboardAnalytics = async (range = {}) => { const { params, whereClause } = buildDateFilter(range); const [totalsResult, salesResult, revenueResult] = await Promise.all([ pool.query(` SELECT COALESCE(SUM(quantidade * valor_unitario), 0) as total_revenue, COALESCE(SUM(quantidade), 0) as total_items, COUNT(*)::int as order_line_count FROM orders ${whereClause}; `, params), pool.query(` SELECT COALESCE(${PRODUCT_NAME_SQL}, 'Unknown') as name, MAX(produto_id) as id, COALESCE(SUM(quantidade), 0) as value FROM orders ${whereClause} GROUP BY name ORDER BY value DESC LIMIT 10; `, params), pool.query(` SELECT COALESCE(${PRODUCT_NAME_SQL}, 'Unknown') as name, MAX(produto_id) as id, COALESCE(SUM(quantidade * valor_unitario), 0) as value FROM orders ${whereClause} GROUP BY name ORDER BY value DESC LIMIT 10; `, params) ]); const totals = totalsResult.rows[0] || {}; const orderLineCount = toNumber(totals.order_line_count); const totalRevenue = toNumber(totals.total_revenue); return { range: { start: normalizeDateParam(range.start), end: normalizeDateParam(range.end) }, totalRevenue, totalOrders: toNumber(totals.total_items), orderLineCount, averageOrderValue: orderLineCount ? totalRevenue / orderLineCount : 0, salesByProduct: salesResult.rows.map(row => ({ name: row.name, id: row.id, value: toNumber(row.value) })), revenueByProduct: revenueResult.rows.map(row => ({ name: row.name, id: row.id, value: toNumber(row.value) })) }; }; const getProductAnalytics = async (range = {}) => { const { params, whereClause } = buildDateFilter(range); const result = await pool.query(` SELECT COALESCE(${PRODUCT_NAME_SQL}, 'Unknown') as name, MAX(produto_id) as id, COALESCE(SUM(quantidade), 0) as quantity_sold, COALESCE(SUM(quantidade * valor_unitario), 0) as revenue, COUNT(*)::int as order_line_count, MIN(data_pedido_date) as first_sale_date, MAX(data_pedido_date) as last_sale_date FROM orders ${whereClause} GROUP BY name ORDER BY revenue DESC, quantity_sold DESC LIMIT 500; `, params); return result.rows.map(row => ({ name: row.name, id: row.id, quantitySold: toNumber(row.quantity_sold), revenue: toNumber(row.revenue), orderLineCount: toNumber(row.order_line_count), firstSaleDate: row.first_sale_date, lastSaleDate: row.last_sale_date })); }; const getClientAnalytics = async (range = {}) => { const { params, whereClause } = buildDateFilter(range); const result = await pool.query(` SELECT MAX(cliente_nome) as name, cliente_fone as phone, COALESCE(SUM(quantidade), 0) as quantity_purchased, COALESCE(SUM(quantidade * valor_unitario), 0) as total_spent, COUNT(*)::int as order_line_count, MAX(data_pedido_date) as last_purchase_date FROM orders ${whereClause} AND cliente_fone IS NOT NULL AND cliente_fone != '' GROUP BY cliente_fone ORDER BY total_spent DESC LIMIT 500; `, params); return result.rows.map(row => ({ name: row.name, phone: row.phone, quantityPurchased: toNumber(row.quantity_purchased), totalSpent: toNumber(row.total_spent), orderLineCount: toNumber(row.order_line_count), lastPurchaseDate: row.last_purchase_date })); }; module.exports = { buildDateFilter, getClientAnalytics, getDashboardAnalytics, getProductAnalytics, normalizeDateParam };