UNPKG

@tiledesk/tiledesk-server

Version:
1,449 lines (1,130 loc) 88.7 kB
'use strict'; var departmentService = require('../services/departmentService'); var Request = require("../models/request"); var Project_user = require("../models/project_user"); var Project = require("../models/project"); var messageService = require('../services/messageService'); const requestEvent = require('../event/requestEvent'); const leadEvent = require('../event/leadEvent'); var winston = require('../config/winston'); var RequestConstants = require("../models/requestConstants"); var requestUtil = require("../utils/requestUtil"); var cacheUtil = require("../utils/cacheUtil"); var arrayUtil = require("../utils/arrayUtil"); var cacheEnabler = require("../services/cacheEnabler"); var UIDGenerator = require("../utils/UIDGenerator"); const { TdCache } = require('../utils/TdCache'); const { QuoteManager } = require('./QuoteManager'); var configGlobal = require('../config/global'); const projectService = require('./projectService'); const axios = require("axios").default; let port = process.env.PORT || '3000'; let TILEBOT_ENDPOINT = "http://localhost:" + port + "/modules/tilebot/ext/";; if (process.env.TILEBOT_ENDPOINT) { TILEBOT_ENDPOINT = process.env.TILEBOT_ENDPOINT + "/ext/" } let tdCache = new TdCache({ host: process.env.CACHE_REDIS_HOST, port: process.env.CACHE_REDIS_PORT, password: process.env.CACHE_REDIS_PASSWORD }); tdCache.connect(); let qm = new QuoteManager({ tdCache: tdCache }); class RequestService { constructor() { this.listen(); } listen() { // 12 marzo 2024 I disabled these two functions due to performance problems for a chatbot created by Sponziello "Community bots Sendinblue Hubspot Qapla)" // this.updateSnapshotLead(); // this.sendMessageUpdateLead(); } // 12 marzo 2024 I disabled these two functions due to performance problems for a chatbot created by Sponziello "Community bots Sendinblue Hubspot Qapla)" // updateSnapshotLead() { // leadEvent.on('lead.update', function (lead) { // setImmediate(() => { // winston.debug("updateSnapshotLead on lead.update ", lead); // Request.updateMany({ lead: lead._id, id_project: lead.id_project }, { "$set": { "snapshot.lead": lead } }, function (err, updates) { // if (err) { // winston.error("Error updating requests updateSnapshotLead", err); // return 0; // } // winston.verbose("updateSnapshotLead updated for " + updates.nModified + " request") // requestEvent.emit('request.update.snapshot.lead', { lead: lead, updates: updates }); // return; // }); // // Request.find({lead: lead._id, id_project: lead.id_project}, function(err, requests) { // // if (err) { // // winston.error("Error getting request by lead", err); // // return 0; // // } // // if (!requests || (requests && requests.length==0)) { // // winston.warn("No request found for lead id " +lead._id ); // // return 0; // // } // // requests.forEach(function(request) { // // }); // // }); // }); // }); // } // 12 marzo 2024 I disabled these two functions due to performance problems for a chatbot created by Sponziello "Community bots Sendinblue Hubspot Qapla)" // sendMessageUpdateLead() { // leadEvent.on('lead.fullname.email.update', function (lead) { // winston.debug("lead.fullname.email.update "); // // leadEvent.on('lead.update', function(lead) { // setImmediate(() => { // winston.debug("sendMessageUpdateLead on lead.update ", lead); // Request.find({ lead: lead._id, id_project: lead.id_project }, function (err, requests) { // if (err) { // winston.error("Error getting sendMessageUpdateLead request by lead", err); // return 0; // } // if (!requests || (requests && requests.length == 0)) { // winston.warn("sendMessageUpdateLead No request found for lead id " + lead._id); // return 0; // } // // winston.info("sendMessageUpdateLead requests ", requests); // requests.forEach(function (request) { // winston.debug("sendMessageUpdateLead request ", request); // // send(sender, senderFullname, recipient, text, id_project, createdBy, attributes, type, metadata, language) // messageService.send( // 'system', // 'Bot', // // lead.fullname, // request.request_id, // "Lead updated", // request.id_project, // 'system', // { // subtype: "info/support", // "updateconversation": false, // messagelabel: { key: "LEAD_UPDATED" }, // updateUserEmail: lead.email, // updateUserFullname: lead.fullname // }, // undefined, // request.language // ); // }); // }); // }); // }); // } getAvailableAgentsCount(agents) { var project_users_available = agents.filter(function (projectUser) { if (projectUser.user_available == true) { return true; } }); winston.debug('++ AVAILABLE PROJECT USERS count ', project_users_available) if (project_users_available && project_users_available.length > 0) { return project_users_available.length; } else { return 0; } } //change create with this routeInternal(request, departmentid, id_project, nobot) { var that = this; return new Promise(function (resolve, reject) { var context = { request: request }; // getOperators(departmentid, projectid, nobot, disableWebHookCall, context) return departmentService.getOperators(departmentid, id_project, nobot, undefined, context).then(function (result) { // winston.debug("getOperators", result); var assigned_at = undefined; var status = RequestConstants.UNASSIGNED; var assigned_operator_id; var participants = []; var participantsAgents = []; var participantsBots = []; var hasBot = false; if (result.operators && result.operators.length > 0) { assigned_operator_id = result.operators[0].id_user; status = RequestConstants.ASSIGNED; var assigned_operator_idString = assigned_operator_id.toString(); participants.push(assigned_operator_idString); // botprefix if (assigned_operator_idString.startsWith("bot_")) { hasBot = true; // botprefix var assigned_operator_idStringBot = assigned_operator_idString.replace("bot_", ""); participantsBots.push(assigned_operator_idStringBot); } else { participantsAgents.push(assigned_operator_idString); hasBot = false; //?? } assigned_at = Date.now(); } winston.debug("routeInternal assigned_operator_id: " + assigned_operator_id); winston.debug("routeInternal status: " + status); // cosi modifica la request originale forse devi fare il clone????? request.status = status; request.participants = participants; request.participantsAgents = participantsAgents; request.participantsBots = participantsBots; request.hasBot = hasBot; request.department = result.department._id; // request.agents = result.agents; request.assigned_at = assigned_at; request.waiting_time = undefined //reset waiting_time on reroute //console.log("request.snapshot for ", request.request_id ," exists: ", request.snapshot ? "yes" : "no\n", new Date()); //console.log("request.snapshot.agents for ", request.request_id ," exists: ", request.snapshot?.agents ? "yes" : "no\n", new Date()); if (!request.snapshot) { //if used other methods than .create request.snapshot = {} } request.snapshot.department = result.department; request.snapshot.agents = result.agents; request.snapshot.availableAgentsCount = that.getAvailableAgentsCount(result.agents); return resolve(request); }).catch(function (err) { return reject(err); }); }); } // TODO changePreflightByRequestId se un agente entra in request freflight true disabilitare add agente e reassing ma mettere un bottone removePreflight??? // usalo no_populate async route(request_id, departmentid, id_project, nobot, no_populate) { try { winston.debug("request_id:" + request_id); winston.debug("departmentid:" + departmentid); winston.debug("id_project:" + id_project); winston.debug("nobot:" + nobot); //winston.info("main_flow_cache_3 route"); // Find request let query = Request.findOne({ request_id, id_project }); if (cacheEnabler.request) { query = query.cache(cacheUtil.defaultTTL, id_project + ":requests:request_id:" + request_id + ":simple"); winston.debug('request cache enabled'); } const request = await query.exec(); if (!request) { throw new Error(`Request not found: ${request_id}`); } // Clone before route const requestBeforeRoute = request.toObject(); const beforeParticipants = requestBeforeRoute.participants; const beforeDepartmentId = requestBeforeRoute.department?.toString(); // Route internal const routedRequest = await this.routeInternal(request, departmentid, id_project, nobot); const afterDepartmentId = routedRequest.department?.toString(); // Case 1 - No changes if ( requestBeforeRoute.status === routedRequest.status && beforeDepartmentId === afterDepartmentId && requestUtil.arraysEqual(beforeParticipants, routedRequest.participants) ) { winston.verbose(`Request ${request.request_id} routed to same status/participants`); if (routedRequest.attributes?.fully_abandoned === true) { request.status = RequestConstants.ABANDONED; request.attributes.fully_abandoned = true; request.markModified("status"); request.markModified("attributes"); try { await request.save(); winston.verbose(`Status set to ABANDONED for request ${request._id}`); } catch (err) { winston.error("Error updating request to ABANDONED", err); } } /** * TODO: Restore proper functioning * Option commented on 03/12/2025 by Johnny in order to allows clients to receive the request populated * in such case of Abandoned Request (Status 150) */ // if (no_populate === "true" || no_populate === true) { // winston.debug("no_populate is true"); // requestEvent.emit('request.update', request); // return request; // } const requestComplete = await request .populate("lead") .populate("department") .populate("participatingBots") .populate("participatingAgents") .populate({ path: "requester", populate: { path: "id_user" } }) .execPopulate(); this.emitParticipantsEvents( request, requestComplete, beforeParticipants ); return requestComplete; } // Quota check let project; try { project = await projectService.getCachedProject(id_project); } catch (err) { winston.warn("Error getting cached project, skip quota check", err); } const isTestConversation = request.attributes?.sourcePage?.includes("td_draft=true"); const isVoiceConversation = request.channel?.name === "voice-vxml"; const isStandardConversation = !isTestConversation && !isVoiceConversation; if (isStandardConversation) { const available = await qm.checkQuote(project, request, "requests"); if (!available) { throw new Error(`Requests limits reached for project ${project._id}`); } } // Case 2 - Leaving TEMP status. After internal routing: STATUS changed from 50 to 100 or 200 if (requestBeforeRoute.status === RequestConstants.TEMP && [RequestConstants.ASSIGNED, RequestConstants.UNASSIGNED].includes(routedRequest.status) && isStandardConversation ) { requestEvent.emit("request.create.quote", { project, request }); } // Case 3 - Conversation opened through proactive message. After internal routing: STATUS changed from undefined to 100 if ( !requestBeforeRoute.status && routedRequest.status === RequestConstants.ASSIGNED && isStandardConversation ) { requestEvent.emit("request.create.quote", { project, request }); } // Save and populate const savedRequest = await routedRequest.save(); const requestComplete = await savedRequest .populate("lead") .populate("department") .populate("participatingBots") .populate("participatingAgents") .populate({ path: "requester", populate: { path: "id_user" } }) .execPopulate(); this.emitParticipantsEvents( request, requestComplete, beforeParticipants ); requestEvent.emit("request.department.update", requestComplete); return requestComplete; } catch (error) { winston.error("Route error", { error, request_id, id_project }); throw error; } } reroute(request_id, id_project, nobot) { var that = this; var startDate = new Date(); return new Promise(function (resolve, reject) { // winston.debug("request_id", request_id); // winston.debug("newstatus", newstatus); let q = Request .findOne({ request_id: request_id, id_project: id_project }); if (cacheEnabler.request) { q.cache(cacheUtil.defaultTTL, id_project + ":requests:request_id:" + request_id + ":simple") //request_cache winston.debug('request cache enabled'); } return q.exec(function (err, request) { if (err) { winston.error(err); return reject(err); } winston.debug('request cache simple 3', request); winston.debug("here reroute1 "); // Cannot read property 'toString' of undefined at /usr/src/app/services/requestService.js:404:61 at /usr/src/app/node_modules/cachegoose/out/extend-query.js:44:13 at Command.cb [as callback] (/usr/src/app/node_modules/cacheman-redis/node/index.js:142:9) at normal_reply (/usr/src/app/node_modules/redis/index.js:655:21) at RedisClient.return_reply (/usr/src/app/node_modules/redis/index.js:753:9) at JavascriptRedisParser.returnReply (/usr/src/app/node_modules/redis/index.js:138:18) at JavascriptRedisParser.execute (/usr/src/app/node_modules/redis-parser/lib/parser.js:544:14) at Socket.<anonymous> (/usr/src/app/node_modules/redis/index.js:219:27) at Socket.emit (events.js:314:20) at addChunk (_stream_readable.js:297:12) at readableAddChunk (_stream_readable.js:272:9) at Socket.Readable.push (_stream_readable.js:213:10) at TCP.onStreamRead (internal/stream_base_commons.js:188:23) {"date":"Tue Mar 21 2023 18:45:47 GMT+0000 (Coordinated Unive if (request.department === undefined) { winston.error("Request with request_id " + request_id + " has empty department. So I can't reroute"); return reject("Request with request_id " + request_id + " has empty department. So I can't reroute"); } return that.route(request_id, request.department.toString(), id_project, nobot).then(function (routedRequest) { var endDate = new Date(); winston.verbose("Performance Request reroute in millis: " + endDate - startDate); return resolve(routedRequest); }).catch(function (err) { return reject(err); }); }); }); } createWithRequester(project_user_id, lead_id, id_project, first_text, departmentid, sourcePage, language, userAgent, status, createdBy, attributes, subject, preflight) { var request_id = 'support-group-' + id_project + "-" + UIDGenerator.generate(); winston.debug("request_id: " + request_id); return this.createWithIdAndRequester(request_id, project_user_id, lead_id, id_project, first_text, departmentid, sourcePage, language, userAgent, status, createdBy, attributes, subject, preflight); } createWithIdAndRequester(request_id, project_user_id, lead_id, id_project, first_text, departmentid, sourcePage, language, userAgent, status, createdBy, attributes, subject, preflight, channel, location) { var request = { request_id: request_id, project_user_id: project_user_id, lead_id: lead_id, id_project: id_project, first_text: first_text, departmentid: departmentid, sourcePage: sourcePage, language: language, userAgent: userAgent, status: status, createdBy: createdBy, attributes: attributes, subject: subject, preflight: preflight, channel: channel, location: location }; return this.create(request); }; async create(request) { const createdAt = request.createdAt || new Date(); let { request_id, project_user_id, lead_id, id_project, first_text, sourcePage, language, userAgent, status, attributes, subject, preflight, channel, location, participants = [], tags, notes, priority, auto_close, followers, contact } = request; let departmentid = request.departmentid || 'default'; let createdBy = request.createdBy || project_user_id || "system"; // Utils and flags let payload; let isTestConversation = false; let isVoiceConversation = false; let isStandardConversation = false; const context = { request: { request_id, project_user_id, lead_id, id_project, first_text, departmentid, sourcePage, language, userAgent, status, createdBy, attributes, subject, preflight, channel, location, participants,tags,notes,priority,auto_close,followers,contact } }; winston.debug("context: ", context); // Initial assignment let agents = []; let participantsAgents = []; let participantsBots = []; let hasBot = false; let dep_id; let assigned_at; let snapshot = {}; // Getting operators let result; try { result = await departmentService.getOperators(departmentid, id_project, false, undefined, context); winston.debug("Get operators result: ", result); } catch (err) { throw new Error("Error getting operators", { cause: err }); } agents = result.agents; // Status and quote management if (status === RequestConstants.TEMP) { // Skip assignment if (participants.length === 0) { dep_id = result.department._id; } } else { let project; try { project = await projectService.getCachedProject(id_project); } catch (err) { throw new Error("Error getting project", { cause: err }); } if (!project) { winston.error(`Project not found for id_project: ${id_project}`); throw new Error(`Project not found for id_project: ${id_project}`); } payload = { project, request }; // Test conversation if (attributes?.sourcePage?.includes("td_draft=true")) { winston.verbose("is a test conversation --> skip quote availability check"); isTestConversation = true; } // Voice conversation else if (channel?.name === "voice-vxml") { isVoiceConversation = true; const available = await qm.checkQuote(project, request, "voice_duration"); if (!available) { winston.info(`Voice duration limits reached for project ${id_project}`); throw new Error(`Voice duration limits reached for project ${id_project}`); } } // Standard conversation else { isStandardConversation = true; const available = await qm.checkQuote(project, request, "requests"); if (!available) { winston.info(`Requests limits reached for project ${project._id}`); throw new Error(`Requests limits reached for project ${project._id}`); } } // Assignment if (participants.length === 0) { if (result.operators?.length > 0) { participants.push(result.operators[0].id_user.toString()); } dep_id = result.department._id; } if (participants.length > 0) { status = RequestConstants.ASSIGNED; const firstParticipant = participants[0]; if (firstParticipant.startsWith("bot_")) { hasBot = true; const botId = firstParticipant.replace("bot_", ""); participantsBots.push(botId); } else { participantsAgents.push(firstParticipant); } assigned_at = Date.now(); } else { status = RequestConstants.UNASSIGNED; } } // Snapshot if (dep_id) { snapshot.department = result.department; } snapshot.availableAgentsCount = this.getAvailableAgentsCount(agents); if (request.requester) { snapshot.requester = request.requester; } if (request.lead) { snapshot.lead = request.lead; } // Create request const newRequest = new Request({ request_id, requester: project_user_id, lead: lead_id, first_text, subject, status, participants, participantsAgents, participantsBots, hasBot, department: dep_id, sourcePage, language, userAgent, assigned_at, attributes, id_project, createdBy, updatedBy: createdBy, preflight, channel, location, tags, notes, priority, auto_close, followers, createdAt, snapshot, contact, }) if (isTestConversation) { newRequest.draft = true; } winston.debug('newRequest.', newRequest); //winston.info("main_flow_cache_ requestService create"); // Save request try { const savedRequest = await newRequest.save(); winston.debug("Request created", savedRequest.toObject()); /** * Se non faccio findOne usando la cache adesso, quando verrà eseguita la query in reroute() la request non ha il department. */ let q = Request.findOne({ request_id, id_project }); if (cacheEnabler.request) { q.cache(cacheUtil.defaultTTL, id_project + ":requests:request_id:" + request_id + ":simple"); winston.debug('request cache enabled'); } await q.exec(); snapshot.agents = agents; requestEvent.emit("request.create.simple", savedRequest, snapshot); if (isStandardConversation) { requestEvent.emit("request.create.quote", payload); } // Emit event to update snapshot in queue // if (Object.keys(snapshot).length > 0) { // requestEvent.emit("request.snapshot.update", { // request: savedRequest, // snapshot: snapshot // }); // } return savedRequest; } catch (err) { winston.error(`RequestService error on save request ${newRequest.request_id}`, err); throw new Error("RequestService error on save request", { cause: err }); } } // DEPRECATED // async create(request) { // if (!request.createdAt) { // request.createdAt = new Date(); // } // var request_id = request.request_id; // var project_user_id = request.project_user_id; // var lead_id = request.lead_id; // var id_project = request.id_project; // var first_text = request.first_text; // var departmentid = request.departmentid; // var sourcePage = request.sourcePage; // var language = request.language; // var userAgent = request.userAgent; // var status = request.status; // var createdBy = request.createdBy; // var attributes = request.attributes; // var subject = request.subject; // var preflight = request.preflight; // var channel = request.channel; // var location = request.location; // var participants = request.participants || []; // var tags = request.tags; // var notes = request.notes; // var priority = request.priority; // var auto_close = request.auto_close; // var followers = request.followers; // let createdAt = request.createdAt; // if (!departmentid) { // departmentid = 'default'; // } // if (!createdBy) { // if (project_user_id) { // createdBy = project_user_id; // } else { // createdBy = "system"; // } // } // // Utils // let payload; // let isTestConversation = false; // let isVoiceConversation = false; // let isStandardConversation = false; // var that = this; // return new Promise( async (resolve, reject) => { // var context = { // request: { // request_id: request_id, project_user_id: project_user_id, lead_id: lead_id, id_project: id_project, // first_text: first_text, departmentid: departmentid, sourcePage: sourcePage, language: language, userAgent: userAgent, status: status, // createdBy: createdBy, attributes: attributes, subject: subject, preflight: preflight, channel: channel, location: location, // participants: participants, tags: tags, notes: notes, // priority: priority, auto_close: auto_close, followers: followers // } // }; // winston.debug("context", context); // var participantsAgents = []; // var participantsBots = []; // var hasBot = false; // var dep_id = undefined; // var assigned_at = undefined; // var agents = []; // var snapshot = {}; // try { // // (method) DepartmentService.getOperators(departmentid: any, projectid: any, nobot: any, disableWebHookCall: any, context: any): Promise<any> // var result = await departmentService.getOperators(departmentid, id_project, false, undefined, context); // winston.debug("getOperators", result); // } catch (err) { // return reject(err); // } // agents = result.agents; // if (status == 50) { // // skip assignment // if (participants.length == 0) { // dep_id = result.department._id; // } // } else { // let project = await projectService.getCachedProject(id_project).catch((err) => { // winston.warn("Error getting cached project. Skip conversation quota check.") // winston.warn("Getting cached project error: ", err) // }) // payload = { // project: project, // request: request // } // if (attributes && attributes.sourcePage && (attributes.sourcePage.indexOf("td_draft=true") > -1)) { // winston.verbose("is a test conversation --> skip quote availability check") // isTestConversation = true; // } // else if (channel && (channel.name === 'voice-vxml')) { // isVoiceConversation = true; // let available = await qm.checkQuote(project, request, 'voice_duration'); // if (available === false) { // winston.info("Voice duration limits reached for project " + project._id); // return reject("Voice duration limits reached for project " + project._id); // } // } // else { // isStandardConversation = true; // let available = await qm.checkQuote(project, request, 'requests'); // if (available === false) { // winston.info("Requests limits reached for project " + project._id) // return reject("Requests limits reached for project " + project._id); // } // } // if (participants.length == 0) { // if (result.operators && result.operators.length > 0) { // participants.push(result.operators[0].id_user.toString()); // } // // for preflight it is important to save agents in req for trigger. try to optimize it // dep_id = result.department._id; // } // if (participants.length > 0) { // status = RequestConstants.ASSIGNED; // // botprefix // if (participants[0].startsWith("bot_")) { // hasBot = true; // winston.debug("hasBot:" + hasBot); // // botprefix // var assigned_operator_idStringBot = participants[0].replace("bot_", ""); // winston.debug("assigned_operator_idStringBot:" + assigned_operator_idStringBot); // participantsBots.push(assigned_operator_idStringBot); // } else { // participantsAgents.push(participants[0]); // } // assigned_at = Date.now(); // } else { // status = RequestConstants.UNASSIGNED; // } // } // if (dep_id) { // snapshot.department = result.department; // } // snapshot.agents = agents; // snapshot.availableAgentsCount = that.getAvailableAgentsCount(agents); // if (request.requester) { // snapshot.requester = request.requester; // } // if (request.lead) { // snapshot.lead = request.lead; // } // var newRequest = new Request({ // request_id: request_id, // requester: project_user_id, // lead: lead_id, // first_text: first_text, // subject: subject, // status: status, // participants: participants, // participantsAgents: participantsAgents, // participantsBots: participantsBots, // hasBot: hasBot, // department: dep_id, // // agents: agents, // //others // sourcePage: sourcePage, // language: language, // userAgent: userAgent, // assigned_at: assigned_at, // attributes: attributes, // //standard // id_project: id_project, // createdBy: createdBy, // updatedBy: createdBy, // preflight: preflight, // channel: channel, // location: location, // //snapshot: snapshot, // tags: tags, // notes: notes, // priority: priority, // auto_close: auto_close, // followers: followers, // createdAt: createdAt // }); // if (isTestConversation) { // newRequest.draft = true; // } // winston.debug('newRequest.', newRequest); // //cacheinvalidation // return newRequest.save( async function (err, savedRequest) { // if (err) { // winston.error('RequestService error for method createWithIdAndRequester for newRequest' + JSON.stringify(newRequest), err); // return reject(err); // } // winston.debug("Request created", savedRequest.toObject()); // requestEvent.emit('request.create.simple', savedRequest); // if (isStandardConversation) { // requestEvent.emit('request.create.quote', payload);; // } // return resolve(savedRequest); // }); // }) // } //DEPRECATED. USED ONLY IN SAME TESTS createWithId(request_id, requester_id, id_project, first_text, departmentid, sourcePage, language, userAgent, status, createdBy, attributes) { // winston.debug("request_id", request_id); if (!departmentid) { departmentid = 'default'; } if (!createdBy) { createdBy = requester_id; } var that = this; return new Promise(function (resolve, reject) { var context = { request: { request_id: request_id, requester_id: requester_id, id_project: id_project, first_text: first_text, departmentid: departmentid, sourcePage: sourcePage, language: language, userAgent: userAgent, status: status, createdBy: createdBy, attributes: attributes } }; // getOperators(departmentid, projectid, nobot, disableWebHookCall, context) return departmentService.getOperators(departmentid, id_project, false, undefined, context).then(function (result) { // winston.debug("getOperators", result); var status = RequestConstants.UNASSIGNED; var assigned_operator_id; var participants = []; var participantsAgents = []; var participantsBots = []; var hasBot = false; var assigned_at = undefined; if (result.operators && result.operators.length > 0) { assigned_operator_id = result.operators[0].id_user; status = RequestConstants.ASSIGNED; var assigned_operator_idString = assigned_operator_id.toString(); participants.push(assigned_operator_idString); // botprefix if (assigned_operator_idString.startsWith("bot_")) { hasBot = true; // botprefix var assigned_operator_idStringBot = assigned_operator_idString.replace("bot_", ""); winston.debug("assigned_operator_idStringBot:" + assigned_operator_idStringBot); participantsBots.push(assigned_operator_idStringBot); } else { participantsAgents.push(assigned_operator_idString); } assigned_at = Date.now(); } // winston.debug("assigned_operator_id", assigned_operator_id); // winston.debug("status", status); var newRequest = new Request({ request_id: request_id, requester_id: requester_id, first_text: first_text, status: status, participants: participants, participantsAgents: participantsAgents, participantsBots: participantsBots, hasBot: hasBot, department: result.department._id, agents: result.agents, //availableAgents: result.available_agents, // assigned_operator_id: result.assigned_operator_id, //others sourcePage: sourcePage, language: language, userAgent: userAgent, assigned_at: assigned_at, attributes: attributes, //standard id_project: id_project, createdBy: createdBy, updatedBy: createdBy }); // winston.debug('newRequest.',newRequest); //cacheinvalidation return newRequest.save(function (err, savedRequest) { if (err) { winston.error('RequestService error for method createWithId for newRequest' + JSON.stringify(newRequest), err); return reject(err); } winston.verbose("Request created", savedRequest.toObject()); requestEvent.emit('request.create.simple', savedRequest); return resolve(savedRequest); }); }).catch(function (err) { return reject(err); }); }); } changeStatusByRequestId(request_id, id_project, newstatus) { return new Promise(function (resolve, reject) { // winston.debug("request_id", request_id); // winston.debug("newstatus", newstatus); //TODO CHECK IF ALREADY CLOSED return Request .findOneAndUpdate({ request_id: request_id, id_project: id_project }, { status: newstatus }, { new: true, upsert: false }) .populate('lead') .populate('department') .populate('participatingBots') .populate('participatingAgents') .populate({ path: 'requester', populate: { path: 'id_user' } }) .exec(function (err, updatedRequest) { if (err) { winston.error(err); return reject(err); } requestEvent.emit('request.update', updatedRequest); //deprecated requestEvent.emit("request.update.comment", { comment: "STATUS_CHANGE", request: updatedRequest });//Deprecated requestEvent.emit("request.updated", { comment: "STATUS_CHANGE", request: updatedRequest, patch: { status: newstatus } }); //TODO emit request.clone or reopen also return resolve(updatedRequest); }); }); } changeFirstTextAndPreflightByRequestId(request_id, id_project, first_text, preflight) { return new Promise(function (resolve, reject) { winston.debug("changeFirstTextAndPreflightByRequestId" + request_id); // winston.debug("request_id", request_id); // winston.debug("newstatus", newstatus); if (!first_text) { winston.error("Error changing first text. The field first_text is empty for request " + request_id); return reject({ err: "Error changing first text. The field first_text is empty" }); } return Request .findOneAndUpdate({ request_id: request_id, id_project: id_project }, { first_text: first_text, preflight: preflight }, { new: true, upsert: false }) .populate('lead') .populate('department') .populate('participatingBots') .populate('participatingAgents') .populate({ path: 'requester', populate: { path: 'id_user' } }) .exec(function (err, updatedRequest) { if (err) { winston.error(err); return reject(err); } requestEvent.emit('request.update.preflight', updatedRequest); //archive to audit log requestEvent.emit('request.update', updatedRequest); requestEvent.emit("request.update.comment", { comment: "FIRSTTEXT_PREFLIGHT_CHANGE", request: updatedRequest });//Deprecated requestEvent.emit("request.updated", { comment: "FIRSTTEXT_PREFLIGHT_CHANGE", request: updatedRequest, patch: { first_text: first_text, preflight: preflight } }); //TODO emit request.clone or reopen also return resolve(updatedRequest); }); }); } changeFirstTextByRequestId(request_id, id_project, first_text) { return new Promise(function (resolve, reject) { // winston.debug("request_id", request_id); // winston.debug("newstatus", newstatus); return Request .findOneAndUpdate({ request_id: request_id, id_project: id_project }, { first_text: first_text }, { new: true, upsert: false }) .populate('lead') .populate('department') .populate('participatingBots') .populate('participatingAgents') .populate({ path: 'requester', populate: { path: 'id_user' } }) .exec(function (err, updatedRequest) { if (err) { winston.error(err); return reject(err); } requestEvent.emit('request.update', updatedRequest); requestEvent.emit("request.update.comment", { comment: "FIRSTTEXT_CHANGE", request: updatedRequest });//Deprecated requestEvent.emit("request.updated", { comment: "FIRSTTEXT_CHANGE", request: updatedRequest, patch: { first_text: first_text } }); //TODO emit request.clone or reopen also return resolve(updatedRequest); }); }); } changePreflightByRequestId(request_id, id_project, preflight) { return new Promise(function (resolve, reject) { // winston.debug("request_id", request_id); // winston.debug("newstatus", newstatus); return Request .findOneAndUpdate({ request_id: request_id, id_project: id_project }, { preflight: preflight }, { new: true, upsert: false }) .populate('lead') .populate('department') .populate('participatingBots') .populate('participatingAgents') .populate({ path: 'requester', populate: { path: 'id_user' } }) .exec(function (err, updatedRequest) { if (err) { winston.error(err); return reject(err); } requestEvent.emit('request.update', updatedRequest); requestEvent.emit("request.update.comment", { comment: "PREFLIGHT_CHANGE", request: updatedRequest });//Deprecated requestEvent.emit("request.updated", { comment: "PREFLIGHT_CHANGE", request: updatedRequest, patch: { preflight: preflight } }); return resolve(updatedRequest); }); }); } setClosedAtByRequestId(request_id, id_project, created_at, closed_at, closed_by) { return new Promise(function (resolve, reject) { // winston.debug("request_id", request_id); // winston.debug("newstatus", newstatus); let duration = Math.abs(new Date(created_at) - new Date(closed_at)) return Request .findOneAndUpdate({ request_id: request_id, id_project: id_project }, { closed_at: closed_at, closed_by: closed_by, duration: duration }, { new: true, upsert: false }) .populate('lead') .populate('department') .populate('participatingBots') .populate('participatingAgents') .populate({ path: 'requester', populate: { path: 'id_user' } }) .exec( async function (err, updatedRequest) { if (err) { winston.error(err); return reject(err); } let project = await projectService.getCachedProject(id_project).catch((err) => { winston.warn("Error getting cached project. Skip conversation quota check.") winston.warn("Getting cached project error: ", err) }) let payload = { project: project, request: updatedRequest } if (updatedRequest.channel.name === 'voice-vxml') { requestEvent.emit('request.close.quote', payload); } // winston.debug("updatedRequest", updatedRequest); return resolve(updatedRequest); }); }); } // unused incrementMessagesCountByRequestId(request_id, id_project) { return new Promise(function (resolve, reject) { // winston.debug("request_id", request_id); // winston.debug("newstatus", newstatus); return Request .findOneAndUpdate({ request_id: request_id, id_project: id_project }, { $inc: { 'messages.messages_count': 1 } }, { new: true, upsert: false }, function (err, updatedRequest) { if (err) { winston.error(err); return reject(err); } winston.debug("Message count +1"); return resolve(updatedRequest); }); }); } updateWaitingTimeByRequestId(request_id, id_project, enable_populate) { return new Promise(function (resolve, reject) { // winston.debug("request_id", request_id); // winston.debug("newstatus", newstatus); let q = Request .findOne({ request_id: request_id, id_project: id_project }); if (enable_populate == true) { winston.debug("updateWaitingTimeByRequestId enable_populate"); q.populate('lead') .populate('department') .populate('participatingBots') .populate('participatingAgents') .populate({ path: 'requester', populate: { path: 'id_user' } }); } // if (cacheEnabler.request) { //attention this cache is not usable bacause cacheoose don't support populate without .lean.. so if cached populated field is not returned with cacheoose, updateWaitingTime is only used in chat21webhook but i thik it is important for messages route // q.cache(cacheUtil.defaultTTL, id_project+":requests:request_id:"+request_id) //request_cache // winston.debug('request cache enabled'); // } q.exec(function (err, request) { if (err) { winston.error(err); return reject(err); } //update waiting_time only the first time if (!request.waiting_time) { var now = Date.now(); var waitingTime = now - request.createdAt; // winston.debug("waitingTime", waitingTime); request.waiting_time = waitingTime; // TODO REENABLE SERVED // request.status = RequestConstants.SERVED; request.first_response_at = now; // winston.debug(" request", request); winston.debug("Request waitingTime setted"); //cacheinvalidation // return resolve(request.save()); request.save(function (err, savedRequest) { if (err) { return reject(err); } requestEvent.emit('request.update', savedRequest); return resolve(savedRequest); }); } else { return resolve(request); } }); }); } closeRequestByRequestId(request_id, id_project, skipStatsUpdate, notify, closed_by, force) { var that = this; return new Promise(function (resolve, reject) { // winston.debug("request_id", request_id); if (force == undefined) { winston.debug("force is undefined "); force = false; } // else { // winston.info("force is: " + force); // } return Request .findOne({ request_id: request_id, id_project: id_project }) .populate('lead') .populate('department') .populate('participatingBots') .populate('participatingAgents') .populate({ path: 'requester', populate: { path: 'id_user' } }) .exec(function (err, request) { if (err) { winston.error("Error getting closing request with request_id: " + request_id, err); return reject(err); } if (!request) { winston.error("Request not found for request_id " + request_id + " and id_project " + id_project); return reject({ "success": false, msg: "Request not found for request_id " + request_id + " and id_project " + id_project }); } if (force == false && request.status == RequestConstants.CLOSED) { // qui1000 // if (request.statusObj.closed) { requestEvent.emit('request.close', request); winston.debug("Request already closed for request_id " + request_id + " and id_project " + id_project); return resolve(request); } winston.debug("sono qui"); // un utente può chiudere se appartiene a participatingAgents oppure meglio agents del progetto? return that.changeStatusByRequestId(request_id, id_project, 1000).then(function (updatedRequest) { // qui1000 // return that.changeStatusByRequestId(request_id, id_project, {closed:true}).then(function(updatedRequest) { // winston.debug("updatedRequest", updatedRequest); return messageService.getTranscriptByRequestId(request_id, id_project).then(function (transcript) { // winston.debug("transcript", transcript); return that.updateTrascriptByRequestId(request_id, id_project, transcript).then(function (updatedRequest) { if (skipStatsUpdate) { // TODO test it winston.verbose("Request closed with skipStatsUpdate and with id: " + updatedRequest.id); winston.debug("Request closed ", updatedRequest); //TODO ?? requestEvent.emit('request.update', updatedRequest); requestEvent.emit('request.close', updatedRequest); requestEvent.emit('request.close.extended', { request: updatedRequest, notify: notify }); return resolve(updatedRequest); } // setClosedAtByRequestId(request_id, id_project, closed_at, closed_by) return that.setClosedAtByRequestId(request_id, id_project, request.createdAt, new Date().getTime(), closed_by).then(function (updatedRequest) { winston.verbose("Request closed with id: " + updatedRequest.id); winston.debug("Request closed ", updatedRequest); //TODO ?? requestEvent.emit('request.update', updatedRequest); requestEvent.emit('request.close', updatedRequest); requestEvent.emit('request.close.extended', { request: updatedRequest, notify: notify }); return resolve(updatedRequest); }); }); }); }).catch(function (err) { winston.error(err); return reject(err); }); }); }); } reopenRequestByRequestId(request_id, id_project) { var that = this; return new Promise(function (resolve, reject) { // winston.debug("request_id", request_id); return Request .findOne({ request_id: request_id, id_project: id_project }) .populate('lead') .populate('department') .populate('participatingBots') .populate('participatingAgents') .populate({ path: 'requester', populate: { path: 'id_user' } }) .exec(function (err, request) { if (err) { winston.error("Error getting reopened request ", err); return reject(err); } if (!request) { winston.error("Request not found for request_id " + request_id + " and id_project " + id_project); return reject({ "success": false, msg: "Request not found for request_id " + request_id + " and id_project " + id_project }); } if (request.status == RequestConstants.ASSIGNED || request.status == RequestConstants.UNASSIGNED // TODO REENABLE SERVED // || request.status == RequestConstants.SERVED ) { winston.debug("request already open"); return resolve(request);