diff --git a/server.js b/server.js index 9cb0464..33a302a 100644 --- a/server.js +++ b/server.js @@ -480,18 +480,8 @@ app.get('/admin/api/cliente/:id/data', requirePermission('cliente'), async (req, fetchClientData(clienteId, start, end), fetchMerchantData(merchant.empresa_id, start, end) ]); - // Add checkout KPIs + // Pass checkout KPIs separately (no merge into hero totals) data.kpis.checkout = mData.kpis; - // Merge totals - data.kpis.total.qtd += mData.kpis.qtd; - data.kpis.total.vol_usd += mData.kpis.vol_usd; - data.kpis.total.spread_revenue += mData.kpis.revenue; - const totalQtd = data.kpis.total.qtd; - data.kpis.total.ticket_medio = totalQtd > 0 ? Math.round(data.kpis.total.vol_usd / totalQtd) : 0; - // Merge comparison - data.comparison.prev_qtd += mData.comparison.prev_qtd; - data.comparison.prev_vol_usd += mData.comparison.prev_vol_usd; - data.comparison.prev_spread += mData.comparison.prev_revenue; // Merchant-specific data data.merchant = { monthly: mData.monthly, diff --git a/src/admin-cliente.js b/src/admin-cliente.js index 5bffe97..96c7ace 100644 --- a/src/admin-cliente.js +++ b/src/admin-cliente.js @@ -460,6 +460,14 @@ ${buildHeader({ role: role, userName: user.nome, activePage: 'cliente', permissi + +
+
+ 💱 + CambioPay (Transfers) +
+
+
Volume USD
@@ -967,6 +975,19 @@ function renderProfile(p) { document.querySelectorAll('.checkout-section').forEach(function(el) { el.classList.toggle('visible', isMerchant); }); + // Relabel hero cards for merchants to clarify CambioPay-only + var heroLabels = document.querySelectorAll('#heroGrid .hero-label'); + heroLabels.forEach(function(lbl) { + if (isMerchant) { + if (lbl.textContent === 'Volume USD') lbl.textContent = 'CambioPay Volume'; + else if (lbl.textContent === 'Transacoes') lbl.textContent = 'CambioPay Ops'; + else if (lbl.textContent === 'Receita USD') lbl.textContent = 'Receita CambioPay'; + } else { + if (lbl.textContent === 'CambioPay Volume') lbl.textContent = 'Volume USD'; + else if (lbl.textContent === 'CambioPay Ops') lbl.textContent = 'Transacoes'; + else if (lbl.textContent === 'Receita CambioPay') lbl.textContent = 'Receita USD'; + } + }); } function renderChurnRisk(churn) { diff --git a/src/queries/bi.queries.js b/src/queries/bi.queries.js index 87ddd69..e157223 100644 --- a/src/queries/bi.queries.js +++ b/src/queries/bi.queries.js @@ -15,29 +15,40 @@ async function fetchBIData(dataInicio, dataFim, getAgenteName = null) { [trendBrlUsd], [trendUsdBrl], [trendUsdUsd], [topClients], [retention], [clientsAtRisk], [agentRanking] ] = await Promise.all([ - // 1. BRL→USD KPIs + // 1. BRL→USD KPIs (real revenue = P&L formula) pool.execute(` SELECT COUNT(*) as qtd, - ROUND(COALESCE(SUM(amount_usd), 0), 2) as vol_usd, - ROUND(COALESCE(SUM(amount_brl), 0), 2) as vol_brl, - ROUND(COALESCE(SUM((exchange_rate - ptax) / exchange_rate * amount_usd), 0), 2) as spread_revenue, - ROUND(COALESCE(AVG((exchange_rate - ptax) / exchange_rate * 100), 0), 2) as avg_spread_pct, - COUNT(DISTINCT id_conta) as clientes - FROM br_transaction_to_usa - WHERE DATE(created_at) >= ? AND DATE(created_at) <= ? + ROUND(COALESCE(SUM(t.amount_usd), 0), 2) as vol_usd, + ROUND(COALESCE(SUM(t.amount_brl), 0), 2) as vol_brl, + ROUND(COALESCE(SUM( + (ROUND((t.amount_brl - IF(pm.provider IN ('ouribank','bs2'), 0, t.fee)) / t.ptax, 2) - COALESCE(t.pfee, 0)) + - (t.amount_usd + COALESCE(t.bonus_valor, 0) - COALESCE(t.taxa_cr, 0)) + ), 0), 2) as spread_revenue, + ROUND(COALESCE(AVG((t.exchange_rate - t.ptax) / t.exchange_rate * 100), 0), 2) as avg_spread_pct, + COUNT(DISTINCT t.id_conta) as clientes + FROM br_transaction_to_usa t + JOIN br_payment_methods pm ON t.payment_method_id = pm.id + WHERE DATE(t.created_at) >= ? AND DATE(t.created_at) <= ? + AND pm.provider IN ('dlocal','bexs','braza','bs2','ouribank','msb') + AND t.ptax IS NOT NULL AND t.ptax > 0 + AND (t.status IN ('boleto_pago','finalizado') OR t.date_sent_usa <> '0000-00-00 00:00:00') `, [dataInicio, dataFim]), - // 2. USD→BRL KPIs + // 2. USD→BRL KPIs (real revenue: spread + fee for non-balance, fee only for balance) pool.execute(` SELECT COUNT(*) as qtd, - ROUND(COALESCE(SUM(valor), 0), 2) as vol_usd, - ROUND(COALESCE(SUM(valor_sol), 0), 2) as vol_brl, - ROUND(COALESCE(SUM((ptax - cotacao) / ptax * valor), 0), 2) as spread_revenue, - ROUND(COALESCE(AVG(CASE WHEN cotacao > 0 THEN (ptax - cotacao) / ptax * 100 ELSE 0 END), 0), 2) as avg_spread_pct, - COUNT(DISTINCT id_conta) as clientes - FROM pagamento_br - WHERE DATE(created_at) >= ? AND DATE(created_at) <= ? - AND cotacao IS NOT NULL AND cotacao > 0 - AND (pgto IS NULL OR pgto != 'balance') + ROUND(COALESCE(SUM(pb.valor), 0), 2) as vol_usd, + ROUND(COALESCE(SUM(pb.valor_sol), 0), 2) as vol_brl, + ROUND(COALESCE(SUM( + CASE WHEN pb.tipo_envio = 'balance' THEN COALESCE(pb.fee, 0) + ELSE COALESCE(CASE WHEN pb.ptax IS NOT NULL AND pb.ptax > 0 THEN ((pb.ptax - pb.cotacao) * pb.valor) / pb.ptax ELSE 0 END, 0) + COALESCE(pb.fee, 0) + END + ), 0), 2) as spread_revenue, + ROUND(COALESCE(AVG(CASE WHEN pb.cotacao > 0 AND pb.tipo_envio != 'balance' THEN (pb.ptax - pb.cotacao) / pb.ptax * 100 ELSE 0 END), 0), 2) as avg_spread_pct, + COUNT(DISTINCT pb.id_conta) as clientes + FROM pagamento_br pb + WHERE pb.valor > 0 AND pb.data_cp IS NOT NULL AND pb.data_cp <> '0000-00-00' + AND DATE(CASE WHEN pb.tipo_envio = 'balance' THEN pb.data_cp ELSE pb.created_at END) >= ? + AND DATE(CASE WHEN pb.tipo_envio = 'balance' THEN pb.data_cp ELSE pb.created_at END) <= ? `, [dataInicio, dataFim]), // 3. USD→USD KPIs pool.execute(` @@ -56,17 +67,31 @@ async function fetchBIData(dataInicio, dataFim, getAgenteName = null) { SELECT id_conta FROM pagamento_br WHERE DATE(created_at) >= ? AND DATE(created_at) <= ? ) all_clients `, [dataInicio, dataFim, dataInicio, dataFim]), - // 5. Previous period + // 5. Previous period (real revenue formulas) pool.execute(` - SELECT COUNT(*) as qtd, ROUND(COALESCE(SUM(amount_usd),0),2) as vol_usd, - ROUND(COALESCE(SUM((exchange_rate - ptax) / exchange_rate * amount_usd),0),2) as spread_revenue - FROM br_transaction_to_usa WHERE DATE(created_at) >= ? AND DATE(created_at) <= ? + SELECT COUNT(*) as qtd, ROUND(COALESCE(SUM(t.amount_usd),0),2) as vol_usd, + ROUND(COALESCE(SUM( + (ROUND((t.amount_brl - IF(pm.provider IN ('ouribank','bs2'), 0, t.fee)) / t.ptax, 2) - COALESCE(t.pfee, 0)) + - (t.amount_usd + COALESCE(t.bonus_valor, 0) - COALESCE(t.taxa_cr, 0)) + ),0),2) as spread_revenue + FROM br_transaction_to_usa t + JOIN br_payment_methods pm ON t.payment_method_id = pm.id + WHERE DATE(t.created_at) >= ? AND DATE(t.created_at) <= ? + AND pm.provider IN ('dlocal','bexs','braza','bs2','ouribank','msb') + AND t.ptax IS NOT NULL AND t.ptax > 0 + AND (t.status IN ('boleto_pago','finalizado') OR t.date_sent_usa <> '0000-00-00 00:00:00') `, [prevStartStr, prevEndStr]), pool.execute(` - SELECT COUNT(*) as qtd, ROUND(COALESCE(SUM(valor),0),2) as vol_usd, - ROUND(COALESCE(SUM((ptax - cotacao) / ptax * valor),0),2) as spread_revenue - FROM pagamento_br WHERE DATE(created_at) >= ? AND DATE(created_at) <= ? - AND cotacao IS NOT NULL AND cotacao > 0 AND (pgto IS NULL OR pgto != 'balance') + SELECT COUNT(*) as qtd, ROUND(COALESCE(SUM(pb.valor),0),2) as vol_usd, + ROUND(COALESCE(SUM( + CASE WHEN pb.tipo_envio = 'balance' THEN COALESCE(pb.fee, 0) + ELSE COALESCE(CASE WHEN pb.ptax IS NOT NULL AND pb.ptax > 0 THEN ((pb.ptax - pb.cotacao) * pb.valor) / pb.ptax ELSE 0 END, 0) + COALESCE(pb.fee, 0) + END + ),0),2) as spread_revenue + FROM pagamento_br pb + WHERE pb.valor > 0 AND pb.data_cp IS NOT NULL AND pb.data_cp <> '0000-00-00' + AND DATE(CASE WHEN pb.tipo_envio = 'balance' THEN pb.data_cp ELSE pb.created_at END) >= ? + AND DATE(CASE WHEN pb.tipo_envio = 'balance' THEN pb.data_cp ELSE pb.created_at END) <= ? `, [prevStartStr, prevEndStr]), pool.execute(` SELECT COUNT(*) as qtd, ROUND(COALESCE(SUM(valor),0),2) as vol_usd @@ -133,22 +158,35 @@ async function fetchBIData(dataInicio, dataFim, getAgenteName = null) { FROM pagamento_br p INNER JOIN conta c ON c.id_conta = p.id_conta GROUP BY c.nome ) combined GROUP BY nome HAVING MAX(last_op) < CURDATE() ORDER BY total_usd DESC LIMIT 20 `), - // 12. Agent ranking + // 12. Agent ranking (real revenue formulas) pool.execute(` SELECT agente_id, SUM(vol) as total_usd, SUM(qtd) as total_qtd, ROUND(SUM(spread_rev), 2) as total_spread, COUNT(DISTINCT client_id) as clientes FROM ( SELECT ac.agente_id, t.id_conta as client_id, SUM(t.amount_usd) as vol, COUNT(*) as qtd, - SUM((t.exchange_rate - t.ptax) / t.exchange_rate * t.amount_usd) as spread_rev - FROM br_transaction_to_usa t INNER JOIN ag_contas ac ON ac.conta_id = t.id_conta + SUM( + (ROUND((t.amount_brl - IF(pm.provider IN ('ouribank','bs2'), 0, t.fee)) / t.ptax, 2) - COALESCE(t.pfee, 0)) + - (t.amount_usd + COALESCE(t.bonus_valor, 0) - COALESCE(t.taxa_cr, 0)) + ) as spread_rev + FROM br_transaction_to_usa t + INNER JOIN ag_contas ac ON ac.conta_id = t.id_conta + INNER JOIN br_payment_methods pm ON t.payment_method_id = pm.id WHERE DATE(t.created_at) >= ? AND DATE(t.created_at) <= ? + AND pm.provider IN ('dlocal','bexs','braza','bs2','ouribank','msb') + AND t.ptax IS NOT NULL AND t.ptax > 0 + AND (t.status IN ('boleto_pago','finalizado') OR t.date_sent_usa <> '0000-00-00 00:00:00') GROUP BY ac.agente_id, t.id_conta UNION ALL SELECT ac.agente_id, p.id_conta as client_id, SUM(p.valor) as vol, COUNT(*) as qtd, - SUM((p.ptax - p.cotacao) / p.ptax * p.valor) as spread_rev + SUM( + CASE WHEN p.tipo_envio = 'balance' THEN COALESCE(p.fee, 0) + ELSE COALESCE(CASE WHEN p.ptax IS NOT NULL AND p.ptax > 0 THEN ((p.ptax - p.cotacao) * p.valor) / p.ptax ELSE 0 END, 0) + COALESCE(p.fee, 0) + END + ) as spread_rev FROM pagamento_br p INNER JOIN ag_contas ac ON ac.conta_id = p.id_conta - WHERE DATE(p.created_at) >= ? AND DATE(p.created_at) <= ? - AND p.cotacao IS NOT NULL AND p.cotacao > 0 AND (p.pgto IS NULL OR p.pgto != 'balance') + WHERE DATE(CASE WHEN p.tipo_envio = 'balance' THEN p.data_cp ELSE p.created_at END) >= ? + AND DATE(CASE WHEN p.tipo_envio = 'balance' THEN p.data_cp ELSE p.created_at END) <= ? + AND p.valor > 0 AND p.data_cp IS NOT NULL AND p.data_cp <> '0000-00-00' GROUP BY ac.agente_id, p.id_conta ) combined GROUP BY agente_id ORDER BY total_usd DESC LIMIT 10 `, [dataInicio, dataFim, dataInicio, dataFim]) diff --git a/src/queries/client.queries.js b/src/queries/client.queries.js index 1d545a9..21f38cc 100644 --- a/src/queries/client.queries.js +++ b/src/queries/client.queries.js @@ -21,7 +21,9 @@ async function fetchTopClients() { COUNT(*) AS cnt, COUNT(DISTINCT DATE_FORMAT(created_at, '%Y-%m')) AS months_active, MAX(created_at) AS last_op - FROM br_transaction_to_usa GROUP BY id_conta + FROM br_transaction_to_usa + WHERE (status IN ('boleto_pago','finalizado') OR date_sent_usa <> '0000-00-00 00:00:00') + GROUP BY id_conta ) t1 ON t1.id_conta = c.id_conta LEFT JOIN ( SELECT id_conta, @@ -92,6 +94,7 @@ async function fetchClientProfile(clienteId) { ROUND(COALESCE(SUM((exchange_rate - ptax) / exchange_rate * amount_usd),0),2) as spread_revenue, MIN(created_at) as first_op, MAX(created_at) as last_op FROM br_transaction_to_usa WHERE id_conta = ? + AND (status IN ('boleto_pago','finalizado') OR date_sent_usa <> '0000-00-00 00:00:00') `, [clienteId]); const [usd] = await conn.execute(` @@ -122,6 +125,7 @@ async function fetchClientProfile(clienteId) { const [monthsRows] = await conn.execute(` SELECT COUNT(DISTINCT mes) as months_active FROM ( SELECT DATE_FORMAT(created_at, '%Y-%m') as mes FROM br_transaction_to_usa WHERE id_conta = ? + AND (status IN ('boleto_pago','finalizado') OR date_sent_usa <> '0000-00-00 00:00:00') UNION SELECT DATE_FORMAT(created_at, '%Y-%m') as mes FROM pagamento_br WHERE id_conta = ? AND cotacao IS NOT NULL AND cotacao > 0 AND (pgto IS NULL OR pgto != 'balance') @@ -165,6 +169,7 @@ async function fetchClientData(clienteId, dataInicio, dataFim) { ROUND(COALESCE(SUM((exchange_rate - ptax) / exchange_rate * amount_usd),0),2) as spread_revenue, ROUND(COALESCE(AVG((exchange_rate - ptax) / exchange_rate * 100),0),2) as avg_spread_pct FROM br_transaction_to_usa WHERE id_conta = ? AND DATE(created_at) >= ? AND DATE(created_at) <= ? + AND (status IN ('boleto_pago','finalizado') OR date_sent_usa <> '0000-00-00 00:00:00') `, [clienteId, dataInicio, dataFim]); // KPIs USD->BRL @@ -182,6 +187,7 @@ async function fetchClientData(clienteId, dataInicio, dataFim) { SELECT COUNT(*) as qtd, ROUND(COALESCE(SUM(amount_usd),0),2) as vol_usd, ROUND(COALESCE(SUM((exchange_rate - ptax) / exchange_rate * amount_usd),0),2) as spread_revenue FROM br_transaction_to_usa WHERE id_conta = ? AND DATE(created_at) >= ? AND DATE(created_at) <= ? + AND (status IN ('boleto_pago','finalizado') OR date_sent_usa <> '0000-00-00 00:00:00') `, [clienteId, prevStartStr, prevEndStr]); const [prevUsd] = await conn.execute(` SELECT COUNT(*) as qtd, ROUND(COALESCE(SUM(valor),0),2) as vol_usd, @@ -196,6 +202,7 @@ async function fetchClientData(clienteId, dataInicio, dataFim) { ROUND(SUM(amount_usd),2) as vol_usd, ROUND(AVG((exchange_rate - ptax) / exchange_rate * 100),2) as avg_spread FROM br_transaction_to_usa WHERE id_conta = ? AND DATE(created_at) >= ? AND DATE(created_at) <= ? + AND (status IN ('boleto_pago','finalizado') OR date_sent_usa <> '0000-00-00 00:00:00') GROUP BY DATE(created_at) ORDER BY dia `, [clienteId, dataInicio, dataFim]); @@ -220,6 +227,7 @@ async function fetchClientData(clienteId, dataInicio, dataFim) { FROM br_transaction_to_usa t LEFT JOIN br_payment_methods pm ON t.payment_method_id = pm.id WHERE t.id_conta = ? AND DATE(t.created_at) >= ? AND DATE(t.created_at) <= ? + AND (t.status IN ('boleto_pago','finalizado') OR t.date_sent_usa <> '0000-00-00 00:00:00') ORDER BY t.created_at DESC `, [clienteId, dataInicio, dataFim]); @@ -241,6 +249,7 @@ async function fetchClientData(clienteId, dataInicio, dataFim) { const [dowBrl] = await conn.execute(` SELECT DAYOFWEEK(created_at) as dow, COUNT(*) as qtd, ROUND(SUM(amount_usd),2) as vol_usd FROM br_transaction_to_usa WHERE id_conta = ? AND DATE(created_at) >= ? AND DATE(created_at) <= ? + AND (status IN ('boleto_pago','finalizado') OR date_sent_usa <> '0000-00-00 00:00:00') GROUP BY DAYOFWEEK(created_at) `, [clienteId, dataInicio, dataFim]); const [dowUsd] = await conn.execute(` @@ -256,6 +265,7 @@ async function fetchClientData(clienteId, dataInicio, dataFim) { FROM br_transaction_to_usa t LEFT JOIN br_payment_methods pm ON t.payment_method_id = pm.id WHERE t.id_conta = ? AND DATE(t.created_at) >= ? AND DATE(t.created_at) <= ? + AND (t.status IN ('boleto_pago','finalizado') OR t.date_sent_usa <> '0000-00-00 00:00:00') GROUP BY pm.provider `, [clienteId, dataInicio, dataFim]); const [provUsd] = await conn.execute(` @@ -272,6 +282,7 @@ async function fetchClientData(clienteId, dataInicio, dataFim) { ROUND(SUM(amount_usd),2) as vol_usd, ROUND(SUM((exchange_rate - ptax) / exchange_rate * amount_usd),2) as spread_revenue FROM br_transaction_to_usa WHERE id_conta = ? AND DATE(created_at) >= ? AND DATE(created_at) <= ? + AND (status IN ('boleto_pago','finalizado') OR date_sent_usa <> '0000-00-00 00:00:00') GROUP BY DATE_FORMAT(created_at, '%Y-%m') ORDER BY mes `, [clienteId, dataInicio, dataFim]); const [monthlyUsd] = await conn.execute(` @@ -378,6 +389,7 @@ async function fetchMerchantProfile(clienteId) { FROM br_cb_cobranca cb INNER JOIN br_transaction_to_usa t ON t.cobranca_id = cb.id WHERE cb.empresa_id = ? + AND (t.status IN ('boleto_pago','finalizado') OR t.date_sent_usa <> '0000-00-00 00:00:00') `, [empresaId]); const [rev] = await conn.execute(` @@ -433,6 +445,7 @@ async function fetchMerchantData(empresaId, dataInicio, dataFim) { FROM br_cb_cobranca cb INNER JOIN br_transaction_to_usa t ON t.cobranca_id = cb.id WHERE cb.empresa_id = ? AND DATE(t.created_at) >= ? AND DATE(t.created_at) <= ? + AND (t.status IN ('boleto_pago','finalizado') OR t.date_sent_usa <> '0000-00-00 00:00:00') `, [empresaId, dataInicio, dataFim]); const [rev] = await conn.execute(` @@ -458,6 +471,7 @@ async function fetchMerchantData(empresaId, dataInicio, dataFim) { FROM br_cb_cobranca cb INNER JOIN br_transaction_to_usa t ON t.cobranca_id = cb.id WHERE cb.empresa_id = ? AND DATE(t.created_at) >= ? AND DATE(t.created_at) <= ? + AND (t.status IN ('boleto_pago','finalizado') OR t.date_sent_usa <> '0000-00-00 00:00:00') `, [empresaId, prevStartStr, prevEndStr]); const [prevRev] = await conn.execute(` @@ -486,6 +500,7 @@ async function fetchMerchantData(empresaId, dataInicio, dataFim) { FROM br_cb_cobranca cb INNER JOIN br_transaction_to_usa t ON t.cobranca_id = cb.id WHERE cb.empresa_id = ? AND DATE(t.created_at) >= ? AND DATE(t.created_at) <= ? + AND (t.status IN ('boleto_pago','finalizado') OR t.date_sent_usa <> '0000-00-00 00:00:00') GROUP BY DATE_FORMAT(t.created_at, '%Y-%m') ORDER BY mes `, [empresaId, dataInicio, dataFim]); @@ -495,6 +510,7 @@ async function fetchMerchantData(empresaId, dataInicio, dataFim) { INNER JOIN br_transaction_to_usa t ON t.cobranca_id = cb.id INNER JOIN conta c ON c.id_conta = t.id_conta WHERE cb.empresa_id = ? AND DATE(t.created_at) >= ? AND DATE(t.created_at) <= ? + AND (t.status IN ('boleto_pago','finalizado') OR t.date_sent_usa <> '0000-00-00 00:00:00') GROUP BY t.id_conta, c.nome ORDER BY vol_usd DESC LIMIT 10 `, [empresaId, dataInicio, dataFim]);