From ce94c22f0aba3ac07e51c722dedc947d9fcbfe08 Mon Sep 17 00:00:00 2001 From: adriano Date: Mon, 16 Jun 2025 14:56:12 -0300 Subject: [PATCH] refactor: code adjustment --- ...auth2_lead_editing_case_cliente_veste.json | 208 ++++++++++++++++++ backend/controllers/crmController.js | 44 +--- .../controllers/transcriptionController.js | 49 ++--- backend/routes/crmRoute.js | 5 +- backend/services/hubspotService.js | 31 ++- 5 files changed, 255 insertions(+), 82 deletions(-) create mode 100644 backend/Templates-test/salesforce_oauth2_lead_editing_case_cliente_veste.json diff --git a/backend/Templates-test/salesforce_oauth2_lead_editing_case_cliente_veste.json b/backend/Templates-test/salesforce_oauth2_lead_editing_case_cliente_veste.json new file mode 100644 index 0000000..6fc8eab --- /dev/null +++ b/backend/Templates-test/salesforce_oauth2_lead_editing_case_cliente_veste.json @@ -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" + } + } + } + ] +} diff --git a/backend/controllers/crmController.js b/backend/controllers/crmController.js index 325323d..b8f3bc4 100644 --- a/backend/controllers/crmController.js +++ b/backend/controllers/crmController.js @@ -408,11 +408,11 @@ 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.`); } } - + return res.status(StatusCodes.OK).json({ crmTicketLinks }) } @@ -556,42 +556,7 @@ 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; @@ -632,8 +597,7 @@ module.exports = { webhook_crm, sfCreateCase, sfUpdateCase, - createTicket, - associateTicketToCaller, + createTicket, checkTicketByCrmPhone } diff --git a/backend/controllers/transcriptionController.js b/backend/controllers/transcriptionController.js index 6f0a806..ecbff8e 100644 --- a/backend/controllers/transcriptionController.js +++ b/backend/controllers/transcriptionController.js @@ -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 HubspotService = require('../services/hubspotService'); +const { get, del } = require('../utils/redisClient') const hubspotService = new HubspotService(); -exports.receiveTranscription = async (req, res) => { - try { - const { callerId, uniqueId, transcription, recordingUrl } = req.body; +const receiveTranscription = async (req, res) => { + try { - 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 +} \ No newline at end of file diff --git a/backend/routes/crmRoute.js b/backend/routes/crmRoute.js index 3430aeb..1ec78d3 100644 --- a/backend/routes/crmRoute.js +++ b/backend/routes/crmRoute.js @@ -3,8 +3,7 @@ const router = express.Router() 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'); +const { fileUpload } = require("../utils") 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) diff --git a/backend/services/hubspotService.js b/backend/services/hubspotService.js index 340736e..d5189ef 100644 --- a/backend/services/hubspotService.js +++ b/backend/services/hubspotService.js @@ -1,6 +1,9 @@ const axios = require('axios'); -const Logger = console; +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} - 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 + } + } }