refactor: code adjustment
parent
a1195810fe
commit
ce94c22f0a
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
|
@ -408,7 +408,7 @@ const createTicket = async (req, res) => {
|
|||
const ticketIdMatch = crmTicketLink.ticketId.match(/ticket\/(\d+)/);
|
||||
if (ticketIdMatch && 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.`);
|
||||
}
|
||||
}
|
||||
|
@ -557,41 +557,6 @@ const webhook_crm = async (req, res) => {
|
|||
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 { crmPhone } = req.query;
|
||||
|
@ -633,7 +598,6 @@ module.exports = {
|
|||
sfCreateCase,
|
||||
sfUpdateCase,
|
||||
createTicket,
|
||||
associateTicketToCaller,
|
||||
checkTicketByCrmPhone
|
||||
}
|
||||
|
||||
|
|
|
@ -1,55 +1,50 @@
|
|||
const redis = require('redis');
|
||||
const HubspotService = require('../services/hubspotService');
|
||||
require('dotenv').config();
|
||||
|
||||
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 { get, del } = require('../utils/redisClient')
|
||||
|
||||
const hubspotService = new HubspotService();
|
||||
|
||||
exports.receiveTranscription = async (req, res) => {
|
||||
const receiveTranscription = async (req, res) => {
|
||||
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.' });
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
// 1. Buscar ticketId no Redis
|
||||
const ticketId = await redisClient.get(callerId);
|
||||
const ticketId = await get(crmPhone);
|
||||
|
||||
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
|
||||
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)
|
||||
await hubspotService.createCallNote(contact.id, {
|
||||
await hubspotService.createCallNote(contact.contactId, {
|
||||
transcription: `${transcription.client || ''}\n${transcription.agent || ''}`,
|
||||
summary: transcription.summary,
|
||||
recordingUrl,
|
||||
callerId,
|
||||
crmPhone,
|
||||
uniqueId,
|
||||
ticketId
|
||||
});
|
||||
|
||||
await del(crmPhone)
|
||||
|
||||
return res.status(200).json({ message: 'Transcrição recebida e processada com sucesso!' });
|
||||
} catch (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.' });
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
module.exports ={
|
||||
receiveTranscription
|
||||
}
|
|
@ -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 { receiveTranscription } = require('../controllers/transcriptionController')
|
||||
const { fileUpload } = require("../utils")
|
||||
const { associateTicketToCaller } = require('../controllers/crmController');
|
||||
|
||||
router.route('/create-contact').post(authorization, contactCreate)
|
||||
router.route('/create-ticket').post(authorization, createTicket)
|
||||
|
@ -19,7 +18,7 @@ router.route('/install').get(install)
|
|||
router.route('/test').post(testTemplate)
|
||||
router.route('/webhook').post(webhook)
|
||||
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('/tickets/check-by-crmphone').get(authorization, checkTicketByCrmPhone)
|
||||
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
const axios = require('axios');
|
||||
const Logger = console;
|
||||
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.
|
||||
|
@ -112,22 +115,26 @@ Link da Gravação: ${recordingUrl}
|
|||
/**
|
||||
* 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.
|
||||
*/
|
||||
async createContactIfNotExists(phoneNumber) {
|
||||
const existing = await this.findContactByPhone(phoneNumber);
|
||||
if (existing) return existing;
|
||||
async createContactIfNotExists(companyId, crmPhone) {
|
||||
|
||||
const response = await this.client.post('/crm/v3/objects/contacts', {
|
||||
properties: {
|
||||
phone: phoneNumber,
|
||||
const crmFiles = await loadCRM(companyId)
|
||||
|
||||
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)
|
||||
}
|
||||
});
|
||||
|
||||
this.logger.log(`Contato criado para o número ${phoneNumber}`);
|
||||
return response.data;
|
||||
return contact
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue