9cf5f00243
- 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
132 lines
5.5 KiB
JavaScript
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;
|