Skip to content
Merged
6 changes: 6 additions & 0 deletions .changeset/calm-rabbits-sin.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@rocket.chat/livechat': patch
'@rocket.chat/meteor': patch
---

Fixes livechat routing algorithm to ensure conversations are correctly assigned to the contact manager when triggers and/or automatic agent routing are enabled.
11 changes: 5 additions & 6 deletions apps/meteor/app/livechat/server/lib/LivechatTyped.ts
Original file line number Diff line number Diff line change
Expand Up @@ -156,12 +156,11 @@ class LivechatClass {
throw new Error('error-contact-channel-blocked');
}

const defaultAgent =
agent ??
(await callbacks.run('livechat.checkDefaultAgentOnNewRoom', agent, {
visitorId: visitor._id,
source: roomInfo.source,
}));
const defaultAgent = await callbacks.run('livechat.checkDefaultAgentOnNewRoom', agent, {
visitorId: visitor._id,
source: roomInfo.source,
});

// if no department selected verify if there is at least one active and pick the first
if (!defaultAgent && !visitor.department) {
const department = await getRequiredDepartment();
Expand Down
19 changes: 18 additions & 1 deletion apps/meteor/app/livechat/server/lib/Visitors.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { UserStatus, type ILivechatVisitor } from '@rocket.chat/core-typings';
import { Logger } from '@rocket.chat/logger';
import { LivechatDepartment, LivechatVisitors } from '@rocket.chat/models';
import { LivechatContacts, LivechatDepartment, LivechatVisitors, Users } from '@rocket.chat/models';

import { validateEmail } from './Helper';
import { settings } from '../../../settings/server';
Expand Down Expand Up @@ -46,6 +46,23 @@ export const Visitors = {
const visitorEmail = email.trim().toLowerCase();
validateEmail(visitorEmail);
visitorDataToUpdate.visitorEmails = [{ address: visitorEmail }];

const contact = await LivechatContacts.findContactByEmailAndContactManager(visitorEmail);
if (contact?.contactManager) {
const shouldConsiderIdleAgent = settings.get<boolean>('Livechat_enabled_when_agent_idle');
const agent = await Users.findOneOnlineAgentById(contact.contactManager, shouldConsiderIdleAgent, {
projection: { _id: 1, username: 1, name: 1, emails: 1 },
});
if (agent && agent.username && agent.name && agent.emails) {
visitorDataToUpdate.contactManager = {
_id: agent._id,
username: agent.username,
name: agent.name,
emails: agent.emails,
};
logger.debug(`Assigning visitor ${token} to agent ${agent.username}`);
}
}
}

const livechatVisitor = await LivechatVisitors.getVisitorByToken(token, { projection: { _id: 1 } });
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ settings.watch<boolean>('Omnichannel_contact_manager_routing', (value) => {
callbacks.add(
'livechat.checkDefaultAgentOnNewRoom',
async (defaultAgent, { visitorId, source } = {}) => {
if (defaultAgent || !visitorId || !source) {
if (!visitorId || !source) {
return defaultAgent;
}

Expand All @@ -104,6 +104,11 @@ callbacks.add(
return undefined;
}

const hasDivergentContactManager = defaultAgent?.agentId !== guest?.contactManager;
if (!hasDivergentContactManager && defaultAgent) {
return defaultAgent;
}

const contactId = await migrateVisitorIfMissingContact(visitorId, source);
const contact = contactId ? await LivechatContacts.findOneById(contactId, { projection: { contactManager: 1 } }) : undefined;

Expand Down
3 changes: 3 additions & 0 deletions packages/core-typings/src/ILivechatVisitor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,10 @@ export interface ILivechatVisitor extends IRocketChatRecord {
};
livechatData?: ILivechatData;
contactManager?: {
_id?: string;
username: string;
name?: string;
emails?: { address: string }[];
};
activity?: string[];
disabled?: boolean;
Expand Down
5 changes: 4 additions & 1 deletion packages/livechat/src/routes/Register/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,10 @@ export const Register: FunctionalComponent<{ path: string }> = () => {

try {
const { visitor: user } = await Livechat.grantVisitor({ visitor: { ...fields, token } });
await dispatch({ user } as Omit<StoreState['user'], 'ts'>);
await dispatch({
user,
...(user.contactManager && { agent: user.contactManager }),
} as Omit<StoreState['user'], 'ts'>);

parentCall('callback', 'pre-chat-form-submit', fields);
Triggers.callbacks?.emit('chat-visitor-registered');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ export interface ILivechatContactsModel extends IBaseModel<ILivechatContact> {
lastChat: ILivechatContact['lastChat'],
): Promise<UpdateResult>;
findContactMatchingVisitor(visitor: AtLeast<ILivechatVisitor, 'visitorEmails' | 'phone'>): Promise<ILivechatContact | null>;
findContactByEmailAndContactManager(email: string): Promise<Pick<ILivechatContact, 'contactManager'> | null>;
findOneByVisitor<T extends Document = ILivechatContact>(
visitor: ILivechatContactVisitorAssociation,
options?: FindOptions<ILivechatContact>,
Expand Down
7 changes: 7 additions & 0 deletions packages/models/src/models/LivechatContacts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,13 @@ export class LivechatContactsRaw extends BaseRaw<ILivechatContact> implements IL
return this.findOne(query);
}

async findContactByEmailAndContactManager(email: string): Promise<Pick<ILivechatContact, 'contactManager'> | null> {
return this.findOne(
{ emails: { $elemMatch: { address: email } }, contactManager: { $exists: true } },
{ projection: { contactManager: 1 } },
);
}

private makeQueryForVisitor(
visitor: ILivechatContactVisitorAssociation,
extraFilters?: Filter<Required<ILivechatContact>['channels'][number]>,
Expand Down
Loading