Skip to content

Commit

Permalink
feat: Allow managing association to business units on departments' cr…
Browse files Browse the repository at this point in the history
…eation and update (#32682)
  • Loading branch information
matheusbsilva137 committed Sep 18, 2024
1 parent 4202d65 commit 9a38c8e
Show file tree
Hide file tree
Showing 18 changed files with 982 additions and 49 deletions.
7 changes: 7 additions & 0 deletions .changeset/dirty-stingrays-beg.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@rocket.chat/meteor": minor
"@rocket.chat/model-typings": minor
"@rocket.chat/rest-typings": minor
---

Added support for specifying a unit on departments' creation and update
15 changes: 12 additions & 3 deletions apps/meteor/app/livechat/imports/server/rest/departments.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,10 +57,18 @@ API.v1.addRoute(
check(this.bodyParams, {
department: Object,
agents: Match.Maybe(Array),
departmentUnit: Match.Maybe({ _id: Match.Optional(String) }),
});

const agents = this.bodyParams.agents ? { upsert: this.bodyParams.agents } : {};
const department = await LivechatTs.saveDepartment(null, this.bodyParams.department as ILivechatDepartment, agents);
const { departmentUnit } = this.bodyParams;
const department = await LivechatTs.saveDepartment(
this.userId,
null,
this.bodyParams.department as ILivechatDepartment,
agents,
departmentUnit || {},
);

if (department) {
return API.v1.success({
Expand Down Expand Up @@ -112,17 +120,18 @@ API.v1.addRoute(
check(this.bodyParams, {
department: Object,
agents: Match.Maybe(Array),
departmentUnit: Match.Maybe({ _id: Match.Optional(String) }),
});

const { _id } = this.urlParams;
const { department, agents } = this.bodyParams;
const { department, agents, departmentUnit } = this.bodyParams;

if (!permissionToSave) {
throw new Error('error-not-allowed');
}

const agentParam = permissionToAddAgents && agents ? { upsert: agents } : {};
await LivechatTs.saveDepartment(_id, department, agentParam);
await LivechatTs.saveDepartment(this.userId, _id, department, agentParam, departmentUnit || {});

return API.v1.success({
department: await LivechatDepartment.findOneById(_id),
Expand Down
25 changes: 24 additions & 1 deletion apps/meteor/app/livechat/server/lib/LivechatTyped.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1789,18 +1789,37 @@ class LivechatClass {
* @param {string|null} _id - The department id
* @param {Partial<import('@rocket.chat/core-typings').ILivechatDepartment>} departmentData
* @param {{upsert?: { agentId: string; count?: number; order?: number; }[], remove?: { agentId: string; count?: number; order?: number; }}} [departmentAgents] - The department agents
* @param {{_id?: string}} [departmentUnit] - The department's unit id
*/
async saveDepartment(
userId: string,
_id: string | null,
departmentData: LivechatDepartmentDTO,
departmentAgents?: {
upsert?: { agentId: string; count?: number; order?: number }[];
remove?: { agentId: string; count?: number; order?: number };
},
departmentUnit?: { _id?: string },
) {
check(_id, Match.Maybe(String));
if (departmentUnit?._id !== undefined && typeof departmentUnit._id !== 'string') {
throw new Meteor.Error('error-invalid-department-unit', 'Invalid department unit id provided', {
method: 'livechat:saveDepartment',
});
}

const department = _id ? await LivechatDepartment.findOneById(_id, { projection: { _id: 1, archived: 1, enabled: 1 } }) : null;
const department = _id
? await LivechatDepartment.findOneById(_id, { projection: { _id: 1, archived: 1, enabled: 1, parentId: 1 } })
: null;

if (departmentUnit && !departmentUnit._id && department && department.parentId) {
const isLastDepartmentInUnit = (await LivechatDepartment.countDepartmentsInUnit(department.parentId)) === 1;
if (isLastDepartmentInUnit) {
throw new Meteor.Error('error-unit-cant-be-empty', "The last department in a unit can't be removed", {
method: 'livechat:saveDepartment',
});
}
}

if (!department && !(await isDepartmentCreationAvailable())) {
throw new Meteor.Error('error-max-departments-number-reached', 'Maximum number of departments reached', {
Expand Down Expand Up @@ -1887,6 +1906,10 @@ class LivechatClass {
await callbacks.run('livechat.afterDepartmentDisabled', departmentDB);
}

if (departmentUnit) {
await callbacks.run('livechat.manageDepartmentUnit', { userId, departmentId: departmentDB._id, unitId: departmentUnit._id });
}

return departmentDB;
}
}
Expand Down
5 changes: 3 additions & 2 deletions apps/meteor/app/livechat/server/methods/saveDepartment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,19 +30,20 @@ declare module '@rocket.chat/ddp-client' {
order?: number | undefined;
}[]
| undefined,
departmentUnit?: { _id?: string },
) => ILivechatDepartment;
}
}

Meteor.methods<ServerMethods>({
async 'livechat:saveDepartment'(_id, departmentData, departmentAgents) {
async 'livechat:saveDepartment'(_id, departmentData, departmentAgents, departmentUnit) {
const uid = Meteor.userId();
if (!uid || !(await hasPermissionAsync(uid, 'manage-livechat-departments'))) {
throw new Meteor.Error('error-not-allowed', 'Not allowed', {
method: 'livechat:saveDepartment',
});
}

return Livechat.saveDepartment(_id, departmentData, { upsert: departmentAgents });
return Livechat.saveDepartment(uid, _id, departmentData, { upsert: departmentAgents }, departmentUnit);
},
});
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,4 @@ import './afterInquiryQueued';
import './sendPdfTranscriptOnClose';
import './applyRoomRestrictions';
import './afterTagRemoved';
import './manageDepartmentUnit';
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import type { ILivechatDepartment, IOmnichannelBusinessUnit } from '@rocket.chat/core-typings';
import { LivechatDepartment, LivechatUnit } from '@rocket.chat/models';

import { hasAnyRoleAsync } from '../../../../../app/authorization/server/functions/hasRole';
import { callbacks } from '../../../../../lib/callbacks';
import { getUnitsFromUser } from '../methods/getUnitsFromUserRoles';

export const manageDepartmentUnit = async ({ userId, departmentId, unitId }: { userId: string; departmentId: string; unitId: string }) => {
const accessibleUnits = await getUnitsFromUser(userId);
const isLivechatManager = await hasAnyRoleAsync(userId, ['admin', 'livechat-manager']);
const department = await LivechatDepartment.findOneById<Pick<ILivechatDepartment, '_id' | 'ancestors' | 'parentId'>>(departmentId, {
projection: { ancestors: 1, parentId: 1 },
});

const isDepartmentAlreadyInUnit = unitId && department?.ancestors?.includes(unitId);
if (!department || isDepartmentAlreadyInUnit) {
return;
}

const currentDepartmentUnitId = department.parentId;
const canManageNewUnit = !unitId || isLivechatManager || (Array.isArray(accessibleUnits) && accessibleUnits.includes(unitId));
const canManageCurrentUnit =
!currentDepartmentUnitId || isLivechatManager || (Array.isArray(accessibleUnits) && accessibleUnits.includes(currentDepartmentUnitId));
if (!canManageNewUnit || !canManageCurrentUnit) {
return;
}

if (unitId) {
const unit = await LivechatUnit.findOneById<Pick<IOmnichannelBusinessUnit, '_id' | 'ancestors'>>(unitId, {
projection: { ancestors: 1 },
});

if (!unit) {
return;
}

if (currentDepartmentUnitId) {
await LivechatUnit.decrementDepartmentsCount(currentDepartmentUnitId);
}

await LivechatDepartment.addDepartmentToUnit(departmentId, unitId, [unitId, ...(unit.ancestors || [])]);
await LivechatUnit.incrementDepartmentsCount(unitId);
return;
}

if (currentDepartmentUnitId) {
await LivechatUnit.decrementDepartmentsCount(currentDepartmentUnitId);
}

await LivechatDepartment.removeDepartmentFromUnit(departmentId);
};

callbacks.add('livechat.manageDepartmentUnit', manageDepartmentUnit, callbacks.priority.HIGH, 'livechat-manage-department-unit');
29 changes: 10 additions & 19 deletions apps/meteor/ee/server/models/raw/LivechatUnit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ const addQueryRestrictions = async (originalQuery: Filter<IOmnichannelBusinessUn

const units = await getUnitsFromUser();
if (Array.isArray(units)) {
query.ancestors = { $in: units };
const expressions = query.$and || [];
const condition = { $or: [{ ancestors: { $in: units } }, { _id: { $in: units } }] };
query.$and = [condition, ...expressions];
Expand Down Expand Up @@ -109,28 +108,12 @@ export class LivechatUnitRaw extends BaseRaw<IOmnichannelBusinessUnit> implement
// remove other departments
for await (const departmentId of savedDepartments) {
if (!departmentsToSave.includes(departmentId)) {
await LivechatDepartment.updateOne(
{ _id: departmentId },
{
$set: {
parentId: null,
ancestors: null,
},
},
);
await LivechatDepartment.removeDepartmentFromUnit(departmentId);
}
}

for await (const departmentId of departmentsToSave) {
await LivechatDepartment.updateOne(
{ _id: departmentId },
{
$set: {
parentId: _id,
ancestors,
},
},
);
await LivechatDepartment.addDepartmentToUnit(departmentId, _id, ancestors);
}

await LivechatRooms.associateRoomsWithDepartmentToUnit(departmentsToSave, _id);
Expand All @@ -154,6 +137,14 @@ export class LivechatUnitRaw extends BaseRaw<IOmnichannelBusinessUnit> implement
return this.updateMany(query, update);
}

incrementDepartmentsCount(_id: string): Promise<UpdateResult | Document> {
return this.updateOne({ _id }, { $inc: { numDepartments: 1 } });
}

decrementDepartmentsCount(_id: string): Promise<UpdateResult | Document> {
return this.updateOne({ _id }, { $inc: { numDepartments: -1 } });
}

async removeById(_id: string): Promise<DeleteResult> {
await LivechatUnitMonitors.removeByUnitId(_id);
await this.removeParentAndAncestorById(_id);
Expand Down
Loading

0 comments on commit 9a38c8e

Please sign in to comment.