feat: add support for ticket creation
parent
8f9afd5f23
commit
7af9e34ad7
|
@ -1,8 +1,8 @@
|
|||
{
|
||||
"authentication": {
|
||||
"type": "bearer",
|
||||
"token": "pat-na1-7aca13dd-9ba5-48db-bf35-570844d31abb",
|
||||
"crmPhoneTest": "5511988334455"
|
||||
"token": "pat-na1-37da6668-e0b1-44cb-bd2d-596f5f65634a",
|
||||
"crmPhoneTest": "5514987659932"
|
||||
},
|
||||
"crmRest": [
|
||||
{
|
||||
|
@ -179,8 +179,56 @@
|
|||
}
|
||||
}
|
||||
],
|
||||
"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"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -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 })
|
||||
}
|
||||
)
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -5,6 +5,8 @@ function findProperty(array, property) {
|
|||
return item[property] !== undefined
|
||||
})
|
||||
|
||||
if (index == -1) return
|
||||
|
||||
return array[index][property]
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
@ -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') {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
Loading…
Reference in New Issue