refactor: code adjustment

analisando_gabriel-poc
adriano 2025-06-16 14:56:12 -03:00
parent a1195810fe
commit ce94c22f0a
5 changed files with 255 additions and 82 deletions

View File

@ -0,0 +1,208 @@
{
"authentication": {
"type": "oauth2",
"crmClientId": "3MVG9JJwBBbcN47Kv0Z7EuNd19INI1Bhe7uX_Wz6M0VlMyWJD4xPKTtn_b39bGn6LmdSkKJ.aLNGdV1brj16C",
"crmClientSecret": "870E8D51A1CA06896D966A3D92ABD885346DAD4428926E965776C479055969E7",
"crmScopes": "full refresh_token",
"crmPhoneTest": "5514983253326"
},
"crmRest": [
{
"authorizationEndpoint": {
"request": {
"requestContentType": "empty",
"requestEncoding": "empty",
"requestType": "Get",
"responseType": "empty",
"url": "https://login.salesforce.com/services/oauth2/authorize?response_type=code&client_id=crmClientId&code_challenge=bDXEJ0wxr0s369lGxHwewLULiOuyl6Y3W7QZABmn2S4&redirect_uri=crmRedirectURI&scope=crmScopes&code_challenge_method=S256"
},
"body": {},
"response": {}
}
},
{
"tokenEndpoint": {
"request": {
"requestContentType": "none",
"requestEncoding": "Json",
"requestType": "Post",
"responseType": "Json",
"url": "https://login.salesforce.com/services/oauth2/token"
},
"body": {},
"response": {}
}
},
{
"createContactRecord": {
"request": {
"requestContentType": "application/json",
"requestEncoding": "Json",
"requestType": "Post",
"responseType": "Json",
"url": "https://nocompany-a9-dev-ed.develop.my.salesforce.com/services/data/v61.0/sobjects/Contact"
},
"body": {
"Phone": "crmPhone",
"LastName": "crmLastName",
"FirstName": "crmFirstName"
},
"response": {
"id": "id"
}
}
},
{
"lookupContactByPhone": {
"request": {
"requestContentType": "application/json",
"requestEncoding": "Json",
"requestType": "Get",
"responseType": "Json",
"url": "https://nocompany-a9-dev-ed.develop.my.salesforce.com/services/data/v61.0/query/?q=SELECT+Id,+Phone,+AccountId+FROM+Contact+WHERE+Phone='crmPhone'"
},
"response": {
"phone": "records[0].Phone",
"id": "records[0].Id",
"accountId": "records[0].AccountId",
"name": "records[0].Name"
}
}
},
{
"callJournaling": {
"request": {
"requestContentType": "application/json",
"requestEncoding": "Json",
"requestType": "Post",
"responseType": "Json",
"url": "https://nocompany-a9-dev-ed.develop.my.salesforce.com/services/data/v61.0/sobjects/Task"
},
"calls": [
{
"inboundAnsweredCall": {
"Subject": "Call Journal",
"WhoId": "crmContactId",
"Description": "Ligação recebida",
"Status": "Completed",
"Priority": "Normal",
"CallType": "Outbound",
"CallDurationInSeconds": {
"_prop": "crmCallDuration",
"_type": "number",
"_format": "seconds"
},
"ActivityDate": "YYYY-MM-DD",
"TaskSubtype": "Call"
}
},
{
"inboundMissedCall": {
"Subject": "Call Journal",
"WhoId": "crmContactId",
"Description": "Ligação recebida perdida",
"Status": "Completed",
"Priority": "Normal",
"CallType": "Outbound",
"CallDurationInSeconds": {
"_prop": "crmCallDuration",
"_type": "number",
"_format": "seconds"
},
"ActivityDate": "YYYY-MM-DD",
"TaskSubtype": "Call"
}
},
{
"outboundAnsweredCall": {
"Subject": "Call Journal",
"WhoId": "crmContactId",
"Description": "Ligação realizada",
"Status": "Completed",
"Priority": "Normal",
"CallType": "Outbound",
"CallDurationInSeconds": {
"_prop": "crmCallDuration",
"_type": "number",
"_format": "seconds"
},
"ActivityDate": "YYYY-MM-DD",
"TaskSubtype": "Call"
}
},
{
"outboundUnansweredCall": {
"Subject": "Call Journal",
"WhoId": "crmContactId",
"Description": "Ligação realizada nao atendida",
"Status": "Completed",
"Priority": "Normal",
"CallType": "Outbound",
"CallDurationInSeconds": {
"_prop": "crmCallDuration",
"_type": "number",
"_format": "seconds"
},
"ActivityDate": "YYYY-MM-DD",
"TaskSubtype": "Call"
}
}
],
"response": {}
}
},
{
"chatJournaling": {
"request": {
"requestContentType": "application/json",
"requestEncoding": "Json",
"requestType": "Post",
"responseType": "Json",
"url": "https://nocompany-a9-dev-ed.develop.my.salesforce.com/services/data/v61.0/sobjects/Task"
},
"chats": [
{
"chatDone":{
"WhoId": "crmContactId",
"Subject": "WhatsApp Chat",
"Description": "Conversation started via WhatsApp. Conversation link: chatLink",
"ActivityDate": "YYYY-MM-DD",
"Status": "Completed",
"Priority": "Normal"
}
}
]
}
},
{
"redirectLink": {
"request": {
"url": "https://nocompany-a9-dev-ed.develop.lightning.force.com/lightning/r/Lead/crmContactId/edit?count=1&backgroundContext=%2Flightning%2Fr%2FLead%2F00Qak0000098YFhEAM%2Fview"
}
}
},
{
"createCase": {
"request": {
"requestContentType": "application/json",
"requestEncoding": "Json",
"requestType": "Post",
"responseType": "Json",
"url": "https://nocompany-a9-dev-ed.develop.my.salesforce.com/services/data/v61.0/sobjects/Case"
},
"body": {
"Subject": "Assunto do Caso teste",
"Description": "Descrição detalhada do caso test",
"Status": "Novo",
"Priority": "Alta",
"Departamento__c": "Financeiro"
},
"response": {
"id": "id"
}
}
}
]
}

View File

@ -408,7 +408,7 @@ const createTicket = async (req, res) => {
const ticketIdMatch = crmTicketLink.ticketId.match(/ticket\/(\d+)/); const ticketIdMatch = crmTicketLink.ticketId.match(/ticket\/(\d+)/);
if (ticketIdMatch && ticketIdMatch[1]) { if (ticketIdMatch && ticketIdMatch[1]) {
const ticketId = ticketIdMatch[1]; const ticketId = ticketIdMatch[1];
await set(crmPhone, ticketId); await set(crmPhone, ticketId, 10800); // excluido após 3 horas(10800 segundos) caso não receba a requisição de trascrição
console.log(`TicketId ${ticketId} para crmPhone ${crmPhone} salvo no Redis com sucesso.`); console.log(`TicketId ${ticketId} para crmPhone ${crmPhone} salvo no Redis com sucesso.`);
} }
} }
@ -557,41 +557,6 @@ const webhook_crm = async (req, res) => {
return res.set('Content-Type', 'text/xml').status(StatusCodes.OK).send(responseXml) return res.set('Content-Type', 'text/xml').status(StatusCodes.OK).send(responseXml)
} }
const associateTicketToCaller = async (req, res) => {
try {
const { callerId, ticketId } = req.body;
if (!callerId || !ticketId) {
return res.status(StatusCodes.BAD_REQUEST).json({
error: 'Campos obrigatórios ausentes. É necessário fornecer callerId e ticketId.'
});
}
// Remove o zero inicial do número se existir
const formattedCallerId = removeZeroInicial(callerId);
// Adiciona o prefixo 55 se não existir
const fullCallerId = formattedCallerId.startsWith('55') ? formattedCallerId : `55${formattedCallerId}`;
// Salva a associação no Redis com um tempo de expiração de 5 minutos (300 segundos)
await set(fullCallerId, ticketId, 300);
console.log(`Ticket ${ticketId} associado ao número ${fullCallerId} com sucesso.`);
return res.status(StatusCodes.OK).json({
message: 'Ticket associado com sucesso',
callerId: fullCallerId,
ticketId
});
} catch (error) {
console.error('Erro ao associar ticket:', error);
Sentry.captureException(error);
return res.status(StatusCodes.INTERNAL_SERVER_ERROR).json({
error: 'Erro ao processar a associação do ticket'
});
}
};
const checkTicketByCrmPhone = async (req, res) => { const checkTicketByCrmPhone = async (req, res) => {
const { crmPhone } = req.query; const { crmPhone } = req.query;
@ -633,7 +598,6 @@ module.exports = {
sfCreateCase, sfCreateCase,
sfUpdateCase, sfUpdateCase,
createTicket, createTicket,
associateTicketToCaller,
checkTicketByCrmPhone checkTicketByCrmPhone
} }

View File

@ -1,55 +1,50 @@
const redis = require('redis');
const HubspotService = require('../services/hubspotService'); const HubspotService = require('../services/hubspotService');
require('dotenv').config(); const { get, del } = require('../utils/redisClient')
const redisClient = redis.createClient({
url: process.env.REDIS_URI
});
redisClient.on('error', (err) => console.log('Redis Client Error', err));
(async () => {
if (!redisClient.isOpen) {
await redisClient.connect();
}
})();
const hubspotService = new HubspotService(); const hubspotService = new HubspotService();
exports.receiveTranscription = async (req, res) => { const receiveTranscription = async (req, res) => {
try { try {
const { callerId, uniqueId, transcription, recordingUrl } = req.body;
if (!callerId || !uniqueId || !transcription || !recordingUrl) { const { crmPhone, uniqueId, transcription, recordingUrl, companyId } = req.body;
if (!crmPhone || !uniqueId || !transcription || !recordingUrl) {
return res.status(400).json({ error: 'Campos obrigatórios ausentes.' }); return res.status(400).json({ error: 'Campos obrigatórios ausentes.' });
} }
console.log(`Recebida transcrição para callerId: ${callerId}, uniqueId: ${uniqueId}`); console.log(`Recebida transcrição para crmPhone: ${crmPhone}, uniqueId: ${uniqueId}`);
console.log('Transcrição:', transcription.summary); console.log('Transcrição:', transcription.summary);
// 1. Buscar ticketId no Redis // 1. Buscar ticketId no Redis
const ticketId = await redisClient.get(callerId); const ticketId = await get(crmPhone);
if (!ticketId) { if (!ticketId) {
console.warn(`Nenhum ticketId encontrado no Redis para o callerId: ${callerId}. A transcrição será salva como uma nota sem associação ao ticket.`); console.warn(`Nenhum ticketId encontrado no Redis para o crmPhone: ${crmPhone}. A transcrição será salva como uma nota sem associação ao ticket.`);
} }
// 2. Buscar ou criar contato no HubSpot // 2. Buscar ou criar contato no HubSpot
const contact = await hubspotService.createContactIfNotExists(callerId); const contact = await hubspotService.createContactIfNotExists(companyId, crmPhone);
// 3. Criar nota no HubSpot e associar ao contato e ao ticket (se existir) // 3. Criar nota no HubSpot e associar ao contato e ao ticket (se existir)
await hubspotService.createCallNote(contact.id, { await hubspotService.createCallNote(contact.contactId, {
transcription: `${transcription.client || ''}\n${transcription.agent || ''}`, transcription: `${transcription.client || ''}\n${transcription.agent || ''}`,
summary: transcription.summary, summary: transcription.summary,
recordingUrl, recordingUrl,
callerId, crmPhone,
uniqueId, uniqueId,
ticketId ticketId
}); });
await del(crmPhone)
return res.status(200).json({ message: 'Transcrição recebida e processada com sucesso!' }); return res.status(200).json({ message: 'Transcrição recebida e processada com sucesso!' });
} catch (error) { } catch (error) {
console.error('Erro ao receber transcrição:', error?.response?.data || error.message || error); console.error('Erro ao receber transcrição:', error?.response?.data || error.message || error);
return res.status(500).json({ error: 'Erro ao processar a transcrição.' }); return res.status(500).json({ error: 'Erro ao processar a transcrição.' });
} }
}; };
module.exports ={
receiveTranscription
}

View File

@ -4,7 +4,6 @@ const { authorization, } = require('../middleware/authentication')
const { contactCreate, sfCreateCase, sfUpdateCase, createTicket, testTemplate, webhook_crm, uploadCrmConfig, callJournaling, oauthCallBack, install, deleteCrm, deleteCompany, getCrms, webhook, checkTicketByCrmPhone } = require('../controllers/crmController') const { contactCreate, sfCreateCase, sfUpdateCase, createTicket, testTemplate, webhook_crm, uploadCrmConfig, callJournaling, oauthCallBack, install, deleteCrm, deleteCompany, getCrms, webhook, checkTicketByCrmPhone } = require('../controllers/crmController')
const { receiveTranscription } = require('../controllers/transcriptionController') const { receiveTranscription } = require('../controllers/transcriptionController')
const { fileUpload } = require("../utils") const { fileUpload } = require("../utils")
const { associateTicketToCaller } = require('../controllers/crmController');
router.route('/create-contact').post(authorization, contactCreate) router.route('/create-contact').post(authorization, contactCreate)
router.route('/create-ticket').post(authorization, createTicket) router.route('/create-ticket').post(authorization, createTicket)
@ -19,7 +18,7 @@ router.route('/install').get(install)
router.route('/test').post(testTemplate) router.route('/test').post(testTemplate)
router.route('/webhook').post(webhook) router.route('/webhook').post(webhook)
router.route('/webhook-crm').post(webhook_crm) router.route('/webhook-crm').post(webhook_crm)
router.route('/transcriptions').post( receiveTranscription) router.route('/transcriptions').post(authorization, receiveTranscription)
router.route('/:companyId').get(authorization, getCrms) router.route('/:companyId').get(authorization, getCrms)
router.route('/tickets/check-by-crmphone').get(authorization, checkTicketByCrmPhone) router.route('/tickets/check-by-crmphone').get(authorization, checkTicketByCrmPhone)

View File

@ -1,6 +1,9 @@
const axios = require('axios'); const axios = require('axios');
const Logger = console; const Logger = console;
require('dotenv').config(); require('dotenv').config();
const loadCRM = require('../utils/loadCRM')
const lookupContactByPhone = require('../utils/lookupCRMContactByPhone')
const createContact = require('../utils/createContact')
/** /**
* Serviço para integração com a API do HubSpot. * Serviço para integração com a API do HubSpot.
@ -112,22 +115,26 @@ Link da Gravação: ${recordingUrl}
/** /**
* Cria um contato com o número informado caso não exista. * Cria um contato com o número informado caso não exista.
* *
* @param {string} phoneNumber - Número de telefone do contato. * @param {string} crmPhone - Número de telefone do contato.
* @returns {Promise<Object>} - Contato existente ou novo contato criado. * @returns {Promise<Object>} - Contato existente ou novo contato criado.
*/ */
async createContactIfNotExists(phoneNumber) { async createContactIfNotExists(companyId, crmPhone) {
const existing = await this.findContactByPhone(phoneNumber);
if (existing) return existing;
const response = await this.client.post('/crm/v3/objects/contacts', { const crmFiles = await loadCRM(companyId)
properties: {
phone: phoneNumber,
if (crmFiles.length > 0) {
const { crmRest: rest, authentication } = crmFiles[0].crm
let contact = await lookupContactByPhone(rest, authentication, crmPhone, companyId)
if (!contact.exist) {
contact = await createContact(companyId, rest, authentication, crmPhone)
}
return contact
} }
});
this.logger.log(`Contato criado para o número ${phoneNumber}`);
return response.data;
} }
} }