alterações de melhorias
This commit is contained in:
@@ -181,70 +181,93 @@ class ChatController {
|
||||
const result = await db.query(alias, `
|
||||
SELECT c.*,
|
||||
(SELECT CME_TEXTO FROM CHATC2_CONVERSAS_MENSAGENS WHERE CME_CONVERSA_ID = c.CON_CODIGO_ID ORDER BY CME_DT_ENVIO DESC FETCH FIRST 1 ROWS ONLY) AS ULTIMA_MSG,
|
||||
(SELECT CME_TIPO FROM CHATC2_CONVERSAS_MENSAGENS WHERE CME_CONVERSA_ID = c.CON_CODIGO_ID ORDER BY CME_DT_ENVIO DESC FETCH FIRST 1 ROWS ONLY) AS ULTIMA_TIPO,
|
||||
(SELECT CME_DT_ENVIO FROM CHATC2_CONVERSAS_MENSAGENS WHERE CME_CONVERSA_ID = c.CON_CODIGO_ID ORDER BY CME_DT_ENVIO DESC FETCH FIRST 1 ROWS ONLY) AS DT_ULTIMA_MSG
|
||||
FROM CHATC2_CONVERSAS c
|
||||
WHERE c.CON_EMPRESA_ID = ? AND c.CON_STATUS IN (${statusPh}) AND c.CON_SITUACAO = 'A' ${extraFilter}
|
||||
ORDER BY c.CON_DT_ULTIMA_MSG DESC
|
||||
`, params);
|
||||
|
||||
const conversations = await Promise.all(result.map(async (row) => {
|
||||
// Busca etiquetas
|
||||
const labels = await db.query(alias, `
|
||||
SELECT e.ETI_CODIGO_ID, e.ETI_NOME, e.ETI_COR
|
||||
FROM CHATC2_ETIQUETAS e INNER JOIN CHATC2_CONVERSAS_ETIQUETAS ce ON e.ETI_CODIGO_ID = ce.CET_ETIQUETA_ID
|
||||
WHERE ce.CET_CONVERSA_ID = ? AND e.ETI_SITUACAO = 'A'
|
||||
`, [row.CON_CODIGO_ID]);
|
||||
// --- Busca em lote: etiquetas de todas as conversas (evita N+1 queries) ---
|
||||
const conversaIds = result.map(r => r.CON_CODIGO_ID);
|
||||
const labelsMap = {};
|
||||
for (const cid of conversaIds) labelsMap[cid] = [];
|
||||
|
||||
// Busca foto do cliente se configurado
|
||||
let foto = null;
|
||||
if (row.CON_CLIENTE_ID) {
|
||||
const config = await db.query(alias,
|
||||
'SELECT CFE_FOTO_CELULAR FROM CHATC2_CONFIGURACOES_EMPRESA WHERE CFE_EMPRESA_ID = ?', [empresaId]);
|
||||
if (config.length > 0 && config[0].CFE_FOTO_CELULAR === 'S') {
|
||||
const cli = await db.query(alias,
|
||||
'SELECT CLI_FOTO, CLI_NOME_FOTO FROM CLIENTES WHERE CLI_CODIGO_ID = ?', [row.CON_CLIENTE_ID]);
|
||||
if (cli.length > 0) {
|
||||
var rawFoto = cli[0].CLI_FOTO;
|
||||
if (rawFoto) {
|
||||
if (Buffer.isBuffer(rawFoto)) {
|
||||
var str = rawFoto.toString('utf8');
|
||||
if (/^[A-Za-z0-9+/]+=*$/.test(str.slice(0, 30))) {
|
||||
foto = str;
|
||||
} else {
|
||||
var prefixo = 'data:image/jpeg;base64,';
|
||||
if (rawFoto[0] === 0x89 && rawFoto[1] === 0x50) prefixo = 'data:image/png;base64,';
|
||||
else if (rawFoto[0] === 0x47 && rawFoto[1] === 0x49) prefixo = 'data:image/gif;base64,';
|
||||
else if (rawFoto[0] === 0x52 && rawFoto[1] === 0x49) prefixo = 'data:image/webp;base64,';
|
||||
foto = prefixo + rawFoto.toString('base64');
|
||||
}
|
||||
if (conversaIds.length > 0) {
|
||||
const idsPlaceholders = conversaIds.map(() => '?').join(',');
|
||||
const todasEtiquetas = await db.query(alias, `
|
||||
SELECT ce.CET_CONVERSA_ID, e.ETI_CODIGO_ID, e.ETI_NOME, e.ETI_COR
|
||||
FROM CHATC2_ETIQUETAS e
|
||||
INNER JOIN CHATC2_CONVERSAS_ETIQUETAS ce ON e.ETI_CODIGO_ID = ce.CET_ETIQUETA_ID
|
||||
WHERE ce.CET_CONVERSA_ID IN (${idsPlaceholders}) AND e.ETI_SITUACAO = 'A'
|
||||
`, conversaIds);
|
||||
for (const et of todasEtiquetas) {
|
||||
if (!labelsMap[et.CET_CONVERSA_ID]) labelsMap[et.CET_CONVERSA_ID] = [];
|
||||
labelsMap[et.CET_CONVERSA_ID].push({
|
||||
id: et.ETI_CODIGO_ID, nome: (et.ETI_NOME || '').trim(), cor: (et.ETI_COR || '#667eea').trim()
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// --- Busca em lote: fotos dos clientes (se config ativo) ---
|
||||
const fotosMap = {};
|
||||
let fotoCelularAtivo = false;
|
||||
{
|
||||
const config = await db.query(alias,
|
||||
'SELECT CFE_FOTO_CELULAR FROM CHATC2_CONFIGURACOES_EMPRESA WHERE CFE_EMPRESA_ID = ?', [empresaId]);
|
||||
if (config.length > 0 && config[0].CFE_FOTO_CELULAR === 'S') {
|
||||
fotoCelularAtivo = true;
|
||||
const clienteIds = result.filter(r => r.CON_CLIENTE_ID).map(r => r.CON_CLIENTE_ID);
|
||||
if (clienteIds.length > 0) {
|
||||
const cliPlaceholders = clienteIds.map(() => '?').join(',');
|
||||
const fotos = await db.query(alias,
|
||||
'SELECT CLI_CODIGO_ID, CLI_FOTO, CLI_NOME_FOTO FROM CLIENTES WHERE CLI_CODIGO_ID IN (' + cliPlaceholders + ')',
|
||||
clienteIds);
|
||||
for (const f of fotos) {
|
||||
const rawFoto = f.CLI_FOTO;
|
||||
if (!rawFoto) continue;
|
||||
let foto = null;
|
||||
if (Buffer.isBuffer(rawFoto)) {
|
||||
const str = rawFoto.toString('utf8');
|
||||
if (/^[A-Za-z0-9+/]+=*$/.test(str.slice(0, 30))) {
|
||||
foto = str;
|
||||
} else {
|
||||
foto = rawFoto;
|
||||
let prefixo = 'data:image/jpeg;base64,';
|
||||
if (rawFoto[0] === 0x89 && rawFoto[1] === 0x50) prefixo = 'data:image/png;base64,';
|
||||
else if (rawFoto[0] === 0x47 && rawFoto[1] === 0x49) prefixo = 'data:image/gif;base64,';
|
||||
else if (rawFoto[0] === 0x52 && rawFoto[1] === 0x49) prefixo = 'data:image/webp;base64,';
|
||||
foto = prefixo + rawFoto.toString('base64');
|
||||
}
|
||||
} else {
|
||||
foto = rawFoto;
|
||||
}
|
||||
fotosMap[f.CLI_CODIGO_ID] = foto;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
id: row.CON_CODIGO_ID,
|
||||
empresaId: row.CON_EMPRESA_ID,
|
||||
instanciaId: row.CON_INSTANCIA_ID,
|
||||
clienteId: row.CON_CLIENTE_ID,
|
||||
numero: (row.CON_NUMERO || '').trim(),
|
||||
nomeContato: (row.CON_NOME_CONTATO || '').trim(),
|
||||
status: (row.CON_STATUS || '').trim(),
|
||||
usuarioId: row.CON_USUARIO_ID,
|
||||
equipeId: row.CON_EQUIPE_ID,
|
||||
dtInicio: row.CON_DT_INICIO,
|
||||
dtFinal: row.CON_DT_FINAL,
|
||||
ultimaMsg: row.ULTIMA_MSG || '',
|
||||
dtUltimaMsg: row.DT_ULTIMA_MSG || row.CON_DT_ULTIMA_MSG,
|
||||
saudacaoEnviada: (row.CON_SAUDACAO_ENVIADA || 'N').trim(),
|
||||
csatEnviado: (row.CON_CSAT_ENVIADO || 'N').trim(),
|
||||
primeiraMsg: row.CON_PRIMEIRA_MSG,
|
||||
labels,
|
||||
foto,
|
||||
};
|
||||
// --- Monta resultado final sem novas queries ---
|
||||
const conversations = result.map((row) => ({
|
||||
id: row.CON_CODIGO_ID,
|
||||
empresaId: row.CON_EMPRESA_ID,
|
||||
instanciaId: row.CON_INSTANCIA_ID,
|
||||
clienteId: row.CON_CLIENTE_ID,
|
||||
numero: (row.CON_NUMERO || '').trim(),
|
||||
nomeContato: (row.CON_NOME_CONTATO || '').trim(),
|
||||
status: (row.CON_STATUS || '').trim(),
|
||||
usuarioId: row.CON_USUARIO_ID,
|
||||
equipeId: row.CON_EQUIPE_ID,
|
||||
dtInicio: row.CON_DT_INICIO,
|
||||
dtFinal: row.CON_DT_FINAL,
|
||||
ultimaMsg: row.ULTIMA_MSG || '',
|
||||
ultimaTipo: (row.ULTIMA_TIPO || 'text').trim(),
|
||||
dtUltimaMsg: row.DT_ULTIMA_MSG || row.CON_DT_ULTIMA_MSG,
|
||||
saudacaoEnviada: (row.CON_SAUDACAO_ENVIADA || 'N').trim(),
|
||||
csatEnviado: (row.CON_CSAT_ENVIADO || 'N').trim(),
|
||||
primeiraMsg: row.CON_PRIMEIRA_MSG,
|
||||
labels: labelsMap[row.CON_CODIGO_ID] || [],
|
||||
foto: fotosMap[row.CON_CLIENTE_ID] || null,
|
||||
}));
|
||||
|
||||
// Contagens para cada filtro
|
||||
@@ -490,8 +513,8 @@ class ChatController {
|
||||
FROM CHATC2_CONVERSAS_MENSAGENS
|
||||
WHERE CME_CONVERSA_ID = ? AND CME_SITUACAO = 'A'
|
||||
ORDER BY CME_DT_ENVIO DESC
|
||||
OFFSET ${offset} ROWS FETCH NEXT ${limit} ROWS ONLY
|
||||
`, [id]);
|
||||
OFFSET ? ROWS FETCH NEXT ? ROWS ONLY
|
||||
`, [id, offset, limit]);
|
||||
|
||||
var mapped = msgs.reverse().map(function(m) { return {
|
||||
id: m.CME_CODIGO_ID,
|
||||
@@ -551,14 +574,12 @@ class ChatController {
|
||||
"UPDATE CHATC2_CONVERSAS SET CON_STATUS = 'A', CON_DT_FINAL = NULL WHERE CON_CODIGO_ID = ?", [id]);
|
||||
}
|
||||
|
||||
const maxId = await db.query(alias, 'SELECT MAX(CME_CODIGO_ID) AS ID FROM CHATC2_CONVERSAS_MENSAGENS');
|
||||
const newId = (maxId[0]?.ID || 0) + 1;
|
||||
const newId = await db.nextId(alias, 'GEN_CONVERSAS_MENSAGENS');
|
||||
let midiaId = null;
|
||||
|
||||
// Salva mídia se houver
|
||||
if (midiaBase64) {
|
||||
const maxMidia = await db.query(alias, 'SELECT MAX(MAT_CODIGO_ID) AS ID FROM CHATC2_MENSAGENS_ATENDIMENTOS');
|
||||
midiaId = (maxMidia[0]?.ID || 0) + 1;
|
||||
midiaId = await db.nextId(alias, 'GEN_MENSAGENS_ATENDIMENTOS');
|
||||
|
||||
// Para áudio: converte WebM → OGG antes de salvar no BD
|
||||
// Assim o BD já tem o OGG e o envio para Evolution usa a mesma versão
|
||||
@@ -824,8 +845,7 @@ class ChatController {
|
||||
}
|
||||
|
||||
// Adiciona mensagem na conversa existente
|
||||
const maxMsgId = await db.query(alias, 'SELECT MAX(CME_CODIGO_ID) AS ID FROM CHATC2_CONVERSAS_MENSAGENS');
|
||||
const msgId = (maxMsgId[0]?.ID || 0) + 1;
|
||||
const msgId = await db.nextId(alias, 'GEN_CONVERSAS_MENSAGENS');
|
||||
await db.execute(alias, `
|
||||
INSERT INTO CHATC2_CONVERSAS_MENSAGENS (CME_CODIGO_ID, CME_CONVERSA_ID, CME_REMETENTE, CME_USUARIO_ID, CME_TEXTO, CME_TIPO, CME_SITUACAO, CME_DT_ENVIO)
|
||||
VALUES (?, ?, 'U', ?, ?, 'text', 'A', CURRENT_TIMESTAMP)
|
||||
@@ -846,8 +866,7 @@ class ChatController {
|
||||
}
|
||||
|
||||
// Gera ID da nova conversa
|
||||
const maxId = await db.query(alias, 'SELECT MAX(CON_CODIGO_ID) AS ID FROM CHATC2_CONVERSAS');
|
||||
const newId = (maxId[0]?.ID || 0) + 1;
|
||||
const newId = await db.nextId(alias, 'GEN_CHATC2_CONVERSAS');
|
||||
|
||||
// Se não informou instância, pega a primeira ativa
|
||||
let instId = instanciaId;
|
||||
@@ -865,8 +884,7 @@ class ChatController {
|
||||
`, [newId, empresaId, instId, numeroLimpo, nomeContato || numeroLimpo, clienteId || null]);
|
||||
|
||||
// Insere primeira mensagem
|
||||
const maxMsgId = await db.query(alias, 'SELECT MAX(CME_CODIGO_ID) AS ID FROM CHATC2_CONVERSAS_MENSAGENS');
|
||||
const msgId = (maxMsgId[0]?.ID || 0) + 1;
|
||||
const msgId = await db.nextId(alias, 'GEN_CONVERSAS_MENSAGENS');
|
||||
|
||||
await db.execute(alias, `
|
||||
INSERT INTO CHATC2_CONVERSAS_MENSAGENS (CME_CODIGO_ID, CME_CONVERSA_ID, CME_REMETENTE, CME_USUARIO_ID, CME_TEXTO, CME_TIPO, CME_SITUACAO, CME_DT_ENVIO)
|
||||
@@ -1279,8 +1297,7 @@ class ChatController {
|
||||
}
|
||||
|
||||
// Salva mensagem do agente
|
||||
const maxId = await db.query(alias, 'SELECT MAX(CME_CODIGO_ID) AS ID FROM CHATC2_CONVERSAS_MENSAGENS');
|
||||
const newId = (maxId[0]?.ID || 0) + 1;
|
||||
const newId = await db.nextId(alias, 'GEN_CONVERSAS_MENSAGENS');
|
||||
await db.execute(alias, `
|
||||
INSERT INTO CHATC2_CONVERSAS_MENSAGENS (CME_CODIGO_ID, CME_CONVERSA_ID, CME_REMETENTE, CME_USUARIO_ID, CME_TEXTO, CME_TIPO, CME_PRIVADA, CME_SITUACAO, CME_DT_ENVIO)
|
||||
VALUES (?, ?, 'U', ?, ?, 'text', 'N', 'A', CURRENT_TIMESTAMP)
|
||||
@@ -1306,7 +1323,57 @@ class ChatController {
|
||||
}
|
||||
|
||||
/**
|
||||
* Recebe avaliação CSAT via formulário web
|
||||
* Busca mensagens/conversas por termo de texto.
|
||||
* GET /api/:alias/search?q=termo&empresaId=
|
||||
*/
|
||||
static async search(req, res) {
|
||||
try {
|
||||
const { alias } = req.params;
|
||||
const q = (req.query.q || '').trim();
|
||||
const empresaId = parseInt(req.query.empresaId) || req.user?.empresas?.[0];
|
||||
const limit = Math.min(parseInt(req.query.limit) || 30, 100);
|
||||
|
||||
if (!q || q.length < 2) {
|
||||
return res.status(400).json({ success: false, error: 'Termo de busca muito curto (mínimo 2 caracteres).' });
|
||||
}
|
||||
if (!req.user?.empresas?.some(e => Number(e) === Number(empresaId))) {
|
||||
return res.status(403).json({ success: false, error: 'Sem permissão.' });
|
||||
}
|
||||
|
||||
const results = await db.query(alias, `
|
||||
SELECT m.CME_CODIGO_ID, m.CME_CONVERSA_ID, m.CME_TEXTO, m.CME_TIPO,
|
||||
m.CME_DT_ENVIO, m.CME_REMETENTE,
|
||||
c.CON_NUMERO, c.CON_NOME_CONTATO, c.CON_STATUS
|
||||
FROM CHATC2_CONVERSAS_MENSAGENS m
|
||||
JOIN CHATC2_CONVERSAS c ON c.CON_CODIGO_ID = m.CME_CONVERSA_ID
|
||||
WHERE c.CON_EMPRESA_ID = ?
|
||||
AND c.CON_SITUACAO = 'A'
|
||||
AND m.CME_SITUACAO = 'A'
|
||||
AND m.CME_TEXTO CONTAINING ?
|
||||
ORDER BY m.CME_DT_ENVIO DESC
|
||||
FETCH FIRST ? ROWS ONLY
|
||||
`, [empresaId, q, limit]);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: results.map(r => ({
|
||||
mensagemId: r.CME_CODIGO_ID,
|
||||
conversaId: r.CME_CONVERSA_ID,
|
||||
texto: (r.CME_TEXTO || '').substring(0, 120),
|
||||
tipo: (r.CME_TIPO || 'text').trim(),
|
||||
dtEnvio: r.CME_DT_ENVIO,
|
||||
remetente: (r.CME_REMETENTE || '').trim(),
|
||||
numero: (r.CON_NUMERO || '').trim(),
|
||||
nomeContato: (r.CON_NOME_CONTATO || '').trim(),
|
||||
status: (r.CON_STATUS || '').trim(),
|
||||
})),
|
||||
count: results.length,
|
||||
});
|
||||
} catch (err) { res.status(500).json({ success: false, error: err.message }); }
|
||||
}
|
||||
|
||||
/**
|
||||
* Recebe avaliação CSAT via formulário web
|
||||
* POST /api/:alias/csat/avaliar
|
||||
*/
|
||||
static async csatAvaliar(req, res) {
|
||||
@@ -1329,8 +1396,7 @@ class ChatController {
|
||||
}
|
||||
|
||||
// Salva avaliação
|
||||
const maxId = await db.query(alias, 'SELECT MAX(CSA_CODIGO_ID) AS ID FROM CHATC2_CSAT_AVALIACOES');
|
||||
const newId = (maxId[0]?.ID || 0) + 1;
|
||||
const newId = await db.nextId(alias, 'GEN_CHATC2_CSAT_AVALIACOES');
|
||||
|
||||
await db.execute(alias,
|
||||
'INSERT INTO CHATC2_CSAT_AVALIACOES (CSA_CODIGO_ID, CSA_CONVERSA_ID, CSA_EMPRESA_ID, CSA_CLIENTE_ID, CSA_NOTA, CSA_COMENTARIO) VALUES (?, ?, ?, ?, ?, ?)',
|
||||
|
||||
Reference in New Issue
Block a user