From cc42193fdb09565b68233ba01384c8e7a753256b Mon Sep 17 00:00:00 2001 From: Marcos Spessatto Defendi Date: Mon, 29 Jun 2020 17:59:14 -0300 Subject: [PATCH] Regression: Improve Omnichannel Business Hours (#18050) Co-authored-by: Renato Becker --- .../views/app/business-hours/BusinessHours.ts | 34 ++- .../views/app/business-hours/IBusinessHour.ts | 7 - .../business-hours/IBusinessHourBehavior.ts | 8 + .../client/views/app/business-hours/Single.ts | 12 +- .../livechatBusinessHoursForm.html | 3 + .../livechatBusinessHoursForm.js | 38 +-- .../imports/server/rest/businessHours.js | 4 +- app/livechat/server/api/lib/businessHours.ts | 4 +- .../business-hour/AbstractBusinessHour.ts | 99 ++++--- .../business-hour/BusinessHourManager.ts | 145 +++++----- app/livechat/server/business-hour/Default.ts | 33 +++ app/livechat/server/business-hour/Helper.ts | 45 +++ app/livechat/server/business-hour/Single.ts | 61 ++-- app/livechat/server/business-hour/index.ts | 11 +- .../server/hooks/processRoomAbandonment.js | 26 +- app/livechat/server/lib/Helper.js | 12 +- app/livechat/server/lib/Livechat.js | 3 +- app/livechat/server/startup.js | 8 +- .../server/models/LivechatBusinessHours.ts | 14 +- .../server/models/LivechatDepartment.js | 1 + app/models/server/models/Users.js | 1 + .../server/raw/LivechatBusinessHours.ts | 85 ++++-- app/models/server/raw/LivechatDepartment.js | 20 ++ app/models/server/raw/Users.js | 13 +- definition/ILivechatBusinessHour.ts | 13 +- .../client/SingleBusinessHour.ts | 7 + ee/app/livechat-enterprise/client/route.js | 2 +- ee/app/livechat-enterprise/client/startup.ts | 37 ++- .../businessHoursCustomFieldsForm.html | 12 +- .../businessHoursCustomFieldsForm.js | 14 - .../customTemplates/businessHoursFormField.js | 34 +++ .../businessHoursTimezoneFormField.html | 13 + .../livechatDepartmentCustomFieldsForm.js | 3 +- .../views/app/registerCustomTemplates.js | 1 + .../client/views/business-hours/Multiple.ts | 16 +- .../business-hours/livechatBusinessHours.js | 8 +- .../server/api/business-hours.ts | 4 + .../server/business-hour/Custom.ts | 84 ++++++ .../server/business-hour/Helper.ts | 66 +++++ .../server/business-hour/Multiple.ts | 268 +++++++----------- .../server/business-hour/index.ts | 1 + .../server/hooks/afterRemoveDepartment.js | 13 +- .../livechat-enterprise/server/hooks/index.js | 1 - .../server/hooks/onBusinessHourStart.ts | 11 +- .../server/hooks/onChangeAgentDepartment.ts | 27 -- ee/app/livechat-enterprise/server/index.js | 1 + .../server/methods/removeBusinessHour.ts | 5 +- ee/app/livechat-enterprise/server/startup.js | 17 +- .../server/raw/LivechatDepartmentAgents.ts | 4 +- package-lock.json | 257 +++++++++-------- package.json | 1 + server/main.d.ts | 18 ++ server/startup/migrations/index.js | 1 + server/startup/migrations/v195.js | 28 +- server/startup/migrations/v197.js | 35 +++ typings.d.ts | 2 + 56 files changed, 1003 insertions(+), 688 deletions(-) delete mode 100644 app/livechat/client/views/app/business-hours/IBusinessHour.ts create mode 100644 app/livechat/client/views/app/business-hours/IBusinessHourBehavior.ts create mode 100644 app/livechat/server/business-hour/Default.ts create mode 100644 app/livechat/server/business-hour/Helper.ts create mode 100644 ee/app/livechat-enterprise/client/SingleBusinessHour.ts create mode 100644 ee/app/livechat-enterprise/client/views/app/customTemplates/businessHoursFormField.js create mode 100644 ee/app/livechat-enterprise/client/views/app/customTemplates/businessHoursTimezoneFormField.html create mode 100644 ee/app/livechat-enterprise/server/business-hour/Custom.ts create mode 100644 ee/app/livechat-enterprise/server/business-hour/Helper.ts create mode 100644 ee/app/livechat-enterprise/server/business-hour/index.ts delete mode 100644 ee/app/livechat-enterprise/server/hooks/onChangeAgentDepartment.ts create mode 100644 server/startup/migrations/v197.js diff --git a/app/livechat/client/views/app/business-hours/BusinessHours.ts b/app/livechat/client/views/app/business-hours/BusinessHours.ts index 8d5fe0401e5..8252d59ec00 100644 --- a/app/livechat/client/views/app/business-hours/BusinessHours.ts +++ b/app/livechat/client/views/app/business-hours/BusinessHours.ts @@ -1,33 +1,37 @@ -import { IBusinessHour } from './IBusinessHour'; -import { SingleBusinessHour } from './Single'; +import { IBusinessHourBehavior } from './IBusinessHourBehavior'; +import { SingleBusinessHourBehavior } from './Single'; import { ILivechatBusinessHour } from '../../../../../../definition/ILivechatBusinessHour'; class BusinessHoursManager { - private businessHour: IBusinessHour; + private behavior: IBusinessHourBehavior; - constructor(businessHour: IBusinessHour) { - this.setBusinessHourManager(businessHour); + constructor(businessHour: IBusinessHourBehavior) { + this.setBusinessHourBehavior(businessHour); } - setBusinessHourManager(businessHour: IBusinessHour): void { - this.registerBusinessHourMethod(businessHour); + setBusinessHourBehavior(businessHour: IBusinessHourBehavior): void { + this.registerBusinessHourBehavior(businessHour); } - registerBusinessHourMethod(businessHour: IBusinessHour): void { - this.businessHour = businessHour; + registerBusinessHourBehavior(behavior: IBusinessHourBehavior): void { + this.behavior = behavior; } getTemplate(): string { - return this.businessHour.getView(); + return this.behavior.getView(); } - shouldShowCustomTemplate(businessHourData: ILivechatBusinessHour): boolean { - return this.businessHour.shouldShowCustomTemplate(businessHourData); + showCustomTemplate(businessHourData: ILivechatBusinessHour): boolean { + return this.behavior.showCustomTemplate(businessHourData); } - shouldShowBackButton(): boolean { - return this.businessHour.shouldShowBackButton(); + showBackButton(): boolean { + return this.behavior.showBackButton(); + } + + showTimezoneTemplate(): boolean { + return this.behavior.showTimezoneTemplate(); } } -export const businessHourManager = new BusinessHoursManager(new SingleBusinessHour() as IBusinessHour); +export const businessHourManager = new BusinessHoursManager(new SingleBusinessHourBehavior()); diff --git a/app/livechat/client/views/app/business-hours/IBusinessHour.ts b/app/livechat/client/views/app/business-hours/IBusinessHour.ts deleted file mode 100644 index f5a1c97dab1..00000000000 --- a/app/livechat/client/views/app/business-hours/IBusinessHour.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { ILivechatBusinessHour } from '../../../../../../definition/ILivechatBusinessHour'; - -export interface IBusinessHour { - getView(): string; - shouldShowCustomTemplate(businessHourData: ILivechatBusinessHour): boolean; - shouldShowBackButton(): boolean; -} diff --git a/app/livechat/client/views/app/business-hours/IBusinessHourBehavior.ts b/app/livechat/client/views/app/business-hours/IBusinessHourBehavior.ts new file mode 100644 index 00000000000..1909f9510d8 --- /dev/null +++ b/app/livechat/client/views/app/business-hours/IBusinessHourBehavior.ts @@ -0,0 +1,8 @@ +import { ILivechatBusinessHour } from '../../../../../../definition/ILivechatBusinessHour'; + +export interface IBusinessHourBehavior { + getView(): string; + showCustomTemplate(businessHourData: ILivechatBusinessHour): boolean; + showBackButton(): boolean; + showTimezoneTemplate(): boolean; +} diff --git a/app/livechat/client/views/app/business-hours/Single.ts b/app/livechat/client/views/app/business-hours/Single.ts index 4dc41b05cb9..2841a27d7b7 100644 --- a/app/livechat/client/views/app/business-hours/Single.ts +++ b/app/livechat/client/views/app/business-hours/Single.ts @@ -1,15 +1,19 @@ -import { IBusinessHour } from './IBusinessHour'; +import { IBusinessHourBehavior } from './IBusinessHourBehavior'; -export class SingleBusinessHour implements IBusinessHour { +export class SingleBusinessHourBehavior implements IBusinessHourBehavior { getView(): string { return 'livechatBusinessHoursForm'; } - shouldShowCustomTemplate(): boolean { + showCustomTemplate(): boolean { return false; } - shouldShowBackButton(): boolean { + showBackButton(): boolean { + return false; + } + + showTimezoneTemplate(): boolean { return false; } } diff --git a/app/livechat/client/views/app/business-hours/livechatBusinessHoursForm.html b/app/livechat/client/views/app/business-hours/livechatBusinessHoursForm.html index a212fed851e..0fc164fee9f 100644 --- a/app/livechat/client/views/app/business-hours/livechatBusinessHoursForm.html +++ b/app/livechat/client/views/app/business-hours/livechatBusinessHoursForm.html @@ -1,6 +1,9 @@ diff --git a/ee/app/livechat-enterprise/client/views/app/customTemplates/businessHoursCustomFieldsForm.js b/ee/app/livechat-enterprise/client/views/app/customTemplates/businessHoursCustomFieldsForm.js index 8a5b94c39c0..399ff0aecc5 100644 --- a/ee/app/livechat-enterprise/client/views/app/customTemplates/businessHoursCustomFieldsForm.js +++ b/ee/app/livechat-enterprise/client/views/app/customTemplates/businessHoursCustomFieldsForm.js @@ -1,16 +1,9 @@ -import moment from 'moment'; import { ReactiveVar } from 'meteor/reactive-var'; import { Template } from 'meteor/templating'; import './businessHoursCustomFieldsForm.html'; Template.businessHoursCustomFieldsForm.helpers({ - timezones() { - return moment.tz.names().map((name) => ({ - key: name, - i18nLabel: name, - })); - }, data() { return Template.instance().businessHour.get(); }, @@ -27,13 +20,6 @@ Template.businessHoursCustomFieldsForm.helpers({ active() { return Template.instance().active.get(); }, - selectedOption(val) { - const { timezone } = Template.instance().businessHour.get(); - if (!timezone) { - return; - } - return timezone.name === val; - }, departmentModifier() { return (filter, text = '') => { const f = filter.get(); diff --git a/ee/app/livechat-enterprise/client/views/app/customTemplates/businessHoursFormField.js b/ee/app/livechat-enterprise/client/views/app/customTemplates/businessHoursFormField.js new file mode 100644 index 00000000000..e9ccb76ef1f --- /dev/null +++ b/ee/app/livechat-enterprise/client/views/app/customTemplates/businessHoursFormField.js @@ -0,0 +1,34 @@ +import moment from 'moment'; +import { Template } from 'meteor/templating'; +import { ReactiveVar } from 'meteor/reactive-var'; + +import './businessHoursTimezoneFormField.html'; + +Template.businessHoursTimezoneFormField.helpers({ + timezones() { + return moment.tz.names().map((name) => ({ + key: name, + i18nLabel: name, + })); + }, + selectedOption(val) { + const { timezone } = Template.instance().businessHour.get(); + if (!timezone) { + return; + } + return timezone.name === val; + }, + data() { + return Template.instance().businessHour.get(); + }, +}); + +Template.businessHoursTimezoneFormField.onCreated(function() { + this.businessHour = new ReactiveVar({}); + + this.autorun(() => { + // To make this template reactive we expect a ReactiveVar through the data property, + // because the parent form may not be rerender, only the dynamic template data + this.businessHour.set(this.data.get()); + }); +}); diff --git a/ee/app/livechat-enterprise/client/views/app/customTemplates/businessHoursTimezoneFormField.html b/ee/app/livechat-enterprise/client/views/app/customTemplates/businessHoursTimezoneFormField.html new file mode 100644 index 00000000000..e618be6687c --- /dev/null +++ b/ee/app/livechat-enterprise/client/views/app/customTemplates/businessHoursTimezoneFormField.html @@ -0,0 +1,13 @@ + diff --git a/ee/app/livechat-enterprise/client/views/app/customTemplates/livechatDepartmentCustomFieldsForm.js b/ee/app/livechat-enterprise/client/views/app/customTemplates/livechatDepartmentCustomFieldsForm.js index 1af3c036cda..29a14ddafcc 100644 --- a/ee/app/livechat-enterprise/client/views/app/customTemplates/livechatDepartmentCustomFieldsForm.js +++ b/ee/app/livechat-enterprise/client/views/app/customTemplates/livechatDepartmentCustomFieldsForm.js @@ -3,6 +3,7 @@ import { Template } from 'meteor/templating'; import { APIClient, mountArrayQueryParameters } from '../../../../../../../app/utils/client'; import './livechatDepartmentCustomFieldsForm.html'; +import { LivechatBusinessHourTypes } from '../../../../../../../definition/ILivechatBusinessHour'; Template.livechatDepartmentCustomFieldsForm.helpers({ department() { @@ -61,7 +62,7 @@ Template.livechatDepartmentCustomFieldsForm.onCreated(function() { }))); } if (department.businessHourId) { - const { businessHour } = await APIClient.v1.get(`livechat/business-hour?_id=${ department.businessHourId }`); + const { businessHour } = await APIClient.v1.get(`livechat/business-hour?_id=${ department.businessHourId }&type=${ LivechatBusinessHourTypes.CUSTOM }`); this.businessHour.set(businessHour); } this.department.set(department); diff --git a/ee/app/livechat-enterprise/client/views/app/registerCustomTemplates.js b/ee/app/livechat-enterprise/client/views/app/registerCustomTemplates.js index 188ced2d77c..072518349ec 100644 --- a/ee/app/livechat-enterprise/client/views/app/registerCustomTemplates.js +++ b/ee/app/livechat-enterprise/client/views/app/registerCustomTemplates.js @@ -6,6 +6,7 @@ import './customTemplates/livechatAgentInfoCustomFieldsForm'; import './customTemplates/visitorEditCustomFieldsForm'; import './customTemplates/visitorInfoCustomForm'; import './customTemplates/businessHoursCustomFieldsForm'; +import './customTemplates/businessHoursFormField'; addCustomFormTemplate('livechatAgentEditForm', 'livechatAgentEditCustomFieldsForm'); addCustomFormTemplate('livechatAgentInfoForm', 'livechatAgentInfoCustomFieldsForm'); diff --git a/ee/app/livechat-enterprise/client/views/business-hours/Multiple.ts b/ee/app/livechat-enterprise/client/views/business-hours/Multiple.ts index 15c383c98bd..15284ce69e0 100644 --- a/ee/app/livechat-enterprise/client/views/business-hours/Multiple.ts +++ b/ee/app/livechat-enterprise/client/views/business-hours/Multiple.ts @@ -1,16 +1,20 @@ -import { IBusinessHour } from '../../../../../../app/livechat/client/views/app/business-hours/IBusinessHour'; -import { ILivechatBusinessHour, LivechatBussinessHourTypes } from '../../../../../../definition/ILivechatBusinessHour'; +import { IBusinessHourBehavior } from '../../../../../../app/livechat/client/views/app/business-hours/IBusinessHourBehavior'; +import { ILivechatBusinessHour, LivechatBusinessHourTypes } from '../../../../../../definition/ILivechatBusinessHour'; -export class MultipleBusinessHours implements IBusinessHour { +export class MultipleBusinessHoursBehavior implements IBusinessHourBehavior { getView(): string { return 'livechatBusinessHours'; } - shouldShowCustomTemplate(businessHourData: ILivechatBusinessHour): boolean { - return !businessHourData._id || businessHourData.type !== LivechatBussinessHourTypes.SINGLE; + showCustomTemplate(businessHourData: ILivechatBusinessHour): boolean { + return !businessHourData._id || businessHourData.type !== LivechatBusinessHourTypes.DEFAULT; } - shouldShowBackButton(): boolean { + showTimezoneTemplate(): boolean { + return true; + } + + showBackButton(): boolean { return true; } } diff --git a/ee/app/livechat-enterprise/client/views/business-hours/livechatBusinessHours.js b/ee/app/livechat-enterprise/client/views/business-hours/livechatBusinessHours.js index 0c8a995d30b..aa1b88bca12 100644 --- a/ee/app/livechat-enterprise/client/views/business-hours/livechatBusinessHours.js +++ b/ee/app/livechat-enterprise/client/views/business-hours/livechatBusinessHours.js @@ -9,7 +9,7 @@ import { hasLicense } from '../../../../license/client'; import './livechatBusinessHours.html'; import { modal } from '../../../../../../app/ui-utils/client'; import { APIClient, handleError, t } from '../../../../../../app/utils'; -import { LivechatBussinessHourTypes } from '../../../../../../definition/ILivechatBusinessHour'; +import { LivechatBusinessHourTypes } from '../../../../../../definition/ILivechatBusinessHour'; const licenseEnabled = new ReactiveVar(false); @@ -32,7 +32,7 @@ Template.livechatBusinessHours.helpers({ return instance.ready && instance.ready.get(); }, isDefault() { - return this.type === LivechatBussinessHourTypes.SINGLE; + return this.type === LivechatBusinessHourTypes.DEFAULT; }, openDays() { return this @@ -72,7 +72,7 @@ Template.livechatBusinessHours.events({ closeOnConfirm: false, html: false, }, () => { - Meteor.call('livechat:removeBusinessHour', this._id, (error/* , result*/) => { + Meteor.call('livechat:removeBusinessHour', this._id, this.type, (error/* , result*/) => { if (error) { return handleError(error); } @@ -90,7 +90,7 @@ Template.livechatBusinessHours.events({ 'click .business-hour-info'(e/* , instance*/) { e.preventDefault(); - FlowRouter.go('livechat-business-hour-edit', { _id: this._id }); + FlowRouter.go('livechat-business-hour-edit', { _id: this._id, type: this.type }); }, 'keydown #business-hour-filter'(e) { diff --git a/ee/app/livechat-enterprise/server/api/business-hours.ts b/ee/app/livechat-enterprise/server/api/business-hours.ts index 2f5fa314e6c..8044a1509a1 100644 --- a/ee/app/livechat-enterprise/server/api/business-hours.ts +++ b/ee/app/livechat-enterprise/server/api/business-hours.ts @@ -1,12 +1,16 @@ +import { Promise } from 'meteor/promise'; + import { API } from '../../../../../app/api/server'; import { findBusinessHours } from '../business-hour/lib/business-hour'; +// @ts-ignore API.v1.addRoute('livechat/business-hours.list', { authRequired: true }, { get() { const { offset, count } = this.getPaginationItems(); const { sort } = this.parseJsonQuery(); const { name } = this.queryParams; + // @ts-ignore return API.v1.success(Promise.await(findBusinessHours( this.userId, { diff --git a/ee/app/livechat-enterprise/server/business-hour/Custom.ts b/ee/app/livechat-enterprise/server/business-hour/Custom.ts new file mode 100644 index 00000000000..67717348c07 --- /dev/null +++ b/ee/app/livechat-enterprise/server/business-hour/Custom.ts @@ -0,0 +1,84 @@ +import { + AbstractBusinessHourType, + IBusinessHourType, +} from '../../../../../app/livechat/server/business-hour/AbstractBusinessHour'; +import { ILivechatBusinessHour, LivechatBusinessHourTypes } from '../../../../../definition/ILivechatBusinessHour'; +import { LivechatDepartmentRaw } from '../../../../../app/models/server/raw/LivechatDepartment'; +import { LivechatDepartment } from '../../../../../app/models/server/raw'; +import { businessHourManager } from '../../../../../app/livechat/server/business-hour'; +import LivechatDepartmentAgents, { LivechatDepartmentAgentsRaw } from '../../../models/server/raw/LivechatDepartmentAgents'; + +export interface IBusinessHoursExtraProperties extends ILivechatBusinessHour { + timezoneName: string; + departmentsToApplyBusinessHour: string; +} + +class CustomBusinessHour extends AbstractBusinessHourType implements IBusinessHourType { + name = LivechatBusinessHourTypes.CUSTOM; + + private DepartmentsRepository: LivechatDepartmentRaw = LivechatDepartment; + + private DepartmentsAgentsRepository: LivechatDepartmentAgentsRaw = LivechatDepartmentAgents; + + async getBusinessHour(id: string): Promise { + if (!id) { + return; + } + const businessHour: ILivechatBusinessHour = await this.BusinessHourRepository.findOneById(id); + businessHour.departments = await this.DepartmentsRepository.findByBusinessHourId(businessHour._id, { fields: { name: 1 } }).toArray(); + return businessHour; + } + + async saveBusinessHour(businessHourData: IBusinessHoursExtraProperties): Promise { + businessHourData.timezone = { + name: businessHourData.timezoneName, + utc: this.getUTCFromTimezone(businessHourData.timezoneName), + }; + const departments = businessHourData.departmentsToApplyBusinessHour?.split(',').filter(Boolean); + const businessHourToReturn = { ...businessHourData }; + delete businessHourData.timezoneName; + delete businessHourData.departmentsToApplyBusinessHour; + delete businessHourData.departments; + const businessHourId = await this.baseSaveBusinessHour(businessHourData); + const currentDepartments = (await this.DepartmentsRepository.findByBusinessHourId(businessHourId, { fields: { _id: 1 } }).toArray()).map((dept: any) => dept._id); + const toRemove = [...currentDepartments.filter((dept: string) => !departments.includes(dept))]; + const toAdd = [...departments.filter((dept: string) => !currentDepartments.includes(dept))]; + await this.removeBusinessHourFromDepartmentsIfNeeded(businessHourId, toRemove); + await this.addBusinessHourToDepartmentsIfNeeded(businessHourId, toAdd); + businessHourToReturn._id = businessHourId; + return businessHourToReturn; + } + + async removeBusinessHourById(businessHourId: string): Promise { + const businessHour = await this.BusinessHourRepository.findOneById(businessHourId, {}); + if (!businessHour) { + return; + } + await this.BusinessHourRepository.removeById(businessHourId); + await this.removeBusinessHourFromAgents(businessHourId); + await this.DepartmentsRepository.removeBusinessHourFromDepartmentsByBusinessHourId(businessHourId); + return this.UsersRepository.updateLivechatStatusBasedOnBusinessHours(); + } + + private async removeBusinessHourFromAgents(businessHourId: string): Promise { + const departmentIds = (await this.DepartmentsRepository.findByBusinessHourId(businessHourId, { fields: { _id: 1 } }).toArray()).map((dept: any) => dept._id); + const agentIds = (await this.DepartmentsAgentsRepository.findByDepartmentIds(departmentIds, { fields: { agentId: 1 } }).toArray()).map((dept: any) => dept.agentId); + return this.UsersRepository.removeBusinessHourByAgentIds(agentIds, businessHourId); + } + + private async removeBusinessHourFromDepartmentsIfNeeded(businessHourId: string, departmentsToRemove: string[]): Promise { + if (!departmentsToRemove.length) { + return; + } + await this.DepartmentsRepository.removeBusinessHourFromDepartmentsByIdsAndBusinessHourId(departmentsToRemove, businessHourId); + } + + private async addBusinessHourToDepartmentsIfNeeded(businessHourId: string, departmentsToAdd: string[]): Promise { + if (!departmentsToAdd.length) { + return; + } + await this.DepartmentsRepository.addBusinessHourToDepartamentsByIds(departmentsToAdd, businessHourId); + } +} + +businessHourManager.registerBusinessHourType(new CustomBusinessHour()); diff --git a/ee/app/livechat-enterprise/server/business-hour/Helper.ts b/ee/app/livechat-enterprise/server/business-hour/Helper.ts new file mode 100644 index 00000000000..3fe0b4e7603 --- /dev/null +++ b/ee/app/livechat-enterprise/server/business-hour/Helper.ts @@ -0,0 +1,66 @@ +import { Meteor } from 'meteor/meteor'; +import moment from 'moment-timezone'; + +import { + LivechatBusinessHours, + LivechatDepartment, + LivechatDepartmentAgents, + Users, +} from '../../../../../app/models/server/raw'; +import { LivechatBusinessHourTypes } from '../../../../../definition/ILivechatBusinessHour'; + +const getAllAgentIdsWithoutDepartment = async (): Promise => { + const agentIdsWithDepartment = (await LivechatDepartmentAgents.find({}, { fields: { agentId: 1 } }).toArray()).map((dept: any) => dept.agentId); + const agentIdsWithoutDepartment = (await Users.findUsersInRolesWithQuery('livechat-agent', { + _id: { $nin: agentIdsWithDepartment }, + }, { fields: { _id: 1 } }).toArray()).map((user: any) => user._id); + return agentIdsWithoutDepartment; +}; + +const getAgentIdsToHandle = async (businessHour: Record): Promise => { + if (businessHour.type === LivechatBusinessHourTypes.DEFAULT) { + return getAllAgentIdsWithoutDepartment(); + } + const departmentIds = (await LivechatDepartment.findEnabledByBusinessHourId(businessHour._id, { fields: { _id: 1 } }).toArray()).map((dept: any) => dept._id); + return (await LivechatDepartmentAgents.findByDepartmentIds(departmentIds, { fields: { agentId: 1 } }).toArray()).map((dept: any) => dept.agentId); +}; + +export const openBusinessHour = async (businessHour: Record): Promise => { + const agentIds: string[] = await getAgentIdsToHandle(businessHour); + await Users.addBusinessHourByAgentIds(agentIds, businessHour._id); + return Users.updateLivechatStatusBasedOnBusinessHours(); +}; + +export const closeBusinessHour = async (businessHour: Record): Promise => { + const agentIds: string[] = await getAgentIdsToHandle(businessHour); + await Users.removeBusinessHourByAgentIds(agentIds, businessHour._id); + return Users.updateLivechatStatusBasedOnBusinessHours(); +}; + +export const removeBusinessHourByAgentIds = async (agentIds: string[], businessHourId: string): Promise => { + if (!agentIds.length) { + return; + } + await Users.removeBusinessHourByAgentIds(agentIds, businessHourId); + return Users.updateLivechatStatusBasedOnBusinessHours(); +}; + +export const resetDefaultBusinessHourIfNeeded = async (): Promise => { + Meteor.call('license:isEnterprise', async (err: any, isEnterprise: any) => { + if (err) { + throw err; + } + if (isEnterprise) { + return; + } + const defaultBusinessHour = await LivechatBusinessHours.findOneDefaultBusinessHour({ fields: { _id: 1 } }); + LivechatBusinessHours.update({ _id: defaultBusinessHour._id }, { + $set: { + timezone: { + name: moment.tz.guess(), + utc: String(moment().utcOffset() / 60), + }, + }, + }); + }); +}; diff --git a/ee/app/livechat-enterprise/server/business-hour/Multiple.ts b/ee/app/livechat-enterprise/server/business-hour/Multiple.ts index e2818de0d25..1e030e1b043 100644 --- a/ee/app/livechat-enterprise/server/business-hour/Multiple.ts +++ b/ee/app/livechat-enterprise/server/business-hour/Multiple.ts @@ -1,46 +1,54 @@ import moment from 'moment'; import { - AbstractBusinessHour, - IBusinessHour, + AbstractBusinessHourBehavior, + IBusinessHourBehavior, } from '../../../../../app/livechat/server/business-hour/AbstractBusinessHour'; -import { ILivechatBusinessHour, LivechatBussinessHourTypes } from '../../../../../definition/ILivechatBusinessHour'; -import { LivechatDepartment } from '../../../../../app/models/server/raw'; +import { ILivechatBusinessHour } from '../../../../../definition/ILivechatBusinessHour'; +import { LivechatDepartment } from '../../../../../app/models/server'; +import { LivechatDepartment as Raw } from '../../../../../app/models/server/raw'; import { LivechatDepartmentRaw } from '../../../../../app/models/server/raw/LivechatDepartment'; import LivechatDepartmentAgents, { LivechatDepartmentAgentsRaw } from '../../../models/server/raw/LivechatDepartmentAgents'; +import { filterBusinessHoursThatMustBeOpened } from '../../../../../app/livechat/server/business-hour/Helper'; +import { closeBusinessHour, openBusinessHour, removeBusinessHourByAgentIds } from './Helper'; interface IBusinessHoursExtraProperties extends ILivechatBusinessHour { timezoneName: string; departmentsToApplyBusinessHour: string; } -export class MultipleBusinessHours extends AbstractBusinessHour implements IBusinessHour { - private DepartmentsRepository: LivechatDepartmentRaw = LivechatDepartment; +export class MultipleBusinessHoursBehavior extends AbstractBusinessHourBehavior implements IBusinessHourBehavior { + private DepartmentsRepository: LivechatDepartmentRaw = Raw; private DepartmentsAgentsRepository: LivechatDepartmentAgentsRaw = LivechatDepartmentAgents; - async closeBusinessHoursByDayAndHour(day: string, hour: string): Promise { - const businessHours = await this.BusinessHourRepository.findActiveBusinessHoursToClose(day, hour, undefined, { + constructor() { + super(); + this.onAddAgentToDepartment = this.onAddAgentToDepartment.bind(this); + this.onRemoveAgentFromDepartment = this.onRemoveAgentFromDepartment.bind(this); + this.onRemoveDepartment = this.onRemoveDepartment.bind(this); + } + + async onStartBusinessHours(): Promise { + await this.UsersRepository.removeBusinessHoursFromAllUsers(); + await this.UsersRepository.updateLivechatStatusBasedOnBusinessHours(); + const currentTime = moment.utc(moment().utc().format('dddd:HH:mm'), 'dddd:HH:mm'); + const day = currentTime.format('dddd'); + const activeBusinessHours = await this.BusinessHourRepository.findActiveAndOpenBusinessHoursByDay(day, { fields: { - _id: 1, + workHours: 1, + timezone: 1, type: 1, + active: 1, }, }); - for (const businessHour of businessHours) { - this.closeBusinessHour(businessHour); - } - } - - async getBusinessHour(id: string): Promise { - if (!id) { - return; + const businessHoursToOpen = await filterBusinessHoursThatMustBeOpened(activeBusinessHours); + for (const businessHour of businessHoursToOpen) { + this.openBusinessHour(businessHour); } - const businessHour: ILivechatBusinessHour = await this.BusinessHourRepository.findOneById(id); - businessHour.departments = await this.DepartmentsRepository.findByBusinessHourId(businessHour._id, { fields: { name: 1 } }).toArray(); - return businessHour; } - async openBusinessHoursByDayHour(day: string, hour: string): Promise { + async openBusinessHoursByDayAndHour(day: string, hour: string): Promise { const businessHours = await this.BusinessHourRepository.findActiveBusinessHoursToOpen(day, hour, undefined, { fields: { _id: 1, @@ -52,177 +60,111 @@ export class MultipleBusinessHours extends AbstractBusinessHour implements IBusi } } - async saveBusinessHour(businessHourData: IBusinessHoursExtraProperties): Promise { - businessHourData.timezone = { - name: businessHourData.timezoneName, - utc: this.getUTCFromTimezone(businessHourData.timezoneName), - }; - if (businessHourData.timezone.name) { - businessHourData = this.convertWorkHoursWithSpecificTimezone(businessHourData); - } else { - businessHourData = this.convertWorkHoursWithServerTimezone(businessHourData) as IBusinessHoursExtraProperties; - } - businessHourData.active = Boolean(businessHourData.active); - businessHourData.type = businessHourData.type || LivechatBussinessHourTypes.MULTIPLE; - const departments = businessHourData.departmentsToApplyBusinessHour?.split(','); - delete businessHourData.timezoneName; - delete businessHourData.departmentsToApplyBusinessHour; - delete businessHourData.departments; - if (businessHourData._id) { - await this.BusinessHourRepository.updateOne(businessHourData._id, businessHourData); - return this.updateDepartmentBusinessHour(businessHourData._id, departments); - } - const { insertedId } = await this.BusinessHourRepository.insertOne(businessHourData); - return this.updateDepartmentBusinessHour(insertedId, departments); - } - - async removeBusinessHourById(id: string): Promise { - const businessHour = await this.BusinessHourRepository.findOneById(id); - if (!businessHour || businessHour.type !== LivechatBussinessHourTypes.MULTIPLE) { - return; - } - this.BusinessHourRepository.removeById(id); - this.DepartmentsRepository.removeBusinessHourFromDepartmentsByBusinessHourId(id); - } - - async openBusinessHoursIfNeeded(): Promise { - await this.removeBusinessHoursFromUsers(); - const currentTime = moment.utc(moment().utc().format('dddd:HH:mm'), 'dddd:HH:mm'); - const day = currentTime.format('dddd'); - const activeBusinessHours = await this.BusinessHourRepository.findActiveAndOpenBusinessHoursByDay(day, { + async closeBusinessHoursByDayAndHour(day: string, hour: string): Promise { + const businessHours = await this.BusinessHourRepository.findActiveBusinessHoursToClose(day, hour, undefined, { fields: { - workHours: 1, - timezone: 1, + _id: 1, type: 1, }, }); - const businessHoursToOpenIds = await this.getBusinessHoursThatMustBeOpened(currentTime, activeBusinessHours); - for (const businessHour of businessHoursToOpenIds) { - this.openBusinessHour(businessHour); + for (const businessHour of businessHours) { + this.closeBusinessHour(businessHour); } } - async removeBusinessHourFromUsers(departmentId: string, businessHourId: string): Promise { - const agentIds = (await this.DepartmentsAgentsRepository.findByDepartmentIds([departmentId], { fields: { agentId: 1 } }).toArray()).map((dept: any) => dept.agentId); - await this.UsersRepository.closeBusinessHourByAgentIds(agentIds, businessHourId); - return this.UsersRepository.updateLivechatStatusBasedOnBusinessHours(); - } - - async removeBusinessHourFromUsersByIds(userIds: string[], businessHourId: string): Promise { - if (!userIds?.length) { - return; + async afterSaveBusinessHours(businessHourData: IBusinessHoursExtraProperties): Promise { + const departments = businessHourData.departmentsToApplyBusinessHour?.split(',').filter(Boolean); + const currentDepartments = businessHourData.departments?.map((dept: any) => dept._id); + const toRemove = [...(currentDepartments || []).filter((dept: Record) => !departments.includes(dept._id))]; + await this.removeBusinessHourFromRemovedDepartmentsUsersIfNeeded(businessHourData._id, toRemove); + const businessHour = await this.BusinessHourRepository.findOneById(businessHourData._id); + const businessHourIdToOpen = (await filterBusinessHoursThatMustBeOpened([businessHour])).map((businessHour) => businessHour._id); + if (!businessHourIdToOpen.length) { + return closeBusinessHour(businessHour); } - - await this.UsersRepository.closeBusinessHourByAgentIds(userIds, businessHourId); - return this.UsersRepository.updateLivechatStatusBasedOnBusinessHours(userIds); + return openBusinessHour(businessHour); } - async addBusinessHourToUsersByIds(userIds: string[], businessHourId: string): Promise { - if (!userIds?.length) { - return; + async onAddAgentToDepartment(options: Record = {}): Promise { + const { departmentId, agentsId } = options; + const department = await this.DepartmentsRepository.findOneById(departmentId, { fields: { businessHourId: 1 } }); + if (!department || !agentsId.length) { + return options; } - - await this.UsersRepository.openBusinessHourByAgentIds(userIds, businessHourId); - return this.UsersRepository.updateLivechatStatusBasedOnBusinessHours(userIds); - } - - async setDefaultToUsersIfNeeded(userIds: string[]): Promise { - if (!userIds?.length) { - return; + const defaultBusinessHour = await this.BusinessHourRepository.findOneDefaultBusinessHour(); + await removeBusinessHourByAgentIds(agentsId, defaultBusinessHour._id); + if (!department.businessHourId) { + return options; } - const currentTime = moment(moment().format('dddd:HH:mm'), 'dddd:HH:mm'); - const day = currentTime.format('dddd'); - const [businessHour] = await this.BusinessHourRepository.findDefaultActiveAndOpenBusinessHoursByDay(day); + const businessHour = await this.BusinessHourRepository.findOneById(department.businessHourId); if (!businessHour) { - return; + return options; } - for (const userId of userIds) { - if (!(await this.DepartmentsAgentsRepository.findDepartmentsWithBusinessHourByAgentId(userId)).length) { // eslint-disable-line no-await-in-loop - await this.UsersRepository.openBusinessHourByAgentIds([userId], businessHour._id); // eslint-disable-line no-await-in-loop - } + const businessHourToOpen = await filterBusinessHoursThatMustBeOpened([businessHour]); + if (!businessHourToOpen.length) { + return options; } - await this.UsersRepository.updateLivechatStatusBasedOnBusinessHours(); + await this.UsersRepository.addBusinessHourByAgentIds(agentsId, businessHour._id); + return options; } - private async updateDepartmentBusinessHour(businessHourId: string, departments: string[]): Promise { - if (businessHourId) { - await this.DepartmentsRepository.removeBusinessHourFromDepartmentsByBusinessHourId(businessHourId); - } - if (!departments?.length) { - return; + async onRemoveAgentFromDepartment(options: Record = {}): Promise { + const { departmentId, agentsId } = options; + const department = await this.DepartmentsRepository.findOneById(departmentId, { fields: { businessHourId: 1 } }); + if (!department || !agentsId.length) { + return options; } - return this.DepartmentsRepository.addBusinessHourToDepartamentsByIds(departments, businessHourId); + return this.handleRemoveAgentsFromDepartments(department, agentsId, options); } - private convertWorkHoursWithSpecificTimezone(businessHourData: IBusinessHoursExtraProperties): IBusinessHoursExtraProperties { - businessHourData.workHours.forEach((hour: any) => { - const startUtc = moment.tz(`${ hour.day }:${ hour.start }`, 'dddd:HH:mm', businessHourData.timezoneName).utc(); - const finishUtc = moment.tz(`${ hour.day }:${ hour.finish }`, 'dddd:HH:mm', businessHourData.timezoneName).utc(); - hour.start = { - time: hour.start, - utc: { - dayOfWeek: startUtc.clone().format('dddd'), - time: startUtc.clone().format('HH:mm'), - }, - cron: { - dayOfWeek: this.formatDayOfTheWeekFromServerTimezoneAndUtcHour(startUtc, 'dddd'), - time: this.formatDayOfTheWeekFromServerTimezoneAndUtcHour(startUtc, 'HH:mm'), - }, - }; - hour.finish = { - time: hour.finish, - utc: { - dayOfWeek: finishUtc.clone().format('dddd'), - time: finishUtc.clone().format('HH:mm'), - }, - cron: { - dayOfWeek: this.formatDayOfTheWeekFromServerTimezoneAndUtcHour(finishUtc, 'dddd'), - time: this.formatDayOfTheWeekFromServerTimezoneAndUtcHour(finishUtc, 'HH:mm'), - }, - }; - }); - return businessHourData; - } - - private formatDayOfTheWeekFromServerTimezoneAndUtcHour(utc: any, format: string): string { - return moment(utc.format('dddd:HH:mm'), 'dddd:HH:mm').add(moment().utcOffset() / 60, 'hours').format(format); - } - - private async openBusinessHour(businessHour: Record): Promise { - if (businessHour.type === LivechatBussinessHourTypes.MULTIPLE) { - const agentIds = await this.getAgentIdsFromBusinessHour(businessHour); - return this.UsersRepository.openBusinessHourByAgentIds(agentIds, businessHour._id); + async onRemoveDepartment(options: Record = {}): Promise { + const { department, agentsIds } = options; + if (!department || !agentsIds?.length) { + return options; } - const agentIdsWithDepartment = await this.getAgentIdsWithDepartment(); - return this.UsersRepository.openBusinessHourToAgentsWithoutDepartment(agentIdsWithDepartment, businessHour._id); + const deletedDepartment = LivechatDepartment.trashFindOneById(department._id); + return this.handleRemoveAgentsFromDepartments(deletedDepartment, agentsIds, options); } - private async getAgentIdsFromBusinessHour(businessHour: Record): Promise { - const departmentIds = (await this.DepartmentsRepository.findByBusinessHourId(businessHour._id, { fields: { _id: 1 } }).toArray()).map((dept: any) => dept._id); - const agentIds = (await this.DepartmentsAgentsRepository.findByDepartmentIds(departmentIds, { fields: { agentId: 1 } }).toArray()).map((dept: any) => dept.agentId); - return agentIds; + private async handleRemoveAgentsFromDepartments(department: Record, agentsIds: string[], options: any): Promise { + const agentIdsWithoutDepartment = []; + const agentIdsToRemoveCurrentBusinessHour = []; + for (const agentId of agentsIds) { + if (await this.DepartmentsAgentsRepository.findByAgentId(agentId).count() === 0) { // eslint-disable-line no-await-in-loop + agentIdsWithoutDepartment.push(agentId); + } + if (!(await this.DepartmentsAgentsRepository.findAgentsByAgentIdAndBusinessHourId(agentId, department.businessHourId)).length) { // eslint-disable-line no-await-in-loop + agentIdsToRemoveCurrentBusinessHour.push(agentId); + } + } + if (department.businessHourId) { + await removeBusinessHourByAgentIds(agentIdsToRemoveCurrentBusinessHour, department.businessHourId); + } + if (!agentIdsWithoutDepartment.length) { + return options; + } + const defaultBusinessHour = await this.BusinessHourRepository.findOneDefaultBusinessHour(); + const businessHourToOpen = await filterBusinessHoursThatMustBeOpened([defaultBusinessHour]); + if (!businessHourToOpen.length) { + return options; + } + await this.UsersRepository.addBusinessHourByAgentIds(agentIdsWithoutDepartment, defaultBusinessHour._id); + return options; } - private async getAgentIdsWithDepartment(): Promise { - const agentIdsWithDepartment = (await this.DepartmentsAgentsRepository.find({}, { fields: { agentId: 1 } }).toArray()).map((dept: any) => dept.agentId); - return agentIdsWithDepartment; + private async openBusinessHour(businessHour: Record): Promise { + return openBusinessHour(businessHour); } - private async closeBusinessHour(businessHour: Record): Promise { - if (businessHour.type === LivechatBussinessHourTypes.MULTIPLE) { - const agentIds = await this.getAgentIdsFromBusinessHour(businessHour); - await this.UsersRepository.closeBusinessHourByAgentIds(agentIds, businessHour._id); - return this.UsersRepository.updateLivechatStatusBasedOnBusinessHours(); + private async removeBusinessHourFromRemovedDepartmentsUsersIfNeeded(businessHourId: string, departmentsToRemove: string[]): Promise { + if (!departmentsToRemove.length) { + return; } - const agentIdsWithDepartment = await this.getAgentIdsWithDepartment(); - await this.UsersRepository.closeBusinessHourToAgentsWithoutDepartment(agentIdsWithDepartment, businessHour._id); - return this.UsersRepository.updateLivechatStatusBasedOnBusinessHours(); + const agentIds = (await this.DepartmentsAgentsRepository.findByDepartmentIds(departmentsToRemove).toArray()).map((dept: any) => dept.agentId); + await removeBusinessHourByAgentIds(agentIds, businessHourId); } - private getUTCFromTimezone(timezone: string): string { - if (!timezone) { - return String(moment().utcOffset() / 60); - } - return moment.tz(timezone).format('Z'); + private async closeBusinessHour(businessHour: Record): Promise { + closeBusinessHour(businessHour); } } diff --git a/ee/app/livechat-enterprise/server/business-hour/index.ts b/ee/app/livechat-enterprise/server/business-hour/index.ts new file mode 100644 index 00000000000..696f1032f86 --- /dev/null +++ b/ee/app/livechat-enterprise/server/business-hour/index.ts @@ -0,0 +1 @@ +import './Custom'; diff --git a/ee/app/livechat-enterprise/server/hooks/afterRemoveDepartment.js b/ee/app/livechat-enterprise/server/hooks/afterRemoveDepartment.js index 29b34907a72..ad53c9db204 100644 --- a/ee/app/livechat-enterprise/server/hooks/afterRemoveDepartment.js +++ b/ee/app/livechat-enterprise/server/hooks/afterRemoveDepartment.js @@ -1,16 +1,11 @@ import { callbacks } from '../../../../../app/callbacks'; import { LivechatDepartment } from '../../../../../app/models/server'; -import { businessHourManager } from '../../../../../app/livechat/server/business-hour'; -callbacks.add('livechat.afterRemoveDepartment', (department) => { +callbacks.add('livechat.afterRemoveDepartment', (options = {}) => { + const { department } = options; if (!department) { - return department; + return options; } LivechatDepartment.removeDepartmentFromForwardListById(department._id); - const deletedDepartment = LivechatDepartment.trashFindOneById(department._id); - if (!deletedDepartment.businessHourId) { - return department; - } - Promise.await(businessHourManager.removeBusinessHourIdFromUsers(deletedDepartment)); - return department; + return options; }, callbacks.priority.HIGH, 'livechat-after-remove-department'); diff --git a/ee/app/livechat-enterprise/server/hooks/index.js b/ee/app/livechat-enterprise/server/hooks/index.js index 23eebee8c3c..e0d7449ab1c 100644 --- a/ee/app/livechat-enterprise/server/hooks/index.js +++ b/ee/app/livechat-enterprise/server/hooks/index.js @@ -16,4 +16,3 @@ import './onSetUserStatusLivechat'; import './onCloseLivechat'; import './onSaveVisitorInfo'; import './onBusinessHourStart'; -import './onChangeAgentDepartment'; diff --git a/ee/app/livechat-enterprise/server/hooks/onBusinessHourStart.ts b/ee/app/livechat-enterprise/server/hooks/onBusinessHourStart.ts index b46f148d154..a4ab825b8e3 100644 --- a/ee/app/livechat-enterprise/server/hooks/onBusinessHourStart.ts +++ b/ee/app/livechat-enterprise/server/hooks/onBusinessHourStart.ts @@ -1,14 +1,15 @@ import { callbacks } from '../../../../../app/callbacks/server'; -import { MultipleBusinessHours } from '../business-hour/Multiple'; +import { MultipleBusinessHoursBehavior } from '../business-hour/Multiple'; import { settings } from '../../../../../app/settings/server'; +import { LivechatBusinessHourBehaviors } from '../../../../../definition/ILivechatBusinessHour'; callbacks.add('on-business-hour-start', (options: any = {}) => { - const { BusinessHourClass } = options; - if (!BusinessHourClass) { + const { BusinessHourBehaviorClass } = options; + if (!BusinessHourBehaviorClass) { return options; } - if (settings.get('Livechat_business_hour_type') === 'Single') { + if (settings.get('Livechat_business_hour_type') === LivechatBusinessHourBehaviors.SINGLE) { return options; } - return { BusinessHourClass: MultipleBusinessHours }; + return { BusinessHourBehaviorClass: MultipleBusinessHoursBehavior }; }, callbacks.priority.HIGH, 'livechat-on-business-hour-start'); diff --git a/ee/app/livechat-enterprise/server/hooks/onChangeAgentDepartment.ts b/ee/app/livechat-enterprise/server/hooks/onChangeAgentDepartment.ts deleted file mode 100644 index ddb1154349d..00000000000 --- a/ee/app/livechat-enterprise/server/hooks/onChangeAgentDepartment.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { callbacks } from '../../../../../app/callbacks/server'; -import { businessHourManager } from '../../../../../app/livechat/server/business-hour'; -import { LivechatDepartment } from '../../../../../app/models/server'; - -callbacks.add('livechat.removeAgentDepartment', async (options: any = {}) => { - const { departmentId, agentsId } = options; - const department = LivechatDepartment.findOneById(departmentId, { fields: { businessHourId: 1 } }); - if (!department || !department.businessHourId) { - return options; - } - - await businessHourManager.removeBusinessHourFromUsersByIds(agentsId, department.businessHourId); - await businessHourManager.setDefaultToUsersIfNeeded(agentsId); - return options; -}, callbacks.priority.HIGH, 'livechat-on-remove-agent-department'); - -callbacks.add('livechat.saveAgentDepartment', async (options: any = {}) => { - const { departmentId, agentsId } = options; - const department = LivechatDepartment.findOneById(departmentId, { fields: { businessHourId: 1 } }); - if (!department || !department.businessHourId) { - return options; - } - - await businessHourManager.addBusinessHourToUsersByIds(agentsId, department.businessHourId); - - return options; -}, callbacks.priority.HIGH, 'livechat-on-save-agent-department'); diff --git a/ee/app/livechat-enterprise/server/index.js b/ee/app/livechat-enterprise/server/index.js index 3aa2319d672..657548c2a9e 100644 --- a/ee/app/livechat-enterprise/server/index.js +++ b/ee/app/livechat-enterprise/server/index.js @@ -29,6 +29,7 @@ import './hooks/onCloseLivechat'; import './hooks/onSaveVisitorInfo'; import './lib/routing/LoadBalancing'; import { onLicense } from '../../license/server'; +import './business-hour'; onLicense('livechat-enterprise', () => { require('./api'); diff --git a/ee/app/livechat-enterprise/server/methods/removeBusinessHour.ts b/ee/app/livechat-enterprise/server/methods/removeBusinessHour.ts index 960211f1cf5..9498d430e25 100644 --- a/ee/app/livechat-enterprise/server/methods/removeBusinessHour.ts +++ b/ee/app/livechat-enterprise/server/methods/removeBusinessHour.ts @@ -1,14 +1,15 @@ import { Meteor } from 'meteor/meteor'; +import { Promise } from 'meteor/promise'; import { hasPermission } from '../../../../../app/authorization/server'; import { businessHourManager } from '../../../../../app/livechat/server/business-hour'; Meteor.methods({ - 'livechat:removeBusinessHour'(id: string) { + 'livechat:removeBusinessHour'(id: string, type: string) { if (!Meteor.userId() || !hasPermission(Meteor.userId(), 'view-livechat-business-hours')) { throw new Meteor.Error('error-not-allowed', 'Not allowed', { method: 'livechat:removeBusinessHour' }); } - return Promise.await(businessHourManager.removeBusinessHourById(id)); + return Promise.await(businessHourManager.removeBusinessHourByIdAndType(id, type)); }, }); diff --git a/ee/app/livechat-enterprise/server/startup.js b/ee/app/livechat-enterprise/server/startup.js index e852f250c98..e9615a5735d 100644 --- a/ee/app/livechat-enterprise/server/startup.js +++ b/ee/app/livechat-enterprise/server/startup.js @@ -4,16 +4,18 @@ import { settings } from '../../../../app/settings'; import { checkWaitingQueue, updatePredictedVisitorAbandonment } from './lib/Helper'; import { VisitorInactivityMonitor } from './lib/VisitorInactivityMonitor'; import './lib/query.helper'; -import { MultipleBusinessHours } from './business-hour/Multiple'; -import { SingleBusinessHour } from '../../../../app/livechat/server/business-hour/Single'; +import { MultipleBusinessHoursBehavior } from './business-hour/Multiple'; +import { SingleBusinessHourBehavior } from '../../../../app/livechat/server/business-hour/Single'; import { businessHourManager } from '../../../../app/livechat/server/business-hour'; +import { resetDefaultBusinessHourIfNeeded } from './business-hour/Helper'; const visitorActivityMonitor = new VisitorInactivityMonitor(); const businessHours = { - Multiple: new MultipleBusinessHours(), - Single: new SingleBusinessHour(), + Multiple: new MultipleBusinessHoursBehavior(), + Single: new SingleBusinessHourBehavior(), }; -Meteor.startup(function() { + +Meteor.startup(async function() { settings.onload('Livechat_maximum_chats_per_agent', function(/* key, value */) { checkWaitingQueue(); }); @@ -28,7 +30,8 @@ Meteor.startup(function() { updatePredictedVisitorAbandonment(); }); settings.onload('Livechat_business_hour_type', (_, value) => { - businessHourManager.registerBusinessHourMethod(businessHours[value]); - businessHourManager.dispatchOnStartTasks(); + businessHourManager.registerBusinessHourBehavior(businessHours[value]); + businessHourManager.startManager(); }); + await resetDefaultBusinessHourIfNeeded(); }); diff --git a/ee/app/models/server/raw/LivechatDepartmentAgents.ts b/ee/app/models/server/raw/LivechatDepartmentAgents.ts index 0e4ae010f35..51f4c343056 100644 --- a/ee/app/models/server/raw/LivechatDepartmentAgents.ts +++ b/ee/app/models/server/raw/LivechatDepartmentAgents.ts @@ -2,7 +2,7 @@ import { LivechatDepartmentAgentsRaw as Raw } from '../../../../../app/models/se import { LivechatDepartmentAgents } from '../../../../../app/models/server'; export class LivechatDepartmentAgentsRaw extends Raw { - findDepartmentsWithBusinessHourByAgentId(agentId: string): Promise> { + findAgentsByAgentIdAndBusinessHourId(agentId: string, businessHourId: string): Promise> { const match = { $match: { agentId }, }; @@ -20,7 +20,7 @@ export class LivechatDepartmentAgentsRaw extends Raw { preserveNullAndEmptyArrays: true, }, }; - const withBusinessHourId = { $match: { 'departments.businessHourId': { $exists: true } } }; + const withBusinessHourId = { $match: { 'departments.businessHourId': businessHourId } }; const project = { $project: { departments: 0 } }; return this.col.aggregate([match, lookup, unwind, withBusinessHourId, project]).toArray(); } diff --git a/package-lock.json b/package-lock.json index 1b5faac0c9f..656f5cfee77 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6021,6 +6021,15 @@ "@types/node": "*" } }, + "@types/moment-timezone": { + "version": "0.5.13", + "resolved": "https://registry.npmjs.org/@types/moment-timezone/-/moment-timezone-0.5.13.tgz", + "integrity": "sha512-SWk1qM8DRssS5YR9L4eEX7WUhK/wc96aIr4nMa6p0kTk9YhGGOJjECVhIdPEj13fvJw72Xun69gScXSZ/UmcPg==", + "dev": true, + "requires": { + "moment": ">=2.14.0" + } + }, "@types/mongodb": { "version": "3.5.8", "resolved": "https://registry.npmjs.org/@types/mongodb/-/mongodb-3.5.8.tgz", @@ -15499,28 +15508,28 @@ "dependencies": { "abbrev": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "resolved": false, "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", "dev": true, "optional": true }, "ansi-regex": { "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "resolved": false, "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", "dev": true, "optional": true }, "aproba": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", + "resolved": false, "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==", "dev": true, "optional": true }, "are-we-there-yet": { "version": "1.1.5", - "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz", + "resolved": false, "integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==", "dev": true, "optional": true, @@ -15531,14 +15540,14 @@ }, "balanced-match": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "resolved": false, "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", "dev": true, "optional": true }, "brace-expansion": { "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "resolved": false, "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dev": true, "optional": true, @@ -15556,28 +15565,28 @@ }, "code-point-at": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", + "resolved": false, "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", "dev": true, "optional": true }, "concat-map": { "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "resolved": false, "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", "dev": true, "optional": true }, "console-control-strings": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "resolved": false, "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=", "dev": true, "optional": true }, "core-util-is": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "resolved": false, "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", "dev": true, "optional": true @@ -15594,21 +15603,21 @@ }, "deep-extend": { "version": "0.6.0", - "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "resolved": false, "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", "dev": true, "optional": true }, "delegates": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "resolved": false, "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=", "dev": true, "optional": true }, "detect-libc": { "version": "1.0.3", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", + "resolved": false, "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=", "dev": true, "optional": true @@ -15625,14 +15634,14 @@ }, "fs.realpath": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "resolved": false, "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", "dev": true, "optional": true }, "gauge": { "version": "2.7.4", - "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", + "resolved": false, "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", "dev": true, "optional": true, @@ -15664,14 +15673,14 @@ }, "has-unicode": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "resolved": false, "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=", "dev": true, "optional": true }, "iconv-lite": { "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "resolved": false, "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", "dev": true, "optional": true, @@ -15691,7 +15700,7 @@ }, "inflight": { "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "resolved": false, "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", "dev": true, "optional": true, @@ -15709,14 +15718,14 @@ }, "ini": { "version": "1.3.5", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", + "resolved": false, "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==", "dev": true, "optional": true }, "is-fullwidth-code-point": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "resolved": false, "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", "dev": true, "optional": true, @@ -15726,14 +15735,14 @@ }, "isarray": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "resolved": false, "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", "dev": true, "optional": true }, "minimatch": { "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "resolved": false, "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", "dev": true, "optional": true, @@ -15743,7 +15752,7 @@ }, "minimist": { "version": "0.0.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "resolved": false, "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", "dev": true, "optional": true @@ -15771,7 +15780,7 @@ }, "mkdirp": { "version": "0.5.1", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "resolved": false, "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", "dev": true, "optional": true, @@ -15826,7 +15835,7 @@ }, "nopt": { "version": "4.0.1", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.1.tgz", + "resolved": false, "integrity": "sha1-0NRoWv1UFRk8jHUFYC0NF81kR00=", "dev": true, "optional": true, @@ -15855,7 +15864,7 @@ }, "npmlog": { "version": "4.1.2", - "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", + "resolved": false, "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", "dev": true, "optional": true, @@ -15868,21 +15877,21 @@ }, "number-is-nan": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", + "resolved": false, "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", "dev": true, "optional": true }, "object-assign": { "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "resolved": false, "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", "dev": true, "optional": true }, "once": { "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "resolved": false, "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", "dev": true, "optional": true, @@ -15892,21 +15901,21 @@ }, "os-homedir": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", + "resolved": false, "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", "dev": true, "optional": true }, "os-tmpdir": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "resolved": false, "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", "dev": true, "optional": true }, "osenv": { "version": "0.1.5", - "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz", + "resolved": false, "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", "dev": true, "optional": true, @@ -15917,7 +15926,7 @@ }, "path-is-absolute": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "resolved": false, "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", "dev": true, "optional": true @@ -15931,7 +15940,7 @@ }, "rc": { "version": "1.2.8", - "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "resolved": false, "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", "dev": true, "optional": true, @@ -15944,7 +15953,7 @@ "dependencies": { "minimist": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "resolved": false, "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", "dev": true, "optional": true @@ -15953,7 +15962,7 @@ }, "readable-stream": { "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "resolved": false, "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "optional": true, @@ -15979,21 +15988,21 @@ }, "safe-buffer": { "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "resolved": false, "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", "dev": true, "optional": true }, "safer-buffer": { "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "resolved": false, "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "dev": true, "optional": true }, "sax": { "version": "1.2.4", - "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", + "resolved": false, "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", "dev": true, "optional": true @@ -16007,21 +16016,21 @@ }, "set-blocking": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "resolved": false, "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", "dev": true, "optional": true }, "signal-exit": { "version": "3.0.2", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", + "resolved": false, "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", "dev": true, "optional": true }, "string-width": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "resolved": false, "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", "dev": true, "optional": true, @@ -16033,7 +16042,7 @@ }, "string_decoder": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "resolved": false, "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "optional": true, @@ -16043,7 +16052,7 @@ }, "strip-ansi": { "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "resolved": false, "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", "dev": true, "optional": true, @@ -16053,7 +16062,7 @@ }, "strip-json-comments": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "resolved": false, "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", "dev": true, "optional": true @@ -16076,14 +16085,14 @@ }, "util-deprecate": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "resolved": false, "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", "dev": true, "optional": true }, "wide-align": { "version": "1.1.3", - "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", + "resolved": false, "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", "dev": true, "optional": true, @@ -16093,7 +16102,7 @@ }, "wrappy": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "resolved": false, "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", "dev": true, "optional": true @@ -21070,7 +21079,7 @@ "dependencies": { "asn1.js": { "version": "4.10.1", - "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-4.10.1.tgz", + "resolved": false, "integrity": "sha512-p32cOF5q0Zqs9uBiONKYLm6BClCoBCM5O9JfeUSlnQLBTxYdTK+pW+nXflm8UkKd2UYlEbYz5qEi0JuZR9ckSw==", "requires": { "bn.js": "^4.0.0", @@ -21080,7 +21089,7 @@ }, "assert": { "version": "1.4.1", - "resolved": "https://registry.npmjs.org/assert/-/assert-1.4.1.tgz", + "resolved": false, "integrity": "sha1-mZEtWRg2tab1s0XA8H7vwI/GXZE=", "requires": { "util": "0.10.3" @@ -21088,7 +21097,7 @@ "dependencies": { "util": { "version": "0.10.3", - "resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz", + "resolved": false, "integrity": "sha1-evsa/lCAUkZInj23/g7TeTNqwPk=", "requires": { "inherits": "2.0.1" @@ -21098,22 +21107,22 @@ }, "base64-js": { "version": "1.3.0", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.0.tgz", + "resolved": false, "integrity": "sha512-ccav/yGvoa80BQDljCxsmmQ3Xvx60/UpBIij5QN21W3wBi/hhIC9OoO+KLpu9IJTS9j4DRVJ3aDDF9cMSoa2lw==" }, "bn.js": { "version": "4.11.8", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz", + "resolved": false, "integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==" }, "brorand": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", + "resolved": false, "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=" }, "browserify-aes": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", + "resolved": false, "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==", "requires": { "buffer-xor": "^1.0.3", @@ -21126,7 +21135,7 @@ }, "browserify-cipher": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/browserify-cipher/-/browserify-cipher-1.0.1.tgz", + "resolved": false, "integrity": "sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w==", "requires": { "browserify-aes": "^1.0.4", @@ -21136,7 +21145,7 @@ }, "browserify-des": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/browserify-des/-/browserify-des-1.0.2.tgz", + "resolved": false, "integrity": "sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A==", "requires": { "cipher-base": "^1.0.1", @@ -21147,7 +21156,7 @@ }, "browserify-rsa": { "version": "4.0.1", - "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.0.1.tgz", + "resolved": false, "integrity": "sha1-IeCr+vbyApzy+vsTNWenAdQTVSQ=", "requires": { "bn.js": "^4.1.0", @@ -21156,7 +21165,7 @@ }, "browserify-sign": { "version": "4.0.4", - "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.0.4.tgz", + "resolved": false, "integrity": "sha1-qk62jl17ZYuqa/alfmMMvXqT0pg=", "requires": { "bn.js": "^4.1.1", @@ -21170,7 +21179,7 @@ }, "browserify-zlib": { "version": "0.2.0", - "resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.2.0.tgz", + "resolved": false, "integrity": "sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA==", "requires": { "pako": "~1.0.5" @@ -21178,7 +21187,7 @@ }, "buffer": { "version": "5.2.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.2.1.tgz", + "resolved": false, "integrity": "sha512-c+Ko0loDaFfuPWiL02ls9Xd3GO3cPVmUobQ6t3rXNUk304u6hGq+8N/kFi+QEIKhzK3uwolVhLzszmfLmMLnqg==", "requires": { "base64-js": "^1.0.2", @@ -21187,17 +21196,17 @@ }, "buffer-xor": { "version": "1.0.3", - "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz", + "resolved": false, "integrity": "sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk=" }, "builtin-status-codes": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz", + "resolved": false, "integrity": "sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug=" }, "cipher-base": { "version": "1.0.4", - "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz", + "resolved": false, "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==", "requires": { "inherits": "^2.0.1", @@ -21206,7 +21215,7 @@ }, "console-browserify": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.1.0.tgz", + "resolved": false, "integrity": "sha1-8CQcRXMKn8YyOyBtvzjtx0HQuxA=", "requires": { "date-now": "^0.1.4" @@ -21214,17 +21223,17 @@ }, "constants-browserify": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/constants-browserify/-/constants-browserify-1.0.0.tgz", + "resolved": false, "integrity": "sha1-wguW2MYXdIqvHBYCF2DNJ/y4y3U=" }, "core-util-is": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "resolved": false, "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" }, "create-ecdh": { "version": "4.0.3", - "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.3.tgz", + "resolved": false, "integrity": "sha512-GbEHQPMOswGpKXM9kCWVrremUcBmjteUaQ01T9rkKCPDXfUHX0IoP9LpHYo2NPFampa4e+/pFDc3jQdxrxQLaw==", "requires": { "bn.js": "^4.1.0", @@ -21233,7 +21242,7 @@ }, "create-hash": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", + "resolved": false, "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", "requires": { "cipher-base": "^1.0.1", @@ -21245,7 +21254,7 @@ }, "create-hmac": { "version": "1.1.7", - "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", + "resolved": false, "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==", "requires": { "cipher-base": "^1.0.3", @@ -21258,7 +21267,7 @@ }, "crypto-browserify": { "version": "3.12.0", - "resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.12.0.tgz", + "resolved": false, "integrity": "sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg==", "requires": { "browserify-cipher": "^1.0.0", @@ -21276,12 +21285,12 @@ }, "date-now": { "version": "0.1.4", - "resolved": "https://registry.npmjs.org/date-now/-/date-now-0.1.4.tgz", + "resolved": false, "integrity": "sha1-6vQ5/U1ISK105cx9vvIAZyueNFs=" }, "des.js": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.0.0.tgz", + "resolved": false, "integrity": "sha1-wHTS4qpqipoH29YfmhXCzYPsjsw=", "requires": { "inherits": "^2.0.1", @@ -21290,7 +21299,7 @@ }, "diffie-hellman": { "version": "5.0.3", - "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz", + "resolved": false, "integrity": "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==", "requires": { "bn.js": "^4.1.0", @@ -21300,12 +21309,12 @@ }, "domain-browser": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.2.0.tgz", + "resolved": false, "integrity": "sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA==" }, "elliptic": { "version": "6.4.1", - "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.4.1.tgz", + "resolved": false, "integrity": "sha512-BsXLz5sqX8OHcsh7CqBMztyXARmGQ3LWPtGjJi6DiJHq5C/qvi9P3OqgswKSDftbu8+IoI/QDTAm2fFnQ9SZSQ==", "requires": { "bn.js": "^4.4.0", @@ -21319,12 +21328,12 @@ }, "events": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/events/-/events-3.0.0.tgz", + "resolved": false, "integrity": "sha512-Dc381HFWJzEOhQ+d8pkNon++bk9h6cdAoAj4iE6Q4y6xgTzySWXlKn05/TVNpjnfRqi/X0EpJEJohPjNI3zpVA==" }, "evp_bytestokey": { "version": "1.0.3", - "resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz", + "resolved": false, "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==", "requires": { "md5.js": "^1.3.4", @@ -21333,7 +21342,7 @@ }, "hash-base": { "version": "3.0.4", - "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.0.4.tgz", + "resolved": false, "integrity": "sha1-X8hoaEfs1zSZQDMZprCj8/auSRg=", "requires": { "inherits": "^2.0.1", @@ -21342,7 +21351,7 @@ }, "hash.js": { "version": "1.1.7", - "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", + "resolved": false, "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", "requires": { "inherits": "^2.0.3", @@ -21351,14 +21360,14 @@ "dependencies": { "inherits": { "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "resolved": false, "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" } } }, "hmac-drbg": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", + "resolved": false, "integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=", "requires": { "hash.js": "^1.0.3", @@ -21368,27 +21377,27 @@ }, "https-browserify": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz", + "resolved": false, "integrity": "sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=" }, "ieee754": { "version": "1.1.13", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz", + "resolved": false, "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==" }, "inherits": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz", + "resolved": false, "integrity": "sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE=" }, "isarray": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "resolved": false, "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" }, "md5.js": { "version": "1.3.5", - "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", + "resolved": false, "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==", "requires": { "hash-base": "^3.0.0", @@ -21398,7 +21407,7 @@ }, "miller-rabin": { "version": "4.0.1", - "resolved": "https://registry.npmjs.org/miller-rabin/-/miller-rabin-4.0.1.tgz", + "resolved": false, "integrity": "sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA==", "requires": { "bn.js": "^4.0.0", @@ -21407,27 +21416,27 @@ }, "minimalistic-assert": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "resolved": false, "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==" }, "minimalistic-crypto-utils": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", + "resolved": false, "integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=" }, "os-browserify": { "version": "0.3.0", - "resolved": "https://registry.npmjs.org/os-browserify/-/os-browserify-0.3.0.tgz", + "resolved": false, "integrity": "sha1-hUNzx/XCMVkU/Jv8a9gjj92h7Cc=" }, "pako": { "version": "1.0.10", - "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.10.tgz", + "resolved": false, "integrity": "sha512-0DTvPVU3ed8+HNXOu5Bs+o//Mbdj9VNQMUOe9oKCwh8l0GNwpTDMKCWbRjgtD291AWnkAgkqA/LOnQS8AmS1tw==" }, "parse-asn1": { "version": "5.1.4", - "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.4.tgz", + "resolved": false, "integrity": "sha512-Qs5duJcuvNExRfFZ99HDD3z4mAi3r9Wl/FOjEOijlxwCZs7E7mW2vjTpgQ4J8LpTF8x5v+1Vn5UQFejmWT11aw==", "requires": { "asn1.js": "^4.0.0", @@ -21440,12 +21449,12 @@ }, "path-browserify": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.0.tgz", + "resolved": false, "integrity": "sha512-Hkavx/nY4/plImrZPHRk2CL9vpOymZLgEbMNX1U0bjcBL7QN9wODxyx0yaMZURSQaUtSEvDrfAvxa9oPb0at9g==" }, "pbkdf2": { "version": "3.0.17", - "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.0.17.tgz", + "resolved": false, "integrity": "sha512-U/il5MsrZp7mGg3mSQfn742na2T+1/vHDCG5/iTI3X9MKUuYUZVLQhyRsg06mCgDBTd57TxzgZt7P+fYfjRLtA==", "requires": { "create-hash": "^1.1.2", @@ -21457,17 +21466,17 @@ }, "process": { "version": "0.11.10", - "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "resolved": false, "integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI=" }, "process-nextick-args": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", + "resolved": false, "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==" }, "public-encrypt": { "version": "4.0.3", - "resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.3.tgz", + "resolved": false, "integrity": "sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q==", "requires": { "bn.js": "^4.1.0", @@ -21480,22 +21489,22 @@ }, "punycode": { "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "resolved": false, "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" }, "querystring": { "version": "0.2.0", - "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", + "resolved": false, "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=" }, "querystring-es3": { "version": "0.2.1", - "resolved": "https://registry.npmjs.org/querystring-es3/-/querystring-es3-0.2.1.tgz", + "resolved": false, "integrity": "sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM=" }, "randombytes": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "resolved": false, "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", "requires": { "safe-buffer": "^5.1.0" @@ -21503,7 +21512,7 @@ }, "randomfill": { "version": "1.0.4", - "resolved": "https://registry.npmjs.org/randomfill/-/randomfill-1.0.4.tgz", + "resolved": false, "integrity": "sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw==", "requires": { "randombytes": "^2.0.5", @@ -21512,7 +21521,7 @@ }, "readable-stream": { "version": "3.3.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.3.0.tgz", + "resolved": false, "integrity": "sha512-EsI+s3k3XsW+fU8fQACLN59ky34AZ14LoeVZpYwmZvldCFo0r0gnelwF2TcMjLor/BTL5aDJVBMkss0dthToPw==", "requires": { "inherits": "^2.0.3", @@ -21522,14 +21531,14 @@ "dependencies": { "inherits": { "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "resolved": false, "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" } } }, "ripemd160": { "version": "2.0.2", - "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz", + "resolved": false, "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==", "requires": { "hash-base": "^3.0.0", @@ -21538,17 +21547,17 @@ }, "safe-buffer": { "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "resolved": false, "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" }, "setimmediate": { "version": "1.0.5", - "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", + "resolved": false, "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=" }, "sha.js": { "version": "2.4.11", - "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", + "resolved": false, "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", "requires": { "inherits": "^2.0.1", @@ -21557,7 +21566,7 @@ }, "stream-browserify": { "version": "2.0.2", - "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.2.tgz", + "resolved": false, "integrity": "sha512-nX6hmklHs/gr2FuxYDltq8fJA1GDlxKQCz8O/IM4atRqBH8OORmBNgfvW5gG10GT/qQ9u0CzIvr2X5Pkt6ntqg==", "requires": { "inherits": "~2.0.1", @@ -21566,7 +21575,7 @@ "dependencies": { "readable-stream": { "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "resolved": false, "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "requires": { "core-util-is": "~1.0.0", @@ -21580,14 +21589,14 @@ "dependencies": { "inherits": { "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "resolved": false, "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" } } }, "string_decoder": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "resolved": false, "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "requires": { "safe-buffer": "~5.1.0" @@ -21597,7 +21606,7 @@ }, "stream-http": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/stream-http/-/stream-http-3.0.0.tgz", + "resolved": false, "integrity": "sha512-JELJfd+btL9GHtxU3+XXhg9NLYrKFnhybfvRuDghtyVkOFydz3PKNT1df07AMr88qW03WHF+FSV0PySpXignCA==", "requires": { "builtin-status-codes": "^3.0.0", @@ -21608,7 +21617,7 @@ }, "string_decoder": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.2.0.tgz", + "resolved": false, "integrity": "sha512-6YqyX6ZWEYguAxgZzHGL7SsCeGx3V2TtOTqZz1xSTSWnqsbWwbptafNyvf/ACquZUXV3DANr5BDIwNYe1mN42w==", "requires": { "safe-buffer": "~5.1.0" @@ -21616,7 +21625,7 @@ }, "timers-browserify": { "version": "2.0.10", - "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-2.0.10.tgz", + "resolved": false, "integrity": "sha512-YvC1SV1XdOUaL6gx5CoGroT3Gu49pK9+TZ38ErPldOWW4j49GI1HKs9DV+KGq/w6y+LZ72W1c8cKz2vzY+qpzg==", "requires": { "setimmediate": "^1.0.4" @@ -21624,12 +21633,12 @@ }, "tty-browserify": { "version": "0.0.1", - "resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.1.tgz", + "resolved": false, "integrity": "sha512-C3TaO7K81YvjCgQH9Q1S3R3P3BtN3RIM8n+OvX4il1K1zgE8ZhI0op7kClgkxtutIE8hQrcrHBXvIheqKUUCxw==" }, "url": { "version": "0.11.0", - "resolved": "https://registry.npmjs.org/url/-/url-0.11.0.tgz", + "resolved": false, "integrity": "sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE=", "requires": { "punycode": "1.3.2", @@ -21638,14 +21647,14 @@ "dependencies": { "punycode": { "version": "1.3.2", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", + "resolved": false, "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=" } } }, "util": { "version": "0.11.1", - "resolved": "https://registry.npmjs.org/util/-/util-0.11.1.tgz", + "resolved": false, "integrity": "sha512-HShAsny+zS2TZfaXxD9tYj4HQGlBezXZMZuM/S5PKLLoZkShZiGk9o5CzukI1LVHZvjdvZ2Sj1aW/Ndn2NB/HQ==", "requires": { "inherits": "2.0.3" @@ -21653,24 +21662,24 @@ "dependencies": { "inherits": { "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "resolved": false, "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" } } }, "util-deprecate": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "resolved": false, "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" }, "vm-browserify": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-1.1.0.tgz", + "resolved": false, "integrity": "sha512-iq+S7vZJE60yejDYM0ek6zg308+UZsdtPExWP9VZoCFCz1zkJoXFnAX7aZfd/ZwrkidzdUZL0C/ryW+JwAiIGw==" }, "xtend": { "version": "4.0.1", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", + "resolved": false, "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=" } } diff --git a/package.json b/package.json index c6af3f2ddc7..3e3baaeec24 100644 --- a/package.json +++ b/package.json @@ -68,6 +68,7 @@ "@types/meteor": "^1.4.37", "@types/mocha": "^7.0.2", "@types/mock-require": "^2.0.0", + "@types/moment-timezone": "^0.5.13", "@types/mongodb": "^3.5.8", "@types/react-dom": "^16.9.8", "@typescript-eslint/eslint-plugin": "^2.11.0", diff --git a/server/main.d.ts b/server/main.d.ts index 33f7c642a1f..d1d7791954d 100644 --- a/server/main.d.ts +++ b/server/main.d.ts @@ -57,3 +57,21 @@ declare module 'meteor/rocketchat:tap-i18n' { function __(s: string, options: { lng: string }): string; } } + +declare module 'meteor/promise' { + namespace Promise { + function await(): any; + } +} + +declare module 'meteor/littledata:synced-cron' { + interface ICronAddParameters { + name: string; + schedule: Function; + job: Function; + } + namespace SyncedCron { + function add(params: ICronAddParameters): string; + function remove(name: string): string; + } +} diff --git a/server/startup/migrations/index.js b/server/startup/migrations/index.js index 8511de4c36c..b97123b4706 100644 --- a/server/startup/migrations/index.js +++ b/server/startup/migrations/index.js @@ -193,4 +193,5 @@ import './v193'; import './v194'; import './v195'; import './v196'; +import './v197'; import './xrun'; diff --git a/server/startup/migrations/v195.js b/server/startup/migrations/v195.js index 37890e53c6e..1d46731795b 100644 --- a/server/startup/migrations/v195.js +++ b/server/startup/migrations/v195.js @@ -1,19 +1,31 @@ -import moment from 'moment'; +import moment from 'moment-timezone'; import { ObjectId } from 'mongodb'; import { Mongo } from 'meteor/mongo'; import { Migrations } from '../../../app/migrations/server'; import { Permissions, Settings } from '../../../app/models/server'; import { LivechatBusinessHours } from '../../../app/models/server/raw'; -import { LivechatBussinessHourTypes } from '../../../definition/ILivechatBusinessHour'; +import { LivechatBusinessHourTypes } from '../../../definition/ILivechatBusinessHour'; const migrateCollection = () => { const LivechatOfficeHour = new Mongo.Collection('rocketchat_livechat_office_hour'); - const officeHours = Promise.await(LivechatOfficeHour.rawCollection().find().toArray()); + const days = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday']; + const officeHours = []; + days.forEach((day) => { + const officeHour = LivechatOfficeHour.findOne({ day }); + if (officeHour) { + officeHours.push(officeHour); + } + }); + + if (!officeHours || officeHours.length === 0) { + return; + } + const businessHour = { name: '', active: true, - type: LivechatBussinessHourTypes.SINGLE, + type: LivechatBusinessHourTypes.DEFAULT, ts: new Date(), workHours: officeHours.map((officeHour) => ({ day: officeHour.day, @@ -29,7 +41,7 @@ const migrateCollection = () => { }, }, finish: { - time: '20:00', + time: officeHour.finish, utc: { dayOfWeek: moment(`${ officeHour.day }:${ officeHour.finish }`, 'dddd:HH:mm').utc().format('dddd'), time: moment(`${ officeHour.day }:${ officeHour.finish }`, 'dddd:HH:mm').utc().format('HH:mm'), @@ -43,15 +55,15 @@ const migrateCollection = () => { open: officeHour.open, })), timezone: { - name: '', + name: moment.tz.guess(), utc: moment().utcOffset() / 60, }, }; - if (LivechatBusinessHours.find({ type: LivechatBussinessHourTypes.SINGLE }).count() === 0) { + if (LivechatBusinessHours.find({ type: LivechatBusinessHourTypes.DEFAULT }).count() === 0) { businessHour._id = new ObjectId().toHexString(); LivechatBusinessHours.insertOne(businessHour); } else { - LivechatBusinessHours.update({ type: LivechatBussinessHourTypes.SINGLE }, businessHour); + LivechatBusinessHours.update({ type: LivechatBusinessHourTypes.DEFAULT }, { $set: { ...businessHour } }); } try { Promise.await(LivechatOfficeHour.rawCollection().drop()); diff --git a/server/startup/migrations/v197.js b/server/startup/migrations/v197.js new file mode 100644 index 00000000000..d7b18048583 --- /dev/null +++ b/server/startup/migrations/v197.js @@ -0,0 +1,35 @@ +import moment from 'moment-timezone'; + +import { Migrations } from '../../../app/migrations/server'; +import { LivechatBusinessHours } from '../../../app/models/server/raw'; +import { LivechatBusinessHourTypes } from '../../../definition/ILivechatBusinessHour'; + +const updateBusinessHours = async () => { + await LivechatBusinessHours.update({ type: 'multiple' }, { + $set: { + type: LivechatBusinessHourTypes.CUSTOM, + }, + }, { multi: true }); + + const defaultBusinessHour = await LivechatBusinessHours.findOne({ $or: [{ type: 'single' }, { type: 'default' }] }); + if (!defaultBusinessHour) { + return; + } + + await LivechatBusinessHours.update({ _id: defaultBusinessHour._id }, { + $set: { + type: LivechatBusinessHourTypes.DEFAULT, + timezone: { + name: moment.tz.guess(), + utc: String(moment().utcOffset() / 60), + }, + }, + }); +}; + +Migrations.add({ + version: 197, + up() { + Promise.await(updateBusinessHours()); + }, +}); diff --git a/typings.d.ts b/typings.d.ts index c653f645ee8..7a199164872 100644 --- a/typings.d.ts +++ b/typings.d.ts @@ -1,4 +1,6 @@ declare module 'meteor/rocketchat:tap-i18n'; +declare module 'meteor/littledata:synced-cron'; +declare module 'meteor/promise'; declare module 'meteor/ddp-common'; declare module 'meteor/routepolicy'; declare module 'xml-encryption';