Files
chatc2/src/controllers/dashboardController.js
T
AyronSantos 9cf5f00243 ponytail: remove resolucaoSetup, compressVideo, sendEvolutionMessage, sendEvolution; add migration #24
- delete: src/resolucaoSetup.js (migration paralela, consolidado em #24)
- shrink: chatController.sendEvolutionMessage (140 -> 6 linhas, delega ao evolutionService)
- shrink: triageController.sendEvolution (55 -> 10 linhas, delega ao evolutionService)
- delete: compressVideo em chatController (codigo morto, chamado so pelo old sendEvolutionMessage)
- add: migration #24 consolida DDL que estava espalhada em runtime
- fix: configController.remove garantirEstrutura calls (arquivo removido)
- net: -270 linhas, -1 arquivo
2026-07-01 13:24:06 +00:00

132 lines
5.5 KiB
JavaScript

const db = require('../database');
const { isGerente } = require('../middlewares/roles');
const presence = require('../presence');
/**
* Resolve a lista de empresas para a consulta, respeitando a permissão do
* usuário. Se vier empresaId e o usuário tiver acesso, usa só ela; senão usa
* todas as empresas do usuário.
*/
function empresasPermitidas(req) {
const minhas = (req.user && req.user.empresas) || [];
const pedida = parseInt(req.query.empresaId, 10);
if (pedida && minhas.includes(pedida)) return [pedida];
return minhas;
}
class DashboardController {
/**
* Ping de presença — marca o atendente como online.
* POST /api/:alias/dashboard/ping
*/
static async ping(req, res) {
const { alias } = req.params;
if (req.user && req.user.id != null) presence.registrar(alias, req.user.id);
res.json({ success: true });
}
/**
* Estatísticas do dashboard (apenas gerente).
* GET /api/:alias/dashboard/stats?empresaId=
*/
static async stats(req, res) {
try {
if (!(await isGerente(req))) {
return res.status(403).json({ success: false, error: 'Apenas gerentes podem visualizar o dashboard.' });
}
const { alias } = req.params;
const empresas = empresasPermitidas(req);
if (empresas.length === 0) {
return res.json({ success: true, data: {
conversas: { abertas: 0, naoAtendidas: 0, naoAtribuidas: 0, pendentes: 0 },
atendentes: { disponiveis: 0, desconectados: 0, lista: [] },
trafego: [],
} });
}
const ph = empresas.map(() => '?').join(',');
// ===== Conversas =====
// Abertas: têm atendente E equipe
const abertas = await db.query(alias,
`SELECT COUNT(*) AS CT FROM CHATC2_CONVERSAS
WHERE CON_EMPRESA_ID IN (${ph}) AND CON_STATUS IN ('A','E') AND CON_SITUACAO = 'A'
AND CON_USUARIO_ID IS NOT NULL AND CON_EQUIPE_ID IS NOT NULL`, empresas);
// Não atendidas: têm atendente+equipe e a última mensagem foi do cliente ('C')
const naoAtendidas = await db.query(alias,
`SELECT COUNT(*) AS CT FROM CHATC2_CONVERSAS c
WHERE c.CON_EMPRESA_ID IN (${ph}) AND c.CON_STATUS IN ('A','E') AND c.CON_SITUACAO = 'A'
AND c.CON_USUARIO_ID IS NOT NULL AND c.CON_EQUIPE_ID IS NOT NULL
AND (SELECT m.CME_REMETENTE FROM CHATC2_CONVERSAS_MENSAGENS m
WHERE m.CME_CONVERSA_ID = c.CON_CODIGO_ID
ORDER BY m.CME_DT_ENVIO DESC FETCH FIRST 1 ROWS ONLY) = 'C'`, empresas);
// Não atribuídas: sem atendente e sem equipe
const naoAtribuidas = await db.query(alias,
`SELECT COUNT(*) AS CT FROM CHATC2_CONVERSAS
WHERE CON_EMPRESA_ID IN (${ph}) AND CON_STATUS IN ('A','E') AND CON_SITUACAO = 'A'
AND CON_USUARIO_ID IS NULL AND CON_EQUIPE_ID IS NULL`, empresas);
// Pendentes: têm apenas equipe (sem atendente)
const pendentes = await db.query(alias,
`SELECT COUNT(*) AS CT FROM CHATC2_CONVERSAS
WHERE CON_EMPRESA_ID IN (${ph}) AND CON_STATUS IN ('A','E') AND CON_SITUACAO = 'A'
AND CON_USUARIO_ID IS NULL AND CON_EQUIPE_ID IS NOT NULL`, empresas);
// Agendadas para hoje (status P = Pendente)
const agendadas = await db.query(alias,
`SELECT COUNT(*) AS CT FROM CHATC2_CONVERSAS_AGENDAMENTOS
WHERE CAG_EMPRESA_ID IN (${ph}) AND CAG_DATA = CURRENT_DATE AND CAG_STATUS = 'P'`,
empresas);
// ===== Atendentes =====
const usuarios = await db.query(alias,
`SELECT DISTINCT u.USU_CODIGO_ID, u.USU_NOME
FROM USUARIOS u INNER JOIN USUARIOS_EMPRESA ue ON u.USU_CODIGO_ID = ue.USE_USUARIO_ID
WHERE ue.USE_EMPRESA_ID IN (${ph}) AND u.USU_STATUS = 'A' AND COALESCE(u.USU_ACESSO_WEB,0) = 1
ORDER BY u.USU_NOME`, empresas);
const online = presence.idsOnline(alias);
let disponiveis = 0, desconectados = 0;
const lista = usuarios.map(function (u) {
const on = online.has(Number(u.USU_CODIGO_ID));
if (on) disponiveis++; else desconectados++;
return { id: u.USU_CODIGO_ID, nome: (u.USU_NOME || '').trim(), online: on };
});
// ===== Tráfego das conversas (últimos 365 dias, por dia) =====
const trafegoRows = await db.query(alias,
`SELECT CAST(COALESCE(CON_DT_INICIO, CON_PRIMEIRA_MSG, CON_DT_ULTIMA_MSG) AS DATE) AS DIA, COUNT(*) AS CT
FROM CHATC2_CONVERSAS
WHERE CON_EMPRESA_ID IN (${ph})
AND COALESCE(CON_DT_INICIO, CON_PRIMEIRA_MSG, CON_DT_ULTIMA_MSG) >= CURRENT_DATE - 365
GROUP BY CAST(COALESCE(CON_DT_INICIO, CON_PRIMEIRA_MSG, CON_DT_ULTIMA_MSG) AS DATE)`,
empresas);
const fmtDia = (d) => {
if (!d) return null;
if (typeof d === 'string') return d.split('T')[0];
if (d instanceof Date) return d.toISOString().split('T')[0];
return String(d);
};
const trafego = trafegoRows
.map(function (r) { return { dia: fmtDia(r.DIA), total: Number(r.CT) || 0 }; })
.filter(function (r) { return r.dia; });
res.json({ success: true, data: {
conversas: {
abertas: Number(abertas[0]?.CT) || 0,
naoAtendidas: Number(naoAtendidas[0]?.CT) || 0,
naoAtribuidas: Number(naoAtribuidas[0]?.CT) || 0,
pendentes: Number(pendentes[0]?.CT) || 0,
agendadas: Number(agendadas[0]?.CT) || 0,
},
atendentes: { disponiveis, desconectados, lista },
trafego,
} });
} catch (err) { res.status(500).json({ success: false, error: err.message }); }
}
}
module.exports = DashboardController;