crm-api-template-generator/backend/utils/lookupCRMContactByEmail.js

235 lines
10 KiB
JavaScript

const axios = require('axios')
const flatten = require('flat')
const CustomError = require('../errors')
const CRM_Contact = require('../models/CRM_Contact')
const CRM = require('../models/CRM')
const { URL } = require('url')
const { getAccessToken } = require('./oauth2')
const findProperty = require('./findProperty')
const requestConfigHeader = require('./requestConfigHeader')
const sendMessageSocket = require('./sendMessageSocket')
const CRM_Ticket = require('../models/CRM_Ticket')
/**
* @description Realiza a busca de um contato no CRM externo pelo e-mail.
* Se o contato for encontrado, retorna suas informações.
* Também lida com a lógica de cache e de tratamento de duplicatas (ex: Hubspot).
*
* @param {object} rest - Configurações REST do CRM.
* @param {object} authentication - Detalhes de autenticação do CRM.
* @param {string} crmEmail - O endereço de e-mail do contato a ser procurado.
* @param {string} companyId - O ID da empresa.
* @param {object} [test={}] - Objeto para controle de testes/mensagens de socket.
* @param {boolean} [cacheContact=false] - Indica se deve verificar o cache local antes da API.
* @returns {Promise<object>} Um objeto com { exist: boolean, contactId: string, email: string, name: string, accountId: string } ou { exist: false }.
*/
async function lookupContactByEmail(rest, authentication, crmEmail, companyId, test = {}, cacheContact = false) {
// Acessa a configuração específica para busca de contato por e-mail
let { request, body, response } = findProperty(rest, 'lookupContactByEmail')
if (!request || !response) {
console.error('Configuração de "lookupContactByEmail" ausente ou incompleta no CRM REST properties.');
return { exist: false };
}
let { requestContentType, requestEncoding, requestType, url } = request
const { type, userName, passWord, token, crmClientId, crmAccountId } = authentication
// 1. Tenta buscar o contato no cache local (CRM_Contact) pelo e-mail
if (cacheContact) {
const crmInfo = await CRM_Contact.findOne({ companyId, crmBaseURL: new URL(url).hostname, email: crmEmail })
if (crmInfo) {
console.log(`[${new Date()}] Contato encontrado em cache por e-mail: ${crmEmail}`);
return { exist: true, contactId: crmInfo.contactId, email: crmEmail }
}
}
// 2. Prepara a configuração da requisição HTTP para a API do CRM
// A 'requestConfigHeader' precisa ser capaz de lidar com a busca por e-mail
const config = await requestConfigHeader(url, crmEmail, requestType, requestContentType, type, userName, passWord, token, crmClientId, '', '', companyId)
if (test?.testing) {
let msg = `Tentando checar se o contato de e-mail ${crmEmail} existe no crm`
sendMessageSocket({ companyId, status: 'processing', data: { request: config, msg } })
}
console.log("PAYLOAD CONFIG LOOKUP CONTACT BY EMAIL: ", JSON.stringify(config, null, 6))
let data
try {
let { data: _data } = await axios(config)
data = _data
} catch (error) {
if (error.response) {
console.error('==================> lookupContactByEmail Erro na resposta da API:', {
status: error.response.status,
data: error.response.data,
})
}
else if (error.request) {
console.error('==================> lookupContactByEmail Nenhuma resposta recebida da API:', error.request)
}
else {
console.error('==================> lookupContactByEmail Erro ao configurar a request:', error.message)
}
// throw error
}
console.log('CONTACT LOOKUP BY EMAIL DATA: ', JSON.stringify(data, null, 6))
// 3. Lógica específica para Hubspot (ou outros CRMs com tratamento especial de duplicatas)
// Se o CRM for Hubspot e retornar múltiplos contatos, ele tentará encontrar o mais relevante.
if (url.includes("hubapi") && data?.contacts && data.contacts.length > 1) {
// Assume que 'hs_full_name_or_email' pode ser usado para encontrar o contato "principal"
const auxContatWithName = data.contacts.find((c) => c.properties?.hs_full_name_or_email?.value?.trim().length > 2)
if (auxContatWithName) {
console.log(`[${new Date()}] ****** HUBSPOT CONTATO DUPLICADO POR EMAIL. CONTACTID ${auxContatWithName.vid} A SER CONSIDERADO ****** `)
data.contacts = [auxContatWithName] // Considera apenas este contato para o fluxo
// Lógica para limpar duplicatas no CRM_Contact local (se aplicável ao e-mail)
const contacts = await CRM_Contact.find({ companyId, crmBaseURL: new URL(url).hostname, email: crmEmail })
if (contacts && contacts.length > 1) {
for (const contact of contacts.slice(0, -1)) { // Deleta todos, exceto o último/mais relevante
await CRM_Ticket.deleteMany({ companyId, contact: contact })
await contact.deleteOne()
}
}
console.log(`[${new Date()}] dados do contato duplicado no crm (ajustado): `, data)
const updateResult = await CRM_Contact.updateOne(
{ companyId, crmBaseURL: new URL(url).hostname, email: crmEmail }, // Atualiza pelo e-mail
{
$set: { contactId: auxContatWithName.vid }
})
if (updateResult.modifiedCount > 0)
return { exist: true, contactId: auxContatWithName.vid }
}
}
// 4. Achata o objeto de resposta para facilitar a busca por propriedades
data = flatten(data)
let auxEmail // Variável para o e-mail encontrado na resposta
let auxContactId
let auxName
let auxAccountId
// 5. Itera sobre as propriedades do objeto 'data' para extrair e-mail e ID do contato
// Baseado nas configurações de 'response' do seu CRM REST (response.email e response.id)
for (const prop in data) {
const _prop = prop.replace(/^\d+\./, '').replace(/(?:^|\.)\d+\b/g, '')
if (_prop == response?.email?.trim()) { // Procura pelo campo de e-mail na resposta
auxEmail = data[prop]
}
if (_prop == response?.id?.trim()) {
auxContactId = data[prop]
}
if (auxEmail && auxContactId) break // Se encontrou ambos, pode sair do loop
}
// Caso não encontre nas primeiras iterações (alguns CRMs podem ter estruturas aninhadas ou diferentes)
// Esta parte é para casos onde o 'phone' e 'id' não são encontrados de primeira
// (mantendo a lógica do seu lookupContactByPhone, mas adaptada para email)
if (!auxEmail && !auxContactId) { // Se ainda não encontrou e-mail e ID
for (const prop in data) {
let _prop = prop.replace(/\.(\d+)(\.|$)/g, '[$1]$2')
// SALESFORCE GETTING THE NAME
if (_prop == response?.name?.trim()) {
auxName = data[prop]
}
// SALESFORCE GETTING THE ACCOUNT ID
if (_prop == response?.accountId?.trim()) {
auxAccountId = data[prop]
}
if (_prop == response?.email?.trim()) { // Procura novamente pelo campo de e-mail
auxEmail = data[prop]
}
if (_prop == response?.id?.trim()) {
auxContactId = data[prop]
}
// SALESFORCE CASE LOOOK UP ALL THE OBJECT PROPERTIES
// Se não for salesforce, pode sair do loop assim que encontrar email e ID
if (!url.includes('salesforce'))
if (auxEmail && auxContactId) break
}
}
// Tenta pegar o accountId no body da resposta se não conseguir, pega do template
// se a propriedade crmAccountId existir no template. Isso evita problemas com integrações
// já em funcionamento.
if (!auxAccountId && crmAccountId) {
console.log('---------> auxAccountId definido a partir do crmAccountId do template: ', crmAccountId)
auxAccountId = crmAccountId
}
console.log('---------> auxEmail: ', auxEmail, ' | auxContactId: ', auxContactId, ' | auxAccountId: ', auxAccountId)
// 6. Se um e-mail válido for encontrado na resposta da API
if (auxEmail) {
if (auxEmail && auxContactId) {
// Garante apenas 1 contato no seu banco local (CRM_Contact)
const contacts = await CRM_Contact.find({
companyId,
crmBaseURL: new URL(url).hostname,
email: crmEmail, // Busca pelo e-mail original para evitar duplicatas locais
contactId: { $ne: auxContactId } // O ID encontrado na API deve ser o único para este e-mail
})
if (contacts && contacts.length > 0) {
for (const contact of contacts) {
console.log("=====> DELETING DUPLICATE CONTACTS BY EMAIL: ", contact)
await CRM_Ticket.deleteMany({ companyId, contact: contact })
await contact.deleteOne()
}
}
// Atualiza ou cria o contato no seu banco local (CRM_Contact)
const contactInfo = await CRM_Contact.findOne({ companyId, crmBaseURL: new URL(url).hostname, contactId: auxContactId })
console.log('contactInfo (local): ', contactInfo, " | crmEmail: ", crmEmail)
if (!contactInfo) {
console.log('----------------> CREATE CONTACT MONGO (from email lookup)')
const crm = await CRM.findOne({ companyId, crmBaseURL: new URL(url).hostname })
// await CRM_Contact.create({ companyId, crm, crmBaseURL: new URL(url).hostname, contactId: auxContactId, email: auxEmail }) // Armazena o e-mail
} else if (contactInfo.email !== auxEmail) { // Se o contato existe mas o email mudou
console.log('----------------> UPDATING CONTACT EMAIL IN MONGO')
await CRM_Contact.updateOne(
{ companyId, crmBaseURL: new URL(url).hostname, contactId: auxContactId },
{ $set: { email: auxEmail } }
);
}
}
// Retorna que o contato existe e suas informações
return { exist: true, contactId: auxContactId, email: crmEmail, name: auxName, accountId: auxAccountId }
}
// Se nenhum e-mail válido foi encontrado na resposta da API, o contato não existe
return { exist: false }
}
module.exports = lookupContactByEmail