adição de agendamento de conversa

This commit is contained in:
2026-06-29 12:27:48 +00:00
parent 71778cbd24
commit c9d2c668ee
6 changed files with 336 additions and 2 deletions
+140
View File
@@ -1569,6 +1569,146 @@ class ChatController {
return texto; // fallback: retorna sem substituir
}
}
// ==================== AGENDAMENTOS ====================
/**
* Agenda retorno para uma conversa.
* POST /api/:alias/conversations/:id/schedule
*/
static async scheduleConversation(req, res) {
try {
const { alias, id } = req.params;
const { data, hora, nota } = req.body;
if (!data) return res.status(400).json({ success: false, error: 'Data é obrigatória.' });
const chk = await ChatController.checarConversaEmpresa(alias, id, req);
if (chk.status) return res.status(chk.status).json({ success: false, error: 'Conversa não encontrada ou sem permissão.' });
const newId = await db.nextId(alias, 'GEN_CHATC2_AGENDAMENTOS');
await db.execute(alias, `
INSERT INTO CHATC2_CONVERSAS_AGENDAMENTOS (CAG_CODIGO_ID, CAG_CONVERSA_ID, CAG_EMPRESA_ID, CAG_DATA, CAG_HORA, CAG_NOTA, CAG_USUARIO_ID)
VALUES (?, ?, ?, ?, ?, ?, ?)
`, [newId, id, chk.empresaId, data, hora || null, nota || null, req.user?.id]);
res.json({ success: true, data: { id: newId } });
} catch (err) { res.status(500).json({ success: false, error: err.message }); }
}
/**
* Lista agendamentos de uma data (ou hoje).
* GET /api/:alias/schedules?data=2026-12-25&empresaId=
*/
static async listSchedules(req, res) {
try {
const { alias } = req.params;
const empresaId = parseInt(req.query.empresaId) || req.user?.empresas?.[0];
const data = req.query.data || new Date().toISOString().split('T')[0];
if (!req.user?.empresas?.some(e => Number(e) === Number(empresaId))) {
return res.status(403).json({ success: false, error: 'Sem permissão.' });
}
const result = await db.query(alias, `
SELECT a.*, c.CON_NUMERO, c.CON_NOME_CONTATO, c.CON_CLIENTE_ID
FROM CHATC2_CONVERSAS_AGENDAMENTOS a
JOIN CHATC2_CONVERSAS c ON c.CON_CODIGO_ID = a.CAG_CONVERSA_ID
WHERE a.CAG_EMPRESA_ID = ? AND a.CAG_DATA = ? AND a.CAG_STATUS = 'P'
ORDER BY a.CAG_HORA NULLS LAST, a.CAG_DT_CRIACAO
`, [empresaId, data]);
res.json({
success: true,
data: result.map(r => ({
id: r.CAG_CODIGO_ID,
conversaId: r.CAG_CONVERSA_ID,
data: r.CAG_DATA,
hora: r.CAG_HORA,
nota: (r.CAG_NOTA || '').trim(),
numero: (r.CON_NUMERO || '').trim(),
nomeContato: (r.CON_NOME_CONTATO || '').trim(),
clienteId: r.CON_CLIENTE_ID,
status: (r.CAG_STATUS || '').trim(),
})),
count: result.length,
});
} catch (err) { res.status(500).json({ success: false, error: err.message }); }
}
/**
* Cria nova conversa a partir de um agendamento.
* POST /api/:alias/schedules/:id/reopen
* Retorna a nova conversa e o texto sugerido — NÃO envia automaticamente.
*/
static async reopenSchedule(req, res) {
try {
const { alias, id } = req.params;
const ag = await db.query(alias,
`SELECT a.*, c.CON_NUMERO, c.CON_NOME_CONTATO, c.CON_CLIENTE_ID, c.CON_EMPRESA_ID, c.CON_INSTANCIA_ID
FROM CHATC2_CONVERSAS_AGENDAMENTOS a
JOIN CHATC2_CONVERSAS c ON c.CON_CODIGO_ID = a.CAG_CONVERSA_ID
WHERE a.CAG_CODIGO_ID = ?`, [id]);
if (ag.length === 0) return res.status(404).json({ success: false, error: 'Agendamento não encontrado.' });
const a = ag[0];
const numeroLimpo = (a.CON_NUMERO || '').replace(/\D/g, '').substring(0, 15);
const nomeContato = (a.CON_NOME_CONTATO || a.CON_NUMERO || '').trim();
const nota = (a.CAG_NOTA || '').trim();
let clienteId = a.CON_CLIENTE_ID || null;
// Se não tem cliente vinculado, busca pelo número
if (!clienteId) {
const variantes = [numeroLimpo];
if (numeroLimpo.startsWith('55')) variantes.push(numeroLimpo.substring(2));
variantes.push(numeroLimpo.slice(-8));
for (const termo of variantes) {
const cli = await db.query(alias, `
SELECT CLI_CODIGO_ID, CLI_NOME FROM CLIENTES
WHERE CLI_EMPRESA_ID = ?
AND (REPLACE(REPLACE(REPLACE(COALESCE(CLI_CELULAR,''),'-',''),'(',''),')','') LIKE '%' || ? || '%'
OR REPLACE(REPLACE(REPLACE(COALESCE(CLI_FONE1,''),'-',''),'(',''),')','') LIKE '%' || ? || '%'
OR REPLACE(REPLACE(REPLACE(COALESCE(CLI_FONE2,''),'-',''),'(',''),')','') LIKE '%' || ? || '%')
FETCH FIRST 1 ROWS ONLY
`, [a.CAG_EMPRESA_ID, termo, termo, termo]);
if (cli.length > 0) { clienteId = cli[0].CLI_CODIGO_ID; break; }
}
if (!clienteId) {
for (const termo of variantes) {
const dep = await db.query(alias, `
SELECT DEPC_CLIENTE_ID FROM DEPENDENTES_CLI
WHERE DEPC_EMPRESA_ID = ? AND DEPC_SITUACAO = 'A'
AND REPLACE(REPLACE(REPLACE(COALESCE(DEPC_TELEFONE,''),'-',''),'(',''),')','') LIKE '%' || ? || '%'
FETCH FIRST 1 ROWS ONLY
`, [a.CAG_EMPRESA_ID, termo]);
if (dep.length > 0) { clienteId = dep[0].DEPC_CLIENTE_ID; break; }
}
}
}
// Cria NOVA conversa
const newId = await db.nextId(alias, 'GEN_CHATC2_CONVERSAS');
await db.execute(alias, `
INSERT INTO CHATC2_CONVERSAS (CON_CODIGO_ID, CON_EMPRESA_ID, CON_INSTANCIA_ID, CON_NUMERO,
CON_NOME_CONTATO, CON_CLIENTE_ID, CON_STATUS, CON_SITUACAO, CON_PRIMEIRA_MSG, CON_DT_ULTIMA_MSG)
VALUES (?, ?, ?, ?, ?, ?, 'A', 'A', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)
`, [newId, a.CAG_EMPRESA_ID, a.CON_INSTANCIA_ID, numeroLimpo, nomeContato, clienteId]);
// Monta texto sugerido (placeholders serão processados no frontend ao enviar)
const textoSugerido = nota
? '📅 *Retorno agendado*\n' + nota + '\n\nOlá [CLIENTE]! Conforme combinado, estou retornando o contato. Em que posso ajudar?'
: '👋 Olá [CLIENTE]! Conforme combinado, estou retornando o contato. Em que posso ajudar?';
// Marca agendamento como retomado
await db.execute(alias,
"UPDATE CHATC2_CONVERSAS_AGENDAMENTOS SET CAG_STATUS = 'R' WHERE CAG_CODIGO_ID = ?", [id]);
res.json({ success: true, conversaId: newId, textoSugerido: textoSugerido });
} catch (err) { res.status(500).json({ success: false, error: err.message }); }
}
}
// Helper: normaliza número para formato WhatsApp (DDI + 9º dígito)