diff --git a/backend/Templates-test/template_crm_sap_gradesco_using_tasks2.json b/backend/Templates-test/template_crm_sap_gradesco_using_tasks2.json new file mode 100644 index 0000000..394b39b --- /dev/null +++ b/backend/Templates-test/template_crm_sap_gradesco_using_tasks2.json @@ -0,0 +1,241 @@ +{ + "authentication": { + "type": "basic", + "userName": "_INTEGRACAO_", + "passWord": "p3G44K10366", + "crmPhoneTest": "+5511998765123" + }, + "crmRest": [ + { + "createContactRecord": { + "request": { + "requestContentType": "application/json", + "requestEncoding": "Json", + "requestType": "Post", + "responseType": "Json", + "url": "https://my365398.crm.ondemand.com/sap/c4c/odata/v1/c4codataapi/ContactCollection" + }, + "body": { + "LastName": "crmLastName", + "FirstName": "crmFirstName", + "Phone": "+crmPhone", + "Mobile": "+crmPhone", + "AccountID": "1000320" + }, + "response": { + "id": "d.results.ContactID", + "objectId": "d.results.ObjectID" + } + } + }, + { + "createContactRecord2": { + "request": { + "requestContentType": "application/json", + "requestEncoding": "Json", + "requestType": "Post", + "responseType": "Json", + "url": "https://my365398.crm.ondemand.com/sap/c4c/odata/v1/c4codataapi/IndividualCustomerCollection" + }, + "body": { + "RoleCode": "CRM000", + "LastName": "crmLastName", + "FirstName": "crmFirstName", + "Phone": "+crmPhone", + "Mobile": "+crmPhone" + }, + "response": { + "id": "d.results.CustomerID", + "objectId": "d.results.ObjectID" + } + } + }, + { + "lookupContactByPhone": { + "request": { + "requestContentType": "application/json", + "requestEncoding": "Json", + "requestType": "Get", + "responseType": "Json", + "url": "https://my365398.crm.ondemand.com/sap/c4c/odata/v1/c4codataapi/ContactCollection?$select=ObjectID,FirstName,LastName,Email,StatusCode,AccountID,StatusCodeText,Phone,Mobile,NormalisedMobile,ContactID&$filter=NormalisedMobile eq '%2BcrmPhone'" + }, + "response": { + "phone": "d.results[0].NormalisedMobile", + "id": "d.results[0].ContactID", + "objectId": "d.results[0].ObjectID", + "accountId": "d.results[0].AccountID" + } + } + }, + { + "lookupContactByPhone2": { + "request": { + "requestContentType": "application/json", + "requestEncoding": "Json", + "requestType": "Get", + "responseType": "Json", + "url": "https://my365398.crm.ondemand.com/sap/c4c/odata/v1/c4codataapi/IndividualCustomerCollection?%24select=ObjectID%2CFirstName%2CLastName%2CEmail%2CPhone%2CMobile%2CNormalisedPhone%2CNormalisedMobile%2CCustomerID&%24filter=NormalisedPhone%20eq%20%27%2BcrmPhone%27" + }, + "response": { + "phone": "d.results[0].NormalisedPhone", + "id": "d.results[0].CustomerID", + "objectId": "d.results[0].ObjectID" + } + } + }, + { + "callJournaling": { + "request": { + "requestContentType": "application/json", + "requestEncoding": "Json", + "requestType": "Post", + "responseType": "Json", + "url": "https://my365398.crm.ondemand.com/sap/c4c/odata/v1/c4codataapi/PhoneCallCollection" + }, + "calls": [ + { + "inboundAnsweredCall": { + "Subject": "Ligação recebida", + "Status": "1", + "InitiatorCode": "2", + "DataOriginTypeCode": "3", + "MainContactPartyID": "crmContactId" + } + }, + { + "outboundAnsweredCall": { + "Subject": "Ligação Realizada", + "Status": "1", + "InitiatorCode": "3", + "DataOriginTypeCode": "3", + "MainContactPartyID": "crmContactId" + } + } + ] + } + }, + { + "chatJournaling": { + "request": { + "requestContentType": "application/json", + "requestEncoding": "Json", + "requestType": "Post", + "responseType": "Json", + "url": "https://my365398.crm.ondemand.com/sap/c4c/odata/v1/c4codataapi/TasksCollection" + }, + "chats": [ + { + "chatDone": { + "Subject": "URL de la conversación en omnihit", + "Status": "1", + "PriorityCode": "3", + "MainContactPartyID": "crmContactId", + "TasksTextCollection": { + "results": [ + { + "Text": "Conversación iniciada por WhatsApp. URL de la conversación de Ominhit: chatLink", + "TypeCode": "10002" + } + ] + } + } + } + ] + } + }, + { + "redirectLink": { + "request": { + "url": "https://my365398.crm.ondemand.com/sap/public/byd/runtime?bo_ns=http%3A%2F%2Fsap.com%2FthingTypes&bo=COD_GENERIC&node=Root&operation=OnExtInspect¶m.Type=COD_CONTACT_TT¶m.InternalID=crmContactId" + } + } + }, + { + "redirectLink2": { + "request": { + "url": "https://my365398.crm.ondemand.com/sap/ap/ui/clogin?saml2=disabled&app.component=%2fSAP_UI_CT%2fMain%2froot.uiccwoc&rootWindow=X&redirectUrl=%2fsap%2fpublic%2fbyd%2fruntime&supressAutoLogon=true&sap-ui-language=en_us#Nav/1/crmTicketId" + } + } + }, + { + "createTicketRecord": { + "request": { + "requestContentType": "application/json", + "requestEncoding": "Json", + "requestType": "Post", + "responseType": "Json", + "url": "https://my365398.crm.ondemand.com/sap/c4c/odata/v1/c4codataapi/ServiceRequestCollection" + }, + "body": { + "Name": "Nova Solicitação de Serviço API (Com Contato) test 09", + "ServicePriorityCode": "3", + "ServiceRequestUserLifeCycleStatusCode": "1", + "DataOriginTypeCode": "3", + "BuyerPartyID": "crmAccountId", + "BuyerMainContactPartyID": "crmContactId", + "ServiceIssueCategoryID": "Z1000" + }, + "response": { + "id": "d.results.ID", + "objectId": "d.results.ObjectID" + } + } + }, + { + "createTicketRecord2": { + "request": { + "requestContentType": "application/json", + "requestEncoding": "Json", + "requestType": "Post", + "responseType": "Json", + "url": "https://my365398.crm.ondemand.com/sap/c4c/odata/v1/c4codataapi/ServiceRequestCollection" + }, + "body": { + "Name": "Nova Solicitação de Serviço API (Com Contato) individual customers 1", + "ServicePriorityCode": "3", + "ServiceRequestUserLifeCycleStatusCode": "1", + "DataOriginTypeCode": "3", + "BuyerPartyID": "crmContactId", + "ReportedForPartyID": "crmContactId", + "ServiceIssueCategoryID": "Z1000" + }, + "response": { + "id": "d.results.ID", + "objectId": "d.results.ObjectID" + } + } + }, + { + "lookupTicket": { + "request": { + "requestContentType": "application/json", + "requestEncoding": "Json", + "requestType": "Get", + "responseType": "Json", + "url": "https://my365398.crm.ondemand.com/sap/c4c/odata/v1/c4codataapi/ServiceRequestCollection?$filter=BuyerMainContactPartyID%20eq%20%crmContactId%27%20and%20ServiceRequestUserLifeCycleStatusCode%20eq%20%271%27" + }, + "response": { + "id": "d.results[0].ID", + "objectId": "d.results[0].ObjectID", + "status": "d.results[0].ServiceRequestUserLifeCycleStatusCodeText" + } + } + }, + { + "lookupTicket2": { + "request": { + "requestContentType": "application/json", + "requestEncoding": "Json", + "requestType": "Get", + "responseType": "Json", + "url": "https://my365398.crm.ondemand.com/sap/c4c/odata/v1/c4codataapi/ServiceRequestCollection?$filter=BuyerPartyID%20eq%20%27crmContactId%27%20and%20ServiceRequestUserLifeCycleStatusCode%20eq%20%271%27" + }, + "response": { + "id": "d.results[0].ID", + "objectId": "d.results[0].ObjectID", + "status": "d.results[0].ServiceRequestUserLifeCycleStatusCodeText" + } + } + } + ] +} diff --git a/backend/controllers/crmController.js b/backend/controllers/crmController.js index 20e8f15..825f430 100644 --- a/backend/controllers/crmController.js +++ b/backend/controllers/crmController.js @@ -32,6 +32,7 @@ const sfcase = require('../utils/sfCase') const sfCaseUpdate = require('../utils/sfCaseUpdate') const removeZeroInicial = require('../utils/removeZeroInicial') const lookupContactByEmail = require('../utils/lookupCRMContactByEmail') +const generateC4CServiceRequestDeepLinkBase64 = require('../utils/generateC4CServiceRequestDeepLinkBase64') const contactCreate = async (req, res) => { @@ -39,11 +40,11 @@ const contactCreate = async (req, res) => { const { companyId, crmFirstName, crmLastName, crmPhone, crmEmail, dynamicBodyRequest } = req.body // mustContainProperties(req, ['companyId', 'crmPhone',]) - + if (!companyId || (!crmPhone && !crmEmail)) { return res.status(StatusCodes.BAD_REQUEST).json({ message: 'companyId e crmPhone ou crmEmail são obrigatórios.' - }); + }) } await createCRMContact(companyId, crmFirstName, crmPhone, crmEmail, crmLastName, dynamicBodyRequest) @@ -72,39 +73,39 @@ const contactCreate = async (req, res) => { // res.status(StatusCodes.OK).json({ exist: false }) // } const checkContact = async (req, res) => { - const { companyId, crmPhone, crmEmail } = req.body; + const { companyId, crmPhone, crmEmail } = req.body if (!companyId || (!crmPhone && !crmEmail)) { return res.status(StatusCodes.BAD_REQUEST).json({ message: 'companyId and either crmPhone or crmEmail are required.' - }); + }) } - const crmFiles = await loadCRM(companyId); + const crmFiles = await loadCRM(companyId) if (crmFiles.length > 0) { - const { crmRest: rest, authentication } = crmFiles[0].crm; - let contact = null; + const { crmRest: rest, authentication } = crmFiles[0].crm + let contact = null if (crmPhone) { - contact = await lookupContactByPhone(rest, authentication, crmPhone, companyId); + contact = await lookupContactByPhone(rest, authentication, crmPhone, companyId) } if (!contact?.exist && crmEmail) { - contact = await lookupContactByEmail(rest, authentication, crmEmail, companyId); + contact = await lookupContactByEmail(rest, authentication, crmEmail, companyId) } if (contact?.exist) { - return res.status(StatusCodes.OK).json({ exist: contact.exist }); + return res.status(StatusCodes.OK).json({ exist: contact.exist }) } } - res.status(StatusCodes.OK).json({ exist: false }); -}; + res.status(StatusCodes.OK).json({ exist: false }) +} // const contactActivity = async (req, res) => { // const { companyId, crmPhone, ticketId } = req.body // mustContainProperties(req, ['companyId', 'crmPhone', 'ticketId']) - + // await whatsappJournalingCRM( // companyId, // crmPhone, @@ -117,16 +118,16 @@ const checkContact = async (req, res) => { // } const contactActivity = async (req, res) => { // 1. Recebe 'crmEmail' do corpo da requisição - const { companyId, crmPhone, crmEmail, ticketId, dynamicBodyRequest } = req.body; + const { companyId, crmPhone, crmEmail, ticketId, dynamicBodyRequest } = req.body // 2. Ajusta a validação para que 'crmPhone' ou 'crmEmail' seja obrigatório // Se nenhum dos dois for fornecido, a requisição é inválida. if (!crmPhone && !crmEmail) { return res.status(StatusCodes.BAD_REQUEST).send({ message: "crmPhone ou crmEmail é obrigatório." - }); + }) } - + // A validação para 'companyId' e 'ticketId' continua, se forem obrigatórios // mustContainProperties(req, ['companyId', 'ticketId']); @@ -139,10 +140,10 @@ const contactActivity = async (req, res) => { ticketId, crmEmail, dynamicBodyRequest - ); + ) - res.status(StatusCodes.OK).send(); -}; + res.status(StatusCodes.OK).send() +} const deleteCrm = async (req, res) => { @@ -191,13 +192,13 @@ const callJournaling = async (req, res) => { // return res.status(StatusCodes.OK).send() // Refactor this in the future. Integração Gradezco colombia, o crm sap precisa criar o contato com o + no inicio seguido do codigo do pais - if(companyId == "4953"){ // companyId da gradezco colombia + if (companyId == "4953") { // companyId da gradezco colombia crmPhone = '57' + crmPhone } - else{ + else { crmPhone = '55' + crmPhone } - + console.log('========> CRMPHONE: ', crmPhone) console.log('========> COMPANY ID before: ', companyId) @@ -223,17 +224,18 @@ const callJournaling = async (req, res) => { // throw new CustomError.BadRequestError(`The crmCallDuration property must be provided when operation is outboundAsweredCall`) - - if (!crmCallDuration || crmCallDuration.trim() == "" || crmCallDuration == "0") crmCallDuration = "10" - console.log("=======================> crmLastName: ", crmLastName) + console.log("=======================> crmLastName: ", crmLastName) // crmLastName - if (operationStatus == "hangup") - await journaling(companyId, operation, crmPhone, crmAgent, crmCallDuration, crmFirstName, crmLastName) + if (operationStatus == "hangup") { + let resp = await journaling(companyId, operation, crmPhone, crmAgent, crmCallDuration, crmFirstName, crmLastName) + return res.status(StatusCodes.OK).json({ ticketLinks: resp }) + } + else if (operationStatus == "update-answer") { @@ -269,27 +271,63 @@ const callJournaling = async (req, res) => { } -const contactLink = async (req, res) => { - const { companyId, contactId } = req.query +const ticketLink = async (req, res) => { + + const { companyId, ticketId } = req.query const crmFiles = await loadCRM(companyId) - + const crmContactIds = [] for (const crmConfig of crmFiles) { - const { crmRest: rest, authentication } = crmConfig.crm + const { crmRest: rest, authentication } = crmConfig.crm + + let redirectLink = findProperty(rest, 'redirectLink2') + + if (redirectLink) { + + const url = redirectLink?.request?.url?.replace("crmTicketId", generateC4CServiceRequestDeepLinkBase64(ticketId)) + + console.log('===============> ticketLink redirect Link: ', url) + + return res.status(StatusCodes.OK).json({ link: url }) + } + + } + + res.status(StatusCodes.NOT_FOUND).json({ message: "Company id not found or not exist redirectLink2 property in template crm" }) + +} + +const contactLink = async (req, res) => { + const { companyId, contactId } = req.query + + // test start + // return res.status(StatusCodes.OK).json({ + // "link": "https://my365398.crm.ondemand.com/sap/ap/ui/clogin?saml2=disabled&app.component=%2fSAP_UI_CT%2fMain%2froot.uiccwoc&rootWindow=X&redirectUrl=%2fsap%2fpublic%2fbyd%2fruntime&supressAutoLogon=true&sap-ui-language=en_us#Nav/1/eyJ0aGluZ3BhcmFtcyI6eyJLZXkiOiJPYm5LZXkkPD94bWwgdmVyc2lvbj1cIjEuMFwiIGVuY29kaW5nPVwidXRmLTE2XCI/PjxPYm5LZXk+PFNvdXJjZT48U291cmNlUGF0aD4vQllEX0NPRC9TZXJ2aWNlT25EZW1hbmQvUHJpdmF0ZUFjY291bnQvQ09EX1NFT0RfSU5EX0FDQ09VTlRfVEkuVEkudWljb21wb25lbnQ8L1NvdXJjZVBhdGg+PC9Tb3VyY2U+PFBhdGg+L1Jvb3QvVGlja2V0TGlzdFswMkMyQUM2NEFDRUMxRkQwQUJCREE2RTAwQTQ0MDQzN10vVVVJRDwvUGF0aD48RGF0YT48VVVJRD4wMkMyQUM2NEFDRUMxRkQwQUJCREE2RTAwQTQ0MDQzNzwvVVVJRD48L0RhdGE+PC9PYm5LZXk+IiwiU291cmNlUGF0aCI6Ii9Sb290L1RpY2tldExpc3RbMDJDMkFDNjRBQ0VDMUZEMEFCQkRBNkUwMEE0NDA0MzddL1VVSUQifSwiaW5Qb3J0IjoiSW5zcGVjdCIsInRhcmdldCI6Ii9CWURfQ09EL1NlcnZpY2VPbkRlbWFuZC9Db2xsYWJvcmF0aW9uL0NPRF9TUl9USS5USS51aWNvbXBvbmVudCIsIndpbklkIjoiYzE1YTIyOWY4ZmQwNGFmNmI0YTJlOTFjYTNlMGZkM2MifQ==" + // }) + // test end + + + const crmFiles = await loadCRM(companyId) + + const crmContactIds = [] + + for (const crmConfig of crmFiles) { + + const { crmRest: rest, authentication } = crmConfig.crm // Send the edited contact/lead link url to hitphone to open on another browser tab let redirectLink = findProperty(rest, 'redirectLink') - if (redirectLink) { + if (redirectLink) { const url = redirectLink?.request?.url?.replace(/crmContactId/g, contactId) console.log('===============> Contact id redirect Link: ', url) - return res.status(StatusCodes.OK).json({ link: url}); + return res.status(StatusCodes.OK).json({ link: url }) // console.log('new URL(url).hostname: ', new URL(url).hostname) @@ -299,7 +337,7 @@ const contactLink = async (req, res) => { } - res.status(StatusCodes.NOT_FOUND).json({ message: "Company id not found!"}); + res.status(StatusCodes.NOT_FOUND).json({ message: "Company id not found!" }) } @@ -358,7 +396,7 @@ const oauthCallBack = async (req, res) => { console.log('xxxxxxxxxx companyId: ', companyId) console.log('xxxxxxxxxx code: ', code) - if (code) { + if (code) { let crmOauth = await CRM.findOne({ 'crm.authentication.crmClientId': clientId, 'companyId': companyId }) @@ -534,6 +572,7 @@ const createTicket = async (req, res) => { .catch(function (error) { console.error(`Error on create ticket: companyID ${companyId} | crmPhone: ${crmPhone}`) + console.error(error) console.error(error?.response?.data) console.error(error?.response?.status) console.error(error?.response?.headers) @@ -689,26 +728,26 @@ const webhook_crm = async (req, res) => { } return res.set('Content-Type', 'text/xml').status(StatusCodes.OK).send(responseXml) -} +} const getClientAccessToken = async (req, res) => { const { companyId } = req.params const { clientId } = req.query - console.log('========> getClientAccessToken companyId: ', companyId, ' clientId: ', clientId); + console.log('========> getClientAccessToken companyId: ', companyId, ' clientId: ', clientId) if (!companyId || !clientId) { - console.error('Company ID or Client ID is missing in the request parameters.'); - return res.status(StatusCodes.BAD_REQUEST).send({ msg: "Company ID and Client ID are required!" }); + console.error('Company ID or Client ID is missing in the request parameters.') + return res.status(StatusCodes.BAD_REQUEST).send({ msg: "Company ID and Client ID are required!" }) } - const accessToken = await getAccessToken(clientId, companyId); + const accessToken = await getAccessToken(clientId, companyId) if (!accessToken) { - console.error(`Access token not found for companyId: ${companyId} and clientId: ${clientId}`); - return res.status(StatusCodes.NOT_FOUND).send({ msg: "Access token not found!" }); + console.error(`Access token not found for companyId: ${companyId} and clientId: ${clientId}`) + return res.status(StatusCodes.NOT_FOUND).send({ msg: "Access token not found!" }) } - return res.status(StatusCodes.OK).json({ accessToken }); + return res.status(StatusCodes.OK).json({ accessToken }) } module.exports = { @@ -729,7 +768,8 @@ module.exports = { checkContact, contactActivity, getClientAccessToken, - contactLink + contactLink, + ticketLink } diff --git a/backend/models/CRM.js b/backend/models/CRM.js index 556ced8..32ef680 100644 --- a/backend/models/CRM.js +++ b/backend/models/CRM.js @@ -77,22 +77,43 @@ const CRMRestSchema = new Schema({ body: Object, response: Object }, + createContactRecord2: { + request: RequestSchema, + body: Object, + response: Object + }, lookupContactByPhone: { request: RequestSchema, response: Object }, + lookupContactByPhone2: { + request: RequestSchema, + response: Object + }, createTicketRecord: { request: RequestSchema, body: Object, response: Object }, + createTicketRecord2: { + request: RequestSchema, + body: Object, + response: Object + }, lookupTicket: { request: RequestSchema, response: Object }, + lookupTicket2: { + request: RequestSchema, + response: Object + }, redirectLink: { request: RedirectUrlSchema }, + redirectLink2: { + request: RedirectUrlSchema + }, callJournaling: CallJournalingSchema, chatJournaling: ChatJournalingSchema, createCase: { diff --git a/backend/models/CRM_Ticket.js b/backend/models/CRM_Ticket.js index a7ad280..dc8f258 100644 --- a/backend/models/CRM_Ticket.js +++ b/backend/models/CRM_Ticket.js @@ -11,17 +11,20 @@ const crmTicketSchema = new Schema({ type: mongoose.Schema.ObjectId, ref: 'crm', required: true - }, + }, ticketId: { type: String, required: true, }, + ticketId2: { + type: String, + }, contact: { type: mongoose.Schema.ObjectId, ref: 'contact', required: true }, -}, { timestamps: true }) +}, { timestamps: true }) const CRM_Ticket = mongoose.model('CRM_Ticket', crmTicketSchema) diff --git a/backend/routes/crmRoute.js b/backend/routes/crmRoute.js index ae2af45..0de4ac8 100644 --- a/backend/routes/crmRoute.js +++ b/backend/routes/crmRoute.js @@ -1,7 +1,7 @@ const express = require('express') const router = express.Router() const { authorization, } = require('../middleware/authentication') -const { contactCreate, contactActivity, checkContact, sfCreateCase, sfUpdateCase, createTicket, testTemplate, webhook_crm, uploadCrmConfig, callJournaling, oauthCallBack, install, deleteCrm, deleteCompany, getCrms, webhook, getClientAccessToken, contactLink } = require('../controllers/crmController') +const { contactCreate, contactActivity, checkContact, sfCreateCase, sfUpdateCase, createTicket, testTemplate, webhook_crm, uploadCrmConfig, callJournaling, oauthCallBack, install, deleteCrm, deleteCompany, getCrms, webhook, getClientAccessToken, contactLink, ticketLink } = require('../controllers/crmController') const { fileUpload } = require("../utils") router.route('/create-contact').post(authorization, contactCreate) @@ -19,6 +19,7 @@ router.route('/salesforce/case').patch(authorization, sfUpdateCase) router.route('/oauth-callback').get(oauthCallBack) router.route('/contact-link').get(authorization, contactLink) +router.route('/ticket-link').get(authorization, ticketLink) router.route('/install').get(install) router.route('/test').post(testTemplate) diff --git a/backend/utils/createContact.js b/backend/utils/createContact.js index c2dcc31..c2d5d2e 100644 --- a/backend/utils/createContact.js +++ b/backend/utils/createContact.js @@ -8,13 +8,27 @@ const CRM = require('../models/CRM') const requestConfigHeader = require('./requestConfigHeader') const sendMessageSocket = require('./sendMessageSocket') -async function createContact(companyId, rest, authentication, crmPhone = '', crmFirstName = 'Username', crmLastName = 'Last name', crmEmail = '', test = {}, dynamicBodyRequest = {}) { - let { request, body, response } = findProperty(rest, 'createContactRecord') - +async function createContact(companyId, rest, authentication, crmPhone = '', crmFirstName = 'Username', crmLastName = 'Last name', crmEmail = '', test = {}, dynamicBodyRequest = {}, flow = 1) { + + let propertyKey + + switch (flow) { + case 2: + propertyKey = 'createContactRecord2' + break + default: + propertyKey = 'createContactRecord' + break + } + + console.log("===============> createContact propertyKey: ", propertyKey) + + let { request, body, response } = findProperty(rest, propertyKey) + const { requestContentType, requestEncoding, requestType, responseType, url } = request // O identificador a ser usado na requisição (prioriza telefone, mas usa e-mail se não houver) - const lookupValue = crmPhone || crmEmail; + const lookupValue = crmPhone || crmEmail body = flatten(body) @@ -25,13 +39,13 @@ async function createContact(companyId, rest, authentication, crmPhone = '', crm crmEmail } - for (const prop in body) { + for (const prop in body) { // Para o crm SAP que precisa do +57 no inicio do contato para ser criado corretamente - if(body[prop].includes("+crmPhone")){ - body[prop] = `+${crmPhone}` + if (body[prop].includes("+crmPhone")) { + body[prop] = `+${crmPhone}` continue - } + } if (mapping.hasOwnProperty(body[prop])) { @@ -49,7 +63,7 @@ async function createContact(companyId, rest, authentication, crmPhone = '', crm } } - body = unflatten(body) + body = unflatten(body) const { type, userName, passWord, token, crmClientId } = authentication @@ -65,7 +79,7 @@ async function createContact(companyId, rest, authentication, crmPhone = '', crm console.log('#####################') console.log('CREATE CONTACT PAYLOAD: ', JSON.stringify(config, null, 6)) - console.log('#####################') + console.log('#####################') if (dynamicBodyRequest && Object.keys(dynamicBodyRequest) !== 0) { config.data = { ...config.data, ...dynamicBodyRequest } @@ -95,8 +109,6 @@ async function createContact(companyId, rest, authentication, crmPhone = '', crm throw error } - // let { data } = await axios(config) - data = flatten(data) let auxContactId @@ -107,7 +119,7 @@ async function createContact(companyId, rest, authentication, crmPhone = '', crm const _prop = prop.replace(/^\d+\./, '').replace(/(?:^|\.)\d+\b/g, '') if (_prop == response?.id?.trim()) { - auxContactId = data[prop] + auxContactId = data[prop] } if (_prop == response?.objectId?.trim()) { @@ -117,14 +129,14 @@ async function createContact(companyId, rest, authentication, crmPhone = '', crm console.log('---------> createContact crmPhone: ', crmPhone, ' | auxContactId: ', auxContactId, ' | auxContactId2: ', auxContactId2) - if (auxContactId && !test?.testing && crmEmail=='') { - + if (auxContactId && !test?.testing && crmEmail == '') { + const crm = await CRM.findOne({ companyId, crmBaseURL: new URL(url).hostname }) // await CRM_Contact.create({ companyId, crm, crmBaseURL: new URL(url).hostname, contactId: auxContactId, phone: crmPhone }) - await CRM_Contact.create({ companyId, crm, crmBaseURL: new URL(url).hostname, contactId: auxContactId, phone: crmPhone }) + await CRM_Contact.create({ companyId, crm, crmBaseURL: new URL(url).hostname, contactId: auxContactId, phone: crmPhone }) } - return { exist: true, contactId: auxContactId, phone: crmPhone, contactId2: auxContactId2} + return { exist: true, contactId: auxContactId, phone: crmPhone, contactId2: auxContactId2, flow: flow } } diff --git a/backend/utils/createTicket.js b/backend/utils/createTicket.js index ad8807e..81dc6df 100644 --- a/backend/utils/createTicket.js +++ b/backend/utils/createTicket.js @@ -8,18 +8,39 @@ const findProperty = require('./findProperty') const CRM = require('../models/CRM') const requestConfigHeader = require('./requestConfigHeader') const sendEventTicketCreatedToSocket = require('./sendEventTicketCreatedToSocket') +const generateC4CServiceRequestDeepLinkBase64 = require('./generateC4CServiceRequestDeepLinkBase64') -async function createTicket(companyId, rest, authentication, crmPhone, crmFirstName = 'Username', crmLastName = 'Last name', crmEmail = '', test = {}, crmContactId, crmAgent) { +async function createTicket(companyId, rest, authentication, crmPhone, crmFirstName = 'Username', crmLastName = 'Last name', crmEmail = '', test = {}, contact, crmAgent) { let ticketUrl = '' - let { request, body, response } = findProperty(rest, 'createTicketRecord') + let crmContactId = contact.contactId + + let flow = contact?.flow + let propertyKey + + switch (flow) { + case 2: + propertyKey = 'createTicketRecord2' + break + default: + propertyKey = 'createTicketRecord' + break + } + + console.log('==============> propertyKey createTicketRecord2: ', propertyKey) + + let { request, body, response } = findProperty(rest, propertyKey) console.log('==============> crmContactId: ', crmContactId) + console.log('==============> crmPhone: ', crmPhone) + console.log("==============> contact?.flow: ", contact?.flow) + const { requestContentType, requestEncoding, requestType, responseType, url } = request console.log('========> body1: ', JSON.stringify(body, null, 6)) + console.log("==========>createTicket contact: ", JSON.stringify(contact, null, 6)) body = flatten(body) @@ -30,7 +51,8 @@ async function createTicket(companyId, rest, authentication, crmPhone, crmFirstN crmLastName, crmPhone, crmEmail, - crmContactId + crmContactId, + crmAccountId: contact?.accountId } for (const prop in body) { @@ -62,11 +84,7 @@ async function createTicket(companyId, rest, authentication, crmPhone, crmFirstN sendMessageSocket({ companyId, status: 'processing', data: { request: config, msg } }) } - // console.log('===============> createTicket: ', JSON.stringify(config, null, 6)) - - - - + console.log('===============> createTicket: ', JSON.stringify(config, null, 6)) let resp @@ -100,6 +118,8 @@ async function createTicket(companyId, rest, authentication, crmPhone, crmFirstN data = flatten(data) let auxTicketId + let auxTicketId2 + for (const prop in data) { @@ -107,24 +127,40 @@ async function createTicket(companyId, rest, authentication, crmPhone, crmFirstN if (_prop == response?.id?.trim()) { auxTicketId = data[prop] - break + } + + if (_prop == response?.objectId?.trim()) { + auxTicketId2 = data[prop] } } + console.log("========> createTicket auxTicketId: ", auxTicketId, " | auxTicketId2: ", auxTicketId2) + if (auxTicketId && !test?.testing) { - const contact = await CRM_Contact.findOne({ companyId, crmBaseURL: new URL(url).hostname, phone: crmPhone }) + + let contact2 = await CRM_Contact.findOne({ companyId, crmBaseURL: new URL(url).hostname, phone: crmPhone }) + + if (!contact2) { + const crm = await CRM.findOne({ companyId, crmBaseURL: new URL(url).hostname }) + contact2 = await CRM_Contact.create({ companyId, crm, crmBaseURL: new URL(url).hostname, contactId: contact.contactId, phone: crmPhone }) + } + const crm = await CRM.findOne({ companyId, crmBaseURL: new URL(url).hostname }) - await CRM_Ticket.create({ companyId, contact, ticketId: auxTicketId, crm }) + await CRM_Ticket.create({ companyId, contact: contact2, ticketId: auxTicketId, ticketId2: auxTicketId2, crm }) - // ticketUrl = `https://app.hubspot.com/contacts/23636141/ticket/${auxTicketId}` - ticketUrl = `https://app.hubspot.com/contacts/${crmAccountId}/ticket/${auxTicketId}` + if (url.includes("hubapi")) { + ticketUrl = `https://app.hubspot.com/contacts/${crmAccountId}/ticket/${auxTicketId}` + } + else if (url.includes("c4codataapi")) { + ticketUrl = `https://my365398.crm.ondemand.com/sap/ap/ui/clogin?saml2=disabled&app.component=%2fSAP_UI_CT%2fMain%2froot.uiccwoc&rootWindow=X&redirectUrl=%2fsap%2fpublic%2fbyd%2fruntime&supressAutoLogon=true&sap-ui-language=en_us#Nav/1/${generateC4CServiceRequestDeepLinkBase64(auxTicketId2)}` + } sendEventTicketCreatedToSocket({ companyId, extension: crmAgent, ticketUrl: ticketUrl }) } - return { exist: true, ticketId: auxTicketId, phone: crmPhone, ticketUrl } + return { exist: true, ticketId: auxTicketId, ticketId2: auxTicketId2, phone: crmPhone, ticketUrl } } diff --git a/backend/utils/generateC4CServiceRequestDeepLinkBase64.js b/backend/utils/generateC4CServiceRequestDeepLinkBase64.js new file mode 100644 index 0000000..6d49e08 --- /dev/null +++ b/backend/utils/generateC4CServiceRequestDeepLinkBase64.js @@ -0,0 +1,34 @@ + +function generateC4CServiceRequestDeepLinkBase64(objectId) { + // 1. Defina o XML Template. + // O UUID deve ser inserido duas vezes neste XML. + const xmlTemplate = `/BYD_COD/SalesOnDemand/Account/UI/COD_Account_TI.TI.uicomponent/Root/ServiceRequests[${objectId}]/UUID${objectId}` + + // 2. Defina o objeto JSON completo (Payload). + const payload = { + "thingparams": { + // O XML deve ser incluído dentro da string 'Key' e escapar as aspas. + "Key": `ObnKey$${xmlTemplate.replace(/"/g, '\\"')}`, + "SourcePath": `/Root/ServiceRequests[${objectId}]/UUID` + }, + "inPort": "Inspect", + "target": "/BYD_COD/ServiceOnDemand/Collaboration/COD_SR_TI.TI.uicomponent", + // Note: O 'winId' é geralmente fixo ou pode ser dinâmico em algumas implementações. + // Estamos usando o valor que você forneceu: + "winId": "b175025e290bfa7a7b5a8c88c3330546" + } + + // 3. Converta o objeto JSON para uma string JSON (sem formatação). + const jsonString = JSON.stringify(payload) + + // 4. Codifique a string JSON para Base64. + // Node.js utiliza Buffer.from(string).toString('base64'). + const base64String = Buffer.from(jsonString, 'utf8').toString('base64') + + return base64String +} + +module.exports = generateC4CServiceRequestDeepLinkBase64 + + + diff --git a/backend/utils/journaling.js b/backend/utils/journaling.js index 34e92a5..f882949 100644 --- a/backend/utils/journaling.js +++ b/backend/utils/journaling.js @@ -4,20 +4,25 @@ const createContact = require('./createContact') const findProperty = require('./findProperty') const journalingRequest = require('./journalingRequest') +const ticketCRM = require('./ticketCRM') -async function journaling(companyId, operation, crmPhone, crmAgent, crmCallDuration = 0, crmFirstName ='Username', crmLastName = 'Last name') { - - const crmFiles = await loadCRM(companyId) +async function journaling(companyId, operation, crmPhone, crmAgent, crmCallDuration = 0, crmFirstName = 'Username', crmLastName = 'Last name') { + + const crmFiles = await loadCRM(companyId) + const crmTicketLinks = [] for (const crmConfig of crmFiles) { const { crmRest: rest, authentication } = crmConfig.crm - let contact = await lookupContactByPhone(rest, authentication, crmPhone, companyId) - + let contact = await lookupContactByPhone(rest, authentication, crmPhone, companyId) + + console.log("===========> journaling contat ", JSON.stringify(contact, null, 6)) + if (!contact.exist) { - contact = await createContact(companyId, rest, authentication, crmPhone, crmFirstName, crmLastName) - } + // createContact(companyId, rest, authentication, crmPhone = '', crmFirstName = 'Username', crmLastName = 'Last name', crmEmail = '', test = {}, dynamicBodyRequest = {}) + contact = await createContact(companyId, rest, authentication, crmPhone, crmFirstName, crmLastName, crmEmail = '', test = {}, dynamicBodyRequest = {}, contact?.flow) + } let { request, calls, response } = findProperty(rest, 'callJournaling') @@ -25,6 +30,29 @@ async function journaling(companyId, operation, crmPhone, crmAgent, crmCallDurat await journalingRequest(request, body, crmCallDuration, contact, crmAgent, crmPhone, authentication, rest, companyId) + // create ticket crm sap cliente gradezco. To each call is created or open a ticket + if (companyId == "4953") { + + console.log("============> journaling contact?.flow: ", contact?.flow) + + const crmTicketLinks = await ticketCRM(companyId, crmPhone, crmAgent, crmFirstName, contact?.flow) + .catch(function (error) { + + console.error(`Error on create jounaling ticket: companyID ${companyId} | crmPhone: ${crmPhone}`) + console.error(error) + console.error(error?.response?.data) + console.error(error?.response?.status) + console.error(error?.response?.headers) + + throw new Error(`Error on create jounaling ticket: companyID ${companyId} | crmPhone: ${crmPhone}`) + }) + + // console.log('jounaling crmTicketLinks: ', crmTicketLinks) + return crmTicketLinks + + } + + return [] } } diff --git a/backend/utils/lookupCRMContactByPhone.js b/backend/utils/lookupCRMContactByPhone.js index 42d7187..f6c4e8d 100644 --- a/backend/utils/lookupCRMContactByPhone.js +++ b/backend/utils/lookupCRMContactByPhone.js @@ -12,12 +12,12 @@ const sendMessageSocket = require('./sendMessageSocket') const CRM_Ticket = require('../models/CRM_Ticket') async function lookupContactByPhone(rest, authentication, crmPhone, companyId, test = {}, cacheContact = false) { - + let { request, body, response } = findProperty(rest, 'lookupContactByPhone') let { requestContentType, requestEncoding, requestType, responseType, url } = request - const { type, userName, passWord, token, crmClientId, crmAccountId} = authentication + const { type, userName, passWord, token, crmClientId, crmAccountId } = authentication if (cacheContact) { const crmInfo = await CRM_Contact.findOne({ companyId, crmBaseURL: new URL(url).hostname, phone: crmPhone }) @@ -35,13 +35,13 @@ async function lookupContactByPhone(rest, authentication, crmPhone, companyId, t console.log("PAYLOAD CONFIG LOOKUP CONTACT BY PHONE CONFIG.url 1: ", config.url) // Para consultar contato crm SAP, necessário consultar sem sinal de + no inicio numero - const pattern = /%2B\+/g; + const pattern = /%2B\+/g - if (pattern.test(config.url)){ - config.url = config.url.replace(/%2B\+/g, '%2B'); - } + if (pattern.test(config.url)) { + config.url = config.url.replace(/%2B\+/g, '%2B') + } - console.log("PAYLOAD CONFIG LOOKUP CONTACT BY PHONE CONFIG.url 2: ", config.url) + console.log("PAYLOAD CONFIG LOOKUP CONTACT BY PHONE CONFIG.url 2: ", config.url) console.log("PAYLOAD CONFIG LOOKUP CONTACT BY PHONE: ", JSON.stringify(config, null, 6)) @@ -50,6 +50,36 @@ async function lookupContactByPhone(rest, authentication, crmPhone, companyId, t try { let { data: _data } = await axios(config) data = _data + + const contactExists = JSON.stringify(data)?.includes(crmPhone) + + console.log("=========> contactExists: ", contactExists) + console.log("=========> data 1: ", JSON.stringify(data, null, 6)) + + const lookupContactByPhone2 = findProperty(rest, 'lookupContactByPhone2') + + if (lookupContactByPhone2?.request?.url && !contactExists) { + const { request: request2, response: response2 } = lookupContactByPhone2 + const { url } = request2 + + config.url = url.replace("crmPhone", crmPhone) + + console.log("==========> lookupContactByPhone2 url: ", url) + + console.log("==========> lookupContactByPhone2 config: ", JSON.stringify(config, null, 6)) + + let { data: _data2 } = await axios(config) + data = _data2 + + data.flow = 2 + + if (response2) { + response = response2 + } + + console.log("=========> data 2: ", JSON.stringify(data, null, 6)) + } + } catch (error) { if (error.response) { console.error('==================> lookupContactByPhone Erro na resposta da API:', { @@ -106,8 +136,6 @@ async function lookupContactByPhone(rest, authentication, crmPhone, companyId, t } - - data = flatten(data) let auxPhone @@ -117,7 +145,7 @@ async function lookupContactByPhone(rest, authentication, crmPhone, companyId, t let auxContactId2 for (const prop in data) { - + const _prop = prop.replace(/^\d+\./, '').replace(/(?:^|\.)\d+\b/g, '') if (_prop == response?.phone?.trim()) { @@ -138,13 +166,12 @@ async function lookupContactByPhone(rest, authentication, crmPhone, companyId, t console.log('===========> !auxPhone && !auxContactId: ', !auxPhone && !auxContactId) - if (!auxPhone && !auxContactId) { for (const prop in data) { let _prop = prop.replace(/\.(\d+)(\.|$)/g, '[$1]$2') - // console.log("_prop: ", _prop, " | response ", JSON.stringify(response, null, 6)) + // console.log("\n_prop: ", _prop, "\n| response: ", JSON.stringify(response, null, 6), "\n| data[prop]: ", data[prop], "\n| _prop: ", _prop) // SALESFORCE GETTING THE NAME if (_prop == response?.name?.trim()) { @@ -175,10 +202,12 @@ async function lookupContactByPhone(rest, authentication, crmPhone, companyId, t } } + console.log("=========> auxAccountId from contact response: ", auxAccountId) + // Tenta pegar o accountId no body da resposta se não conseguir, pega do template se a propriedade crmAccountId existir no template // Foi criado dessa forma para evitar problemas com integrações que já estão em funcionamento - if(!auxAccountId && crmAccountId){ - console.log('---------> auxAccountId definido a partir do crmAccountId do template: ',crmAccountId) + if (!auxAccountId && crmAccountId) { + console.log('---------> auxAccountId definido a partir do crmAccountId do template: ', crmAccountId) auxAccountId = crmAccountId } @@ -195,7 +224,7 @@ async function lookupContactByPhone(rest, authentication, crmPhone, companyId, t phone: crmPhone, contactId: { $ne: auxContactId } }) - + if (contacts && contacts.length > 0) { for (const contact of contacts) { @@ -218,18 +247,14 @@ async function lookupContactByPhone(rest, authentication, crmPhone, companyId, t console.log('----------------> CREATE CONTACT MONGO') const crm = await CRM.findOne({ companyId, crmBaseURL: new URL(url).hostname }) await CRM_Contact.create({ companyId, crm, crmBaseURL: new URL(url).hostname, contactId: auxContactId, phone: auxPhone }) - - } } - return { exist: true, contactId: auxContactId, phone: crmPhone, name: auxName, accountId: auxAccountId, contactId2: auxContactId2} + return { exist: true, contactId: auxContactId, phone: crmPhone, name: auxName, accountId: auxAccountId, contactId2: auxContactId2, flow: data?.flow } } - return { exist: false } + return { exist: false, flow: data?.flow } } module.exports = lookupContactByPhone - - diff --git a/backend/utils/lookupCRMTicket.js b/backend/utils/lookupCRMTicket.js index 3bac1ac..a5927e7 100644 --- a/backend/utils/lookupCRMTicket.js +++ b/backend/utils/lookupCRMTicket.js @@ -17,9 +17,23 @@ const findTicketOpenHubspotByContact = require('./findTicketOpenHubspotByContact const lookupContactByPhone = require('./lookupCRMContactByPhone') -async function lookupCrmTicket(rest, authentication, crmPhone, companyId, test = {}, ticketId) { +async function lookupCrmTicket(rest, authentication, crmPhone, companyId, test = {}, ticketId, contact = null) { - let { request, body, response } = findProperty(rest, 'lookupTicket') + + let flow = contact?.flow + let propertyKey + + switch (flow) { + case 2: + propertyKey = 'lookupTicket2' + break + default: + propertyKey = 'lookupTicket' + break + } + + + let { request, body, response } = findProperty(rest, propertyKey) let { requestContentType, requestEncoding, requestType, responseType, url } = request @@ -33,6 +47,51 @@ async function lookupCrmTicket(rest, authentication, crmPhone, companyId, test = sendMessageSocket({ companyId, status: 'processing', data: { request: config, msg } }) } + + // Para crm sap + if (url.includes("crmContactId") && contact && url.includes("c4codataapi")) { + // Valor do contato + const contactId = contact.contactId // Ex: "1112048" + + console.log("============> entrou no if crmContactId: ", contactId) + + // 1. O valor OData correto, com aspas simples, DEVE SER: '1112048' + const odataValueWithQuotes = `'${contactId}'` + + // 2. Codificação URL do valor OData: '%271112048%27' + const correctEncodedValue = encodeURIComponent(odataValueWithQuotes) + + // 3. Substitua a string incorreta (que contém o '%' extra e as aspas incorretas) pela correta. + // O template parece ser: ...eq%20%crmContactId%27 + const templateToReplace = `%${'crmContactId'}%27` + + // A string FINAL no URL deve ser: ...eq%20%271112048%27 + + // Tentativa de correção: Substitua a string inteira que contém o placeholder errado pela correta + // ATENÇÃO: Se o contactId for '1112048', o template incorreto que está no config.url é: + // `%1112048%27` (depois de rodar a primeira substituição) + // OU `%crmContactId%27` (se a substituição ainda não ocorreu) + + // Vamos substituir a string do template ANTES de qualquer coisa. + // Usaremos 'crmContactId' como marcador de substituição: + + config.url = config.url.replace( + // String que está incorreta no seu template (que gera o erro): "%crmContactId%27" + `%${'crmContactId'}%27`, + + // String correta que o servidor OData espera (Aspas codificadas corretamente): "%27" + + "%27" + correctEncodedValue + ) + } + + if (url.includes("crmContactId") && contact && url.includes("c4codataapi")) { + config.url = config.url.replace('crmContactId', contact.contactId) + } + + console.log("============ journaling contact: ", JSON.stringify(contact, null, 6)) + + console.log("PAYLOAD CONFIG LOOKUP TICKET BY PHONE: ", JSON.stringify(config, null, 6)) + let resp try { @@ -46,7 +105,7 @@ async function lookupCrmTicket(rest, authentication, crmPhone, companyId, test = if (error.response) { console.error('==================> lookupCrmTicket Erro na resposta da API:', { status: error.response.status, - data: error.response.data, + data: JSON.stringify(error.response.data, null, 6), }) } else if (error.request) { @@ -66,6 +125,8 @@ async function lookupCrmTicket(rest, authentication, crmPhone, companyId, test = let auxTicketStatus let auxTicketId + let auxTicketId2 + for (const prop in data) { @@ -79,7 +140,11 @@ async function lookupCrmTicket(rest, authentication, crmPhone, companyId, test = auxTicketId = data[prop] } - if (auxTicketStatus && auxTicketId) break + if (_prop == response?.objectId?.trim()) { + auxTicketId2 = data[prop] + } + + // if (auxTicketStatus && auxTicketId) break } @@ -96,20 +161,24 @@ async function lookupCrmTicket(rest, authentication, crmPhone, companyId, test = auxTicketId = data[prop] } - if (auxTicketStatus && auxTicketId) break + if (_prop == response?.objectId?.trim()) { + auxTicketId2 = data[prop] + } + + // if (auxTicketStatus && auxTicketId) break } } - console.log(`[${new Date()}] auxTicketStatus: ${auxTicketStatus} | auxTicketId: ${auxTicketId}`) + console.log(`[${new Date()}] auxTicketStatus: ${auxTicketStatus} | auxTicketId: ${auxTicketId} | auxTicketId2: ${auxTicketId2}`) if (url.includes('api.hubapi.com')) { - + const contact = await lookupContactByPhone(rest, authentication, crmPhone, companyId) if (contact) { const ticket = await findTicketOpenHubspotByContact(authentication, contact) - console.log('=========> OPEN TICKET 1: ', JSON.stringify(ticket, null, 6)) + console.log('=========> OPEN TICKET 1: ', JSON.stringify(ticket, null, 6)) if (ticket && Object.keys(ticket).length != 0) { console.log('**** TICKET ESTA ABERTO 1 ****') @@ -117,12 +186,20 @@ async function lookupCrmTicket(rest, authentication, crmPhone, companyId, test = } } - - return { auxTicketId, auxTicketStatus } - - } - return { auxTicketId, auxTicketStatus } + return { auxTicketId, auxTicketStatus } + + } + // To sap crm, consdering status open == 1 + else if (url.includes("c4codataapi")) { + if (auxTicketStatus && auxTicketStatus.toLowerCase() == "open") + auxTicketStatus = 1 + else + auxTicketStatus = 0 + } + + + return { auxTicketId, auxTicketStatus, auxTicketId2 } } diff --git a/backend/utils/redirectContactLinkCRM.js b/backend/utils/redirectContactLinkCRM.js index f53e76e..60af3be 100644 --- a/backend/utils/redirectContactLinkCRM.js +++ b/backend/utils/redirectContactLinkCRM.js @@ -5,7 +5,6 @@ const findProperty = require('./findProperty') const CRM_Contact = require('../models/CRM_Contact') const CRM_Ticket = require('../models/CRM_Ticket') const CRM = require('../models/CRM') -const createTicket = require('./createTicket') const lookupCRMTicket = require('./lookupCRMTicket') const sendEventTicketCreatedToSocket = require('./sendEventTicketCreatedToSocket') @@ -20,7 +19,7 @@ async function redirectContactLinkCRM(companyId, crmPhone, crmAgent, crmFirstNam for (const crmConfig of crmFiles) { - const { crmRest: rest, authentication } = crmConfig.crm + const { crmRest: rest, authentication } = crmConfig.crm // Send the edited contact/lead link url to hitphone to open on another browser tab let redirectLink = findProperty(rest, 'redirectLink') @@ -67,4 +66,4 @@ async function _lookupContact(rest, authentication, crmPhone, companyId, crmFirs return { created: true, contactId: contact.contactId } } - + diff --git a/backend/utils/ticketCRM.js b/backend/utils/ticketCRM.js index d33107b..ceb74ae 100644 --- a/backend/utils/ticketCRM.js +++ b/backend/utils/ticketCRM.js @@ -16,8 +16,9 @@ const getHubspotPipelines = require('./getHubspotPipelines') const mongoose = require('mongoose') const getHubspotTicketStatusByContact = require('./getHubspotTicketStatusByContact') const findTicketOpenHubspotByContact = require('./findTicketOpenHubspotByContact') +const generateC4CServiceRequestDeepLinkBase64 = require('./generateC4CServiceRequestDeepLinkBase64') -async function ticketCRM(companyId, crmPhone, crmAgent = "0000", crmFirstName = 'Username') { +async function ticketCRM(companyId, crmPhone, crmAgent = "0000", crmFirstName = 'Username', flow = 1) { const crmFiles = await loadCRM(companyId) @@ -26,40 +27,65 @@ async function ticketCRM(companyId, crmPhone, crmAgent = "0000", crmFirstName = for (const crmConfig of crmFiles) { - const { crmRest: rest, authentication } = crmConfig.crm + const { crmRest: rest, authentication } = crmConfig.crm - // Send the ticket url link to hitphone to open on another browser tab - let obj = findProperty(rest, 'lookupTicket') + let propertyKey + + switch (flow) { + case 2: + propertyKey = 'lookupTicket2' + break + default: + propertyKey = 'lookupTicket' + break + } + + // find ticket + let obj = findProperty(rest, propertyKey) let ticket_id = '' + let ticket_id2 = '' + let ticket_url = '' let isCreated = false + console.log("===========> ticketCRM obj: ", obj) + if (obj) { const { crmAccountId } = authentication - console.log("------------> ticketCRM crmAccountId: ",crmAccountId) + console.log("------------> ticketCRM crmAccountId: ", crmAccountId) let { url } = obj.request + console.log("------------> ticketCRM url: ", url) let contact = await _lookupContact(rest, authentication, crmPhone, companyId, crmFirstName, url) + console.log("------------> ticketCRM contact: ", JSON.stringify(contact, null, 6)) + const crm = await CRM.findOne({ companyId, crmBaseURL: new URL(url.trim()).hostname }) const obj_contact = await CRM_Contact.findOne({ companyId, crmBaseURL: new URL(url).hostname, phone: crmPhone }) + console.log("------------> ticketCRM obj_contact: ", JSON.stringify(obj_contact, null, 6)) + const obj_ticket = await CRM_Ticket.findOne( { companyId, crm, contact: obj_contact } - ).select('ticketId') + ).select('ticketId ticketId2') + + console.log("------------> ticketCRM obj_ticket: ", JSON.stringify(obj_ticket, null, 6)) if (obj_ticket) { - const { ticketId } = obj_ticket + const { ticketId, ticketId2 } = obj_ticket if (ticketId) { ticket_id = ticketId } + if (ticketId2) { + ticket_id2 = ticketId2 + } const obj_ticket_status = await lookupCRMTicket( rest, @@ -67,15 +93,19 @@ async function ticketCRM(companyId, crmPhone, crmAgent = "0000", crmFirstName = crmPhone, companyId, test = { testing: false }, - obj_ticket.ticketId + obj_ticket.ticketId, + contact ) - if (obj_ticket_status) console.log("obj_ticket_status: ", JSON.stringify(obj_ticket_status, null, 6)) + if (obj_ticket_status) { const { auxTicketStatus, error } = obj_ticket_status + console.log("===============> ticketCRM url: ", url, " | error: ", error, " | auxTicketStatus: ", auxTicketStatus) + + // refactor this for production. For now only working with hubspot where status new is equal 1 if ((auxTicketStatus && auxTicketStatus != '1') || (error && error == 404)) { @@ -105,9 +135,10 @@ async function ticketCRM(companyId, crmPhone, crmAgent = "0000", crmFirstName = } - const { ticketUrl, ticketId } = await _createTicket(rest, crmPhone, companyId, authentication, crmFirstName, contact, crmAgent) + const { ticketUrl, ticketId, ticketId2 } = await _createTicket(rest, crmPhone, companyId, authentication, crmFirstName, contact, crmAgent, flow) ticket_id = ticketId + ticket_id2 = ticketId2 isCreated = true // crmTicketLinks.push({ ticketUrl, ticketId }) @@ -117,7 +148,6 @@ async function ticketCRM(companyId, crmPhone, crmAgent = "0000", crmFirstName = } else { - if (url.includes("hubapi")) { console.log(`[${new Date()}] ****** IS HUBSPOT CRM LETS TRY FINDING AN OPEN TICKET TO THE CONTACT****** `) @@ -141,20 +171,77 @@ async function ticketCRM(companyId, crmPhone, crmAgent = "0000", crmFirstName = continue } } + // To sap crm try to find ticket open + else if (url.includes("c4codataapi")) { + const result = await lookupCRMTicket( + rest, + authentication, + crmPhone, + companyId, + test = { testing: false }, + obj_ticket?.ticketId, + contact + ) + // elefante + const { auxTicketId, auxTicketStatus, auxTicketId2 } = result + + console.log("============> TICKET STATUS: ", auxTicketStatus, " | auxTicketId: ", auxTicketId) + + if (auxTicketStatus == 1 && auxTicketId2) { + + ticket_id = auxTicketId + ticket_id2 = auxTicketId2 + + isCreated = false + + const _ticketUrl = `https://my365398.crm.ondemand.com/sap/ap/ui/clogin?saml2=disabled&app.component=%2fSAP_UI_CT%2fMain%2froot.uiccwoc&rootWindow=X&redirectUrl=%2fsap%2fpublic%2fbyd%2fruntime&supressAutoLogon=true&sap-ui-language=en_us#Nav/1/${generateC4CServiceRequestDeepLinkBase64(auxTicketId2)}` + + crmTicketLinks.push({ + ticketId: auxTicketId2, + ticketUrl: _ticketUrl, + created: isCreated, + }) + + // sinc ticket + let __contact = await CRM_Contact.findOne({ companyId, crmBaseURL: new URL(url).hostname, phone: crmPhone }) + + await CRM_Ticket.deleteMany({ companyId, contact: __contact }) + + const crm = await CRM.findOne({ companyId, crmBaseURL: new URL(url).hostname }) + + await CRM_Ticket.create({ companyId, contact: __contact, ticketId: auxTicketId, ticketId2: auxTicketId2, crm }) + + continue + } + } console.log(`[${new Date()}] ****** CREATE TICKET ****** `) - - const { ticketUrl, ticketId } = await _createTicket(rest, crmPhone, companyId, authentication, crmFirstName, contact, crmAgent) + const { ticketUrl, ticketId, ticketId2 } = await _createTicket(rest, crmPhone, companyId, authentication, crmFirstName, contact, crmAgent, flow) ticket_id = ticketId + ticket_id2 = ticketId2 + // ticket_url = ticketUrl isCreated = true } - crmTicketLinks.push({ - ticketId: `https://app.hubspot.com/contacts/${crmAccountId}/ticket/${ticket_id}`, - created: isCreated, - }) + if (url.includes("hubapi")) { + crmTicketLinks.push({ + ticketId: `https://app.hubspot.com/contacts/${crmAccountId}/ticket/${ticket_id}`, + created: isCreated, + }) + } + // To sap crm + else if (url.includes("c4codataapi")) { + console.log("=================> c4codataapi ticket_id2: ", ticket_id2) + + crmTicketLinks.push({ + ticketId: ticket_id2, + ticketUrl: `https://my365398.crm.ondemand.com/sap/ap/ui/clogin?saml2=disabled&app.component=%2fSAP_UI_CT%2fMain%2froot.uiccwoc&rootWindow=X&redirectUrl=%2fsap%2fpublic%2fbyd%2fruntime&supressAutoLogon=true&sap-ui-language=en_us#Nav/1/${generateC4CServiceRequestDeepLinkBase64(ticket_id2)}`, + created: isCreated, + }) + + } } // @@ -213,7 +300,7 @@ async function _lookupContact(rest, authentication, crmPhone, companyId, crmFirs let contact = await lookupContactByPhone(rest, authentication, crmPhone, companyId) if (contact?.exist) { - return { created: false, contactId: contact.contactId } + return { created: false, contactId: contact.contactId, contactId2: contact.contactId2, accountId: contact?.accountId, flow: contact?.flow } } if (!contact?.exist) { @@ -231,11 +318,23 @@ async function _lookupContact(rest, authentication, crmPhone, companyId, crmFirs contact = await createContact(companyId, rest, authentication, crmPhone, crmFirstName) } - return { created: true, contactId: contact.contactId } + return { created: true, contactId: contact.contactId, contactId2: contact.contactId2, accountId: contact?.accountId, flow: contact?.flow } } -async function _createTicket(rest, crmPhone, companyId, authentication, crmFirstName, contact, crmAgent) { - let obj = findProperty(rest, 'createTicketRecord') +async function _createTicket(rest, crmPhone, companyId, authentication, crmFirstName, contact, crmAgent, flow = 1) { + + let propertyKey + + switch (flow) { + case 2: + propertyKey = 'createTicketRecord2' + break + default: + propertyKey = 'createTicketRecord' + break + } + + let obj = findProperty(rest, propertyKey) let ticket_url = '' let ticket_id = '' @@ -245,10 +344,10 @@ async function _createTicket(rest, crmPhone, companyId, authentication, crmFirst if (request) { msg = `Tentando criar ticket para o contato ${crmPhone}` - // return { exist: true, ticketId: auxTicketId, phone: crmPhone, ticketUrl } + // return { exist: true, ticketId: auxTicketId, phone: crmPhone, ticketUrl } // return { exist: true, ticketId: auxTicketId, phone: crmPhone, ticketUrl } - const { ticketUrl, ticketId, } = await createTicket(companyId, + const { ticketUrl, ticketId, ticketId2 } = await createTicket(companyId, rest, authentication, crmPhone, @@ -256,14 +355,15 @@ async function _createTicket(rest, crmPhone, companyId, authentication, crmFirst crmLastName = '', crmEmail = '', test = { testing: false }, - crmContactId = contact.contactId, + contact, crmAgent ) ticket_id = ticketId + ticket_id2 = ticketId2 ticket_url = ticketUrl } } - return { crmFirstName, ticketUrl: ticket_url, ticketId: ticket_id } + return { crmFirstName, ticketUrl: ticket_url, ticketId: ticket_id, ticketId2: ticket_id2 } } diff --git a/backend/utils/whatsappJournalingCRM.js b/backend/utils/whatsappJournalingCRM.js index 39acce6..3699420 100644 --- a/backend/utils/whatsappJournalingCRM.js +++ b/backend/utils/whatsappJournalingCRM.js @@ -6,12 +6,11 @@ const findProperty = require('./findProperty') const CRM_Contact = require('../models/CRM_Contact') const CRM_Ticket = require('../models/CRM_Ticket') const CRM = require('../models/CRM') -const createTicket = require('./createTicket') const lookupCRMTicket = require('./lookupCRMTicket') const sendEventTicketCreatedToSocket = require('./sendEventTicketCreatedToSocket') const journalingRequest = require('./journalingRequest') - + const getIntegrationsConfig = require('../utils/getIntegrationsConfig') const updateLeadStatus = require('./updateLeadStatus') const extractLeadStatusChange = require('./extractLeadStatusChange') @@ -21,7 +20,7 @@ async function whatsappJournalingCRM(companyId, crmPhone = '', crmAgent, crmFirs const crmFiles = await loadCRM(companyId) - const crmContactIds = [] + const crmContactIds = [] for (const crmConfig of crmFiles) { @@ -31,11 +30,11 @@ async function whatsappJournalingCRM(companyId, crmPhone = '', crmAgent, crmFirs let chatJournaling = findProperty(rest, 'chatJournaling') console.log('===============> chatJournaling: ', JSON.stringify(chatJournaling, null, 6)) - + if (chatJournaling) { - let contact = await _lookupContact(rest, authentication, crmPhone, crmEmail, companyId, crmFirstName) + let contact = await _lookupContact(rest, authentication, crmPhone, crmEmail, companyId, crmFirstName) console.log("================> whatsappJournalingCRM contact: ", JSON.stringify(contact, null, 6)) @@ -44,7 +43,7 @@ async function whatsappJournalingCRM(companyId, crmPhone = '', crmAgent, crmFirs let body = findProperty(chats, 'chatDone') - let config = await getIntegrationsConfig(companyId, 'omnihit') + let config = await getIntegrationsConfig(companyId, 'omnihit') if (ticketId && config) { @@ -62,7 +61,7 @@ async function whatsappJournalingCRM(companyId, crmPhone = '', crmAgent, crmFirs body = JSON.parse(body) console.log('===============> body3: ', JSON.stringify(body, null, 6)) } - + await journalingRequest(request, body, crmCallDuration = 0, contact, crmAgent, crmPhone, authentication, rest, companyId, dynamicBodyRequest) // Se vier um novo status (__newLeadStatus), executa PATCH aproveitando as validações do fluxo de Activity @@ -106,42 +105,46 @@ module.exports = whatsappJournalingCRM // } async function _lookupContact(rest, authentication, crmPhone, crmEmail, companyId, crmFirstName) { - let contact = null; + let contact = null // 1. Tenta buscar o contato por telefone se o crmPhone for fornecido if (crmPhone) { - console.log(`Buscando contato por telefone: ${crmPhone}`); - contact = await lookupContactByPhone(rest, authentication, crmPhone, companyId); + console.log(`Buscando contato por telefone: ${crmPhone}`) + contact = await lookupContactByPhone(rest, authentication, crmPhone, companyId) } // 2. Se não encontrou por telefone, tenta buscar por e-mail se o crmEmail for fornecido if (!contact?.exist && crmEmail) { - console.log(`Contato não encontrado por telefone. Tentando por e-mail: ${crmEmail}`); - contact = await lookupContactByEmail(rest, authentication, crmEmail, companyId); + console.log(`Contato não encontrado por telefone. Tentando por e-mail: ${crmEmail}`) + contact = await lookupContactByEmail(rest, authentication, crmEmail, companyId) } // 3. Se o contato já existe (seja por telefone ou e-mail), retorna as informações if (contact?.exist) { - console.log(`Contato encontrado! ID: ${contact.contactId}`); - return { created: false, contactId: contact.contactId, contactId2: contact?.contactId2 }; + console.log(`Contato encontrado! ID: ${contact.contactId}`) + return { created: false, contactId: contact.contactId, contactId2: contact?.contactId2 } } // 4. Se o contato não foi encontrado por nenhuma das formas, cria um novo - console.log(`Contato não encontrado. Criando um novo com base no ${crmPhone ? 'telefone' : 'e-mail'}.`); - + console.log(`Contato não encontrado. Criando um novo com base no ${crmPhone ? 'telefone' : 'e-mail'}.`) + // AQUI ESTÁ A ÚNICA MUDANÇA NECESSÁRIA: // Chamamos createContact passando os valores de telefone e e-mail separadamente. // A função createContact agora sabe como lidar com ambos, mesmo que um deles seja vazio. - contact = await createContact(companyId, rest, authentication, crmPhone, crmFirstName, 'Last name', crmEmail); - + // createContact(companyId, rest, authentication, crmPhone = '', crmFirstName = 'Username', crmLastName = 'Last name', crmEmail = '', test = {}, dynamicBodyRequest = {}, flow = 1) + + console.log("=================> whatsappJournalingCRM contact?.flow: ", contact?.flow) + + contact = await createContact(companyId, rest, authentication, crmPhone, crmFirstName, 'Last name', crmEmail, test = {}, dynamicBodyRequest = {}, flow = contact?.flow) + // Se o contato for criado, retorna suas informações if (contact?.contactId) { - console.log(`Novo contato criado com ID: ${contact.contactId}`); - return { created: true, contactId: contact.contactId, contactId2: contact?.contactId2}; + console.log(`Novo contato criado com ID: ${contact.contactId}`) + return { created: true, contactId: contact.contactId, contactId2: contact?.contactId2 } } // Caso não tenha nem telefone nem e-mail, retorna um erro ou um valor nulo - console.error("Erro: Não foi possível encontrar ou criar um contato. Telefone e e-mail não fornecidos."); - return null; + console.error("Erro: Não foi possível encontrar ou criar um contato. Telefone e e-mail não fornecidos.") + return null }