@tiledesk/tiledesk-server
Version:
The Tiledesk server module
1,449 lines (1,130 loc) • 88.7 kB
JavaScript
'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);