feat: add support for ticket creation

master
adriano 2024-07-19 15:48:34 -03:00
parent 8f9afd5f23
commit 7af9e34ad7
12 changed files with 457 additions and 68 deletions

View File

@ -1,55 +1,55 @@
{
"authentication":{
"authentication": {
"type": "bearer",
"token": "pat-na1-7aca13dd-9ba5-48db-bf35-570844d31abb",
"crmPhoneTest": "5511988334455"
"token": "pat-na1-37da6668-e0b1-44cb-bd2d-596f5f65634a",
"crmPhoneTest": "5514987659932"
},
"crmRest":[
"crmRest": [
{
"createContactRecord":{
"request":{
"requestContentType":"application/json",
"requestEncoding":"Json",
"requestType":"Post",
"responseType":"Json",
"url":"https://api.hubapi.com/contacts/v1/contact"
"createContactRecord": {
"request": {
"requestContentType": "application/json",
"requestEncoding": "Json",
"requestType": "Post",
"responseType": "Json",
"url": "https://api.hubapi.com/contacts/v1/contact"
},
"body":{
"properties":[
"body": {
"properties": [
{
"property":"phone",
"value":"crmPhone"
"property": "phone",
"value": "crmPhone"
}
]
},
"response":{
"id":"vid"
"response": {
"id": "vid"
}
}
},
{
"lookupContactByPhone":{
"request":{
"requestContentType":"application/json",
"requestEncoding":"Json",
"requestType":"Get",
"responseType":"Json",
"url":"https://api.hubapi.com/contacts/v1/search/query?q=crmPhone"
"lookupContactByPhone": {
"request": {
"requestContentType": "application/json",
"requestEncoding": "Json",
"requestType": "Get",
"responseType": "Json",
"url": "https://api.hubapi.com/contacts/v1/search/query?q=crmPhone"
},
"response":{
"phone":"contacts.properties.phone.value",
"id":"contacts.vid"
"response": {
"phone": "contacts.properties.phone.value",
"id": "contacts.vid"
}
}
},
{
"callJournaling":{
"request":{
"requestContentType":"application/json",
"requestEncoding":"Json",
"requestType":"Post",
"responseType":"Json",
"url":"https://api.hubapi.com/engagements/v1/engagements"
"callJournaling": {
"request": {
"requestContentType": "application/json",
"requestEncoding": "Json",
"requestType": "Post",
"responseType": "Json",
"url": "https://api.hubapi.com/engagements/v1/engagements"
},
"calls": [
{
@ -179,8 +179,56 @@
}
}
],
"response":{
"response": {}
}
},
{
"createTicketRecord": {
"request": {
"requestContentType": "application/json",
"requestEncoding": "Json",
"requestType": "Post",
"responseType": "Json",
"url": "https://api.hubapi.com/crm/v3/objects/tickets"
},
"body": {
"properties": {
"hs_pipeline": "0",
"hs_pipeline_stage": "1",
"hs_ticket_priority": "HIGH",
"subject": "Teste"
},
"associations": [
{
"to": {
"id": "crmContactId"
},
"types": [
{
"associationCategory": "HUBSPOT_DEFINED",
"associationTypeId": 16
}
]
}
]
},
"response": {
"id": "id"
}
}
},
{
"lookupTicket": {
"request": {
"requestContentType": "application/json",
"requestEncoding": "Json",
"requestType": "Get",
"responseType": "Json",
"url": "https://api.hubapi.com/crm/v3/objects/tickets/ticketId"
},
"response": {
"status": "properties.hs_pipeline_stage",
"id": "id"
}
}
}

View File

@ -5,6 +5,7 @@ const { createCRMContact,
sendMessageSocket,
mustContainProperties,
journaling,
ticketCRM,
findProperty,
journalingRequest,
createContact,
@ -60,7 +61,9 @@ const deleteCompany = async (req, res) => {
const callJournaling = async (req, res) => {
const { companyId, operation, crmPhone, crmAgent, crmCallDuration, crmFirstName } = req.body
const { companyId, operation, crmPhone, crmAgent, crmCallDuration, crmFirstName, operationStatus } = req.body
console.log('REQ.BODY CRM TEST: ', JSON.stringify(req.body, null, 6))
mustContainProperties(req, ['companyId', 'operation', 'crmPhone', 'crmAgent',])
@ -69,7 +72,11 @@ const callJournaling = async (req, res) => {
// if (operation == 'outboundAsweredCall' && !crmCallDuration)
// throw new CustomError.BadRequestError(`The crmCallDuration property must be provided when operation is outboundAsweredCall`)
if (operationStatus == "hangup")
await journaling(companyId, operation, crmPhone, crmAgent, crmCallDuration, crmFirstName)
else if (operationStatus == "answered") {
await ticketCRM(companyId, crmPhone, crmAgent, crmFirstName)
}
res.status(StatusCodes.OK).send()
}

View File

@ -63,6 +63,15 @@ const CRMRestSchema = new Schema({
request: RequestSchema,
response: Object
},
createTicketRecord: {
request: RequestSchema,
body: Object,
response: Object
},
lookupTicket: {
request: RequestSchema,
response: Object
},
callJournaling: CallJournalingSchema
})
@ -125,6 +134,7 @@ crmContactSchema.virtual('crm_contacts', {
crmContactSchema.pre('deleteOne', { document: true, query: false }, async function () {
await this.model('CRM_Contact').deleteMany({ crm: this._id })
// await this.model('CRM_Ticket').deleteMany({ crm: this._id })
}
)

View File

@ -26,6 +26,21 @@ const crmContactSchema = new Schema({
}
}, { timestamps: true })
crmContactSchema.virtual('crm_tickets', {
ref: 'CRM_Ticket',
localField: '_id',
foreignField: 'contact',
justOne: false,
// match: { rating: 4 }
})
crmContactSchema.pre('deleteOne', { document: true, query: false }, async function () {
await this.model('CRM_Ticket').deleteMany({ contact: this._id })
}
)
const CRM_Contact = mongoose.model('CRM_Contact', crmContactSchema)
module.exports = CRM_Contact

View File

@ -0,0 +1,29 @@
const mongoose = require('../db/connect')
const { Schema } = mongoose
const crmTicketSchema = new Schema({
companyId: {
type: String,
required: true,
},
crm: {
type: mongoose.Schema.ObjectId,
ref: 'crm',
required: true
},
ticketId: {
type: String,
required: true,
},
contact: {
type: mongoose.Schema.ObjectId,
ref: 'contact',
required: true
},
}, { timestamps: true })
const CRM_Ticket = mongoose.model('CRM_Ticket', crmTicketSchema)
module.exports = CRM_Ticket

View File

@ -0,0 +1,83 @@
const flatten = require('flat')
const unflatten = require('flat').unflatten
const axios = require('axios')
const CRM_Contact = require('../models/CRM_Contact')
const CRM_Ticket = require('../models/CRM_Ticket')
const { URL } = require('url')
const findProperty = require('./findProperty')
const CRM = require('../models/CRM')
const requestConfigHeader = require('./requestConfigHeader')
const sendMessageSocket = require('./sendMessageSocket')
async function createTicket(companyId, rest, authentication, crmPhone, crmFirstName = 'unnamed', crmLastName = 'no surname', crmEmail = '', test = {}, crmContactId) {
let { request, body, response } = findProperty(rest, 'createTicketRecord')
const { requestContentType, requestEncoding, requestType, responseType, url } = request
body = flatten(body)
const mapping = {
crmFirstName,
crmLastName,
crmPhone,
crmEmail,
crmContactId
}
for (const prop in body) {
if (mapping.hasOwnProperty(body[prop])) {
const variable = mapping[body[prop]]
if (variable) {
body[prop] = variable
} else {
if (body[prop] == 'crmLastName' && !crmLastName) {
body[prop] = 'unnamed'
}
else
delete body[prop]
}
}
}
body = unflatten(body)
const { type, userName, passWord, token, crmClientId } = authentication
//url, crmPhone, requestType, requestContentType, type, userName, passWord, token, crmClientId, data = ''
const config = await requestConfigHeader(url, crmPhone, requestType, requestContentType, type, userName, passWord, token, crmClientId, body)
if (test?.testing) {
msg = `Tentanto criar ticket do numero ${crmPhone} no crm`
sendMessageSocket({ companyId, status: 'processing', data: { request: config, msg } })
}
let { data } = await axios(config)
data = flatten(data)
let auxTicketId
for (const prop in data) {
const _prop = prop.replace(/^\d+\./, '').replace(/(?:^|\.)\d+\b/g, '')
if (_prop == response?.id?.trim()) {
auxTicketId = data[prop]
break
}
}
if (auxTicketId && !test?.testing) {
const contact = await CRM_Contact.findOne({ companyId, crmBaseURL: new URL(url).hostname, phone: crmPhone })
const crm = await CRM.findOne({ companyId, crmBaseURL: new URL(url).hostname })
await CRM_Ticket.create({ companyId, contact, ticketId: auxTicketId, crm})
}
return { exist: true, ticketId: auxTicketId, phone: crmPhone }
}
module.exports = createTicket

View File

@ -5,6 +5,8 @@ function findProperty(array, property) {
return item[property] !== undefined
})
if (index == -1) return
return array[index][property]
}

View File

@ -24,6 +24,8 @@ const lookupContactByPhone = require('./lookupCRMContactByPhone')
const socketIO = require('./socketIO')
const sendMessageSocket = require('./sendMessageSocket')
const templateValidator = require('./templateValidator')
const ticketCRM = require('./ticketCRM')
module.exports = {
fileUpload,
@ -46,5 +48,6 @@ module.exports = {
lookupContactByPhone,
socketIO,
sendMessageSocket,
templateValidator
templateValidator,
ticketCRM
}

View File

@ -32,6 +32,8 @@ async function lookupContactByPhone(rest, authentication, crmPhone, companyId, t
let { data } = await axios(config)
console.log('DATA: ', JSON.stringify(data, null, 6))
data = flatten(data)
let auxPhone
@ -81,7 +83,7 @@ async function lookupContactByPhone(rest, authentication, crmPhone, companyId, t
return { exist: true, contactId: auxContactId, phone: crmPhone }
}
return { exit: false }
return { exist: false }
}
module.exports = lookupContactByPhone

View File

@ -0,0 +1,88 @@
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')
async function lookupCrmTicket(rest, authentication, crmPhone, companyId, test = {}, ticketId) {
let { request, body, response } = findProperty(rest, 'lookupTicket')
let { requestContentType, requestEncoding, requestType, responseType, url } = request
const { type, userName, passWord, token, crmClientId } = authentication
const config = await requestConfigHeader(url, crmPhone, requestType, requestContentType, type, userName, passWord, token, crmClientId, '', ticketId)
if (test?.testing) {
let msg = `Tentanto checar o status do ticket para o numer ${crmPhone} no crm`
sendMessageSocket({ companyId, status: 'processing', data: { request: config, msg } })
}
let resp
try {
resp = await axios(config)
} catch (error) {
if (error?.response?.status == 404)
return { error: 404 }
}
let { data } = resp
data = flatten(data)
let auxTicketStatus
let auxTicketId
for (const prop in data) {
const _prop = prop.replace(/^\d+\./, '').replace(/(?:^|\.)\d+\b/g, '')
if (_prop == response?.status?.trim()) {
auxTicketStatus = data[prop].replace('+', '')
}
if (_prop == response?.id?.trim()) {
auxTicketId = data[prop]
}
if (auxTicketStatus && auxTicketId) break
}
if (!auxTicketStatus && !auxTicketId) {
for (const prop in data) {
let _prop = prop.replace(/\.(\d+)(\.|$)/g, '[$1]$2')
if (_prop == response?.status?.trim()) {
auxTicketStatus = data[prop].replace('+', '')
}
if (_prop == response?.id?.trim()) {
auxTicketId = data[prop]
}
if (auxTicketStatus && auxTicketId) break
}
}
console.log('auxTicketStatus: ', auxTicketStatus, ' | auxTicketId: ', auxTicketId)
return { auxTicketId, auxTicketStatus }
}
module.exports = lookupCrmTicket

View File

@ -1,9 +1,11 @@
const { getAccessToken } = require('./oauth2')
async function requestConfigHeader(url, crmPhone, requestType, requestContentType, type, userName, passWord, token, crmClientId, data = '') {
async function requestConfigHeader(url, crmPhone, requestType, requestContentType, type, userName, passWord, token, crmClientId, data = '', ticketId='') {
let config = {}
console.log('requestConfigHeader ticketId: ', ticketId)
url = url.replace('crmPhone', crmPhone)
url = url.replace('ticketId', ticketId)
if (type == 'api_token') {
if (requestType.trim().toLowerCase() == 'post') {

View File

@ -0,0 +1,100 @@
const loadCRM = require('./loadCRM')
const lookupContactByPhone = require('./lookupCRMContactByPhone')
const createContact = require('./createContact')
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')
async function ticketCRM(companyId, crmPhone, crmAgent, crmFirstName = 'unnamed') {
const crmFiles = await loadCRM(companyId)
for (const crmConfig of crmFiles) {
const { crmRest: rest, authentication } = crmConfig.crm
let obj = findProperty(rest, 'lookupTicket')
if (obj) {
let { url } = obj.request
let contact = await lookupContactByPhone(rest, authentication, crmPhone, companyId)
if (!contact?.exist) {
contact = await createContact(companyId, rest, authentication, crmPhone, crmFirstName)
}
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 })
const obj_ticket = await CRM_Ticket.findOne(
{ companyId, crm, contact: obj_contact }
).select('ticketId')
if (obj_ticket) {
const obj_ticket_status = await lookupCRMTicket(
rest,
authentication,
crmPhone,
companyId,
test = { testing: false },
obj_ticket.ticketId
)
if (obj_ticket_status) {
const { auxTicketStatus, error } = obj_ticket_status
// refactor this for production. For now only working with hubspot where status new is equal 1
if ((auxTicketStatus && auxTicketStatus != '1') || (error && error == 404)) {
await CRM_Ticket.deleteMany({ contact: obj_contact })
crmFirstName = await _createTicket(rest, crmPhone, companyId, authentication, crmFirstName, contact)
}
}
}
else {
crmFirstName = await _createTicket(rest, crmPhone, companyId, authentication, crmFirstName, contact)
}
}
}
}
module.exports = ticketCRM
async function _createTicket(rest, crmPhone, companyId, authentication, crmFirstName, contact) {
let obj = findProperty(rest, 'createTicketRecord')
if (obj) {
let { request, response } = obj
if (request) {
msg = `Tentando criar ticket para o contato ${crmPhone}`
await createTicket(companyId,
rest,
authentication,
crmPhone,
crmFirstName = 'unnamed',
crmLastName = '',
crmEmail = '',
test = { testing: false },
crmContactId = contact.contactId
)
}
}
return crmFirstName
}