diff --git a/.changeset/few-dryers-repeat.md b/.changeset/few-dryers-repeat.md new file mode 100644 index 0000000000000..d22d669fc370f --- /dev/null +++ b/.changeset/few-dryers-repeat.md @@ -0,0 +1,7 @@ +--- +"@rocket.chat/meteor": patch +"@rocket.chat/model-typings": patch +"@rocket.chat/models": patch +--- + +Allows agents to set a default agent when the chat being transferred ends up in the queue diff --git a/apps/meteor/app/livechat/server/lib/Helper.ts b/apps/meteor/app/livechat/server/lib/Helper.ts index af99db22d380d..dd901e1db3e04 100644 --- a/apps/meteor/app/livechat/server/lib/Helper.ts +++ b/apps/meteor/app/livechat/server/lib/Helper.ts @@ -657,7 +657,7 @@ export const forwardRoomToDepartment = async (room: IOmnichannelRoom, guest: ILi isWaitingQueueEnabled, }); await saveTransferHistory(room, transferData); - return RoutingManager.unassignAgent(inquiry, departmentId, true); + return RoutingManager.unassignAgent(inquiry, departmentId, true, agent); } // Fake the department to forward the inquiry - Case the forward process does not success diff --git a/apps/meteor/app/livechat/server/lib/RoutingManager.ts b/apps/meteor/app/livechat/server/lib/RoutingManager.ts index be18f07b08f95..3596296de1a30 100644 --- a/apps/meteor/app/livechat/server/lib/RoutingManager.ts +++ b/apps/meteor/app/livechat/server/lib/RoutingManager.ts @@ -48,7 +48,12 @@ type Routing = { options?: { clientAction?: boolean; forwardingToDepartment?: { oldDepartmentId?: string; transferData?: any } }, room?: IOmnichannelRoom, ): Promise<(IOmnichannelRoom & { chatQueued?: boolean }) | null | void>; - unassignAgent(inquiry: ILivechatInquiryRecord, departmentId?: string, shouldQueue?: boolean): Promise; + unassignAgent( + inquiry: ILivechatInquiryRecord, + departmentId?: string, + shouldQueue?: boolean, + agent?: SelectedAgent | null, + ): Promise; takeInquiry( inquiry: Omit< ILivechatInquiryRecord, @@ -157,11 +162,19 @@ export const RoutingManager: Routing = { return { inquiry, user }; }, - async unassignAgent(inquiry, departmentId, shouldQueue = false) { + async unassignAgent(inquiry, departmentId, shouldQueue = false, defaultAgent?: SelectedAgent | null) { const { rid, department } = inquiry; const room = await LivechatRooms.findOneById(rid); - logger.debug(`Removing assignations of inquiry ${inquiry._id}`); + logger.debug({ + msg: 'Removing assignations of inquiry', + inquiryId: inquiry._id, + departmentId, + room: { _id: room?._id, open: room?.open, servedBy: room?.servedBy }, + shouldQueue, + defaultAgent, + }); + if (!room?.open) { logger.debug(`Cannot unassign agent from inquiry ${inquiry._id}: Room already closed`); return false; @@ -187,7 +200,7 @@ export const RoutingManager: Routing = { } if (shouldQueue) { - const queuedInquiry = await LivechatInquiry.queueInquiry(inquiry._id, room.lastMessage); + const queuedInquiry = await LivechatInquiry.queueInquiry(inquiry._id, room.lastMessage, defaultAgent); if (queuedInquiry) { inquiry = queuedInquiry; void notifyOnLivechatInquiryChanged(inquiry, 'updated', { diff --git a/apps/meteor/tests/end-to-end/api/livechat/00-rooms.ts b/apps/meteor/tests/end-to-end/api/livechat/00-rooms.ts index 91aeb058ae925..875fb59ac17f3 100644 --- a/apps/meteor/tests/end-to-end/api/livechat/00-rooms.ts +++ b/apps/meteor/tests/end-to-end/api/livechat/00-rooms.ts @@ -1278,6 +1278,44 @@ describe('LIVECHAT - rooms', () => { ]); }); + (IS_EE ? it : it.skip)( + 'when manager forwards to department & passes an agent on the request while waiting queue is active, the inquiry should be set to the queue with default agent', + async () => { + const { department: initialDepartment } = await createDepartmentWithAnOnlineAgent(); + const { department: forwardToOfflineDepartment, agent: onlineAgent } = await createDepartmentWithAnOnlineAgent(); + await updateSetting('Livechat_waiting_queue', true); + + const newVisitor = await createVisitor(initialDepartment._id); + const newRoom = await createLivechatRoom(newVisitor.token); + + const manager = await createUser(); + const managerCredentials = await login(manager.username, password); + await createManager(manager.username); + + await request.post(api('livechat/room.forward')).set(managerCredentials).send({ + roomId: newRoom._id, + departmentId: forwardToOfflineDepartment._id, + userId: onlineAgent.user._id, + clientAction: true, + comment: 'test comment', + }); + + const inquiry = await fetchInquiry(newRoom._id); + + expect(inquiry).to.have.property('status', 'queued'); + expect(inquiry) + .to.have.property('defaultAgent') + .to.deep.equal({ agentId: onlineAgent.user._id, username: onlineAgent.user.username }); + + await Promise.all([ + updateSetting('Livechat_waiting_queue', false), + deleteDepartment(initialDepartment._id), + deleteDepartment(forwardToOfflineDepartment._id), + closeOmnichannelRoom(newRoom._id), + ]); + }, + ); + (IS_EE ? it : it.skip)( 'when manager forward to offline (agent away, accept when agent idle off) department the inquiry should be set to the queue', async () => { diff --git a/packages/model-typings/src/models/ILivechatInquiryModel.ts b/packages/model-typings/src/models/ILivechatInquiryModel.ts index 13fb344fd8eaa..3397e7da16f65 100644 --- a/packages/model-typings/src/models/ILivechatInquiryModel.ts +++ b/packages/model-typings/src/models/ILivechatInquiryModel.ts @@ -1,4 +1,4 @@ -import type { IMessage, ILivechatInquiryRecord, LivechatInquiryStatus } from '@rocket.chat/core-typings'; +import type { IMessage, ILivechatInquiryRecord, LivechatInquiryStatus, SelectedAgent } from '@rocket.chat/core-typings'; import type { FindOptions, Document, UpdateResult, DeleteResult, FindCursor, DeleteOptions, AggregateOptions } from 'mongodb'; import type { IBaseModel } from './IBaseModel'; @@ -33,7 +33,7 @@ export interface ILivechatInquiryModel extends IBaseModel): FindCursor; takeInquiry(inquiryId: string): Promise; openInquiry(inquiryId: string): Promise; - queueInquiry(inquiryId: string, lastMessage?: IMessage): Promise; + queueInquiry(inquiryId: string, lastMessage?: IMessage, defaultAgent?: SelectedAgent | null): Promise; queueInquiryAndRemoveDefaultAgent(inquiryId: string): Promise; readyInquiry(inquiryId: string): Promise; changeDepartmentIdByRoomId(rid: string, department: string): Promise; diff --git a/packages/models/src/models/LivechatInquiry.ts b/packages/models/src/models/LivechatInquiry.ts index d3c9b18bc0b93..d65b7c26e3e35 100644 --- a/packages/models/src/models/LivechatInquiry.ts +++ b/packages/models/src/models/LivechatInquiry.ts @@ -1,4 +1,10 @@ -import type { ILivechatInquiryRecord, IMessage, RocketChatRecordDeleted, ILivechatPriority } from '@rocket.chat/core-typings'; +import type { + ILivechatInquiryRecord, + IMessage, + RocketChatRecordDeleted, + ILivechatPriority, + SelectedAgent, +} from '@rocket.chat/core-typings'; import { LivechatInquiryStatus } from '@rocket.chat/core-typings'; import type { ILivechatInquiryModel } from '@rocket.chat/model-typings'; import type { @@ -331,7 +337,11 @@ export class LivechatInquiryRaw extends BaseRaw implemen ); } - async queueInquiry(inquiryId: string, lastMessage?: IMessage): Promise { + async queueInquiry( + inquiryId: string, + lastMessage?: IMessage, + defaultAgent?: SelectedAgent | null, + ): Promise { return this.findOneAndUpdate( { _id: inquiryId, @@ -341,6 +351,7 @@ export class LivechatInquiryRaw extends BaseRaw implemen status: LivechatInquiryStatus.QUEUED, queuedAt: new Date(), ...(lastMessage && { lastMessage }), + ...(defaultAgent && { defaultAgent }), }, $unset: { takenAt: 1 }, },