From 393e613068c032bd316512302563eae236257ada Mon Sep 17 00:00:00 2001 From: Kevin Aleman Date: Wed, 24 Jul 2024 06:13:13 -0600 Subject: [PATCH] feat: Allow admins to decide if email transcript should be sent always (#32820) Co-authored-by: Marcos Spessatto Defendi Co-authored-by: Guilherme Gazzo --- .changeset/shaggy-hats-raise.md | 5 + .../app/livechat/server/lib/LivechatTyped.ts | 49 +++---- .../app/livechat/server/lib/localTypes.ts | 31 +++++ .../server/lib/parseTranscriptRequest.ts | 61 +++++++++ .../Omnichannel/modals/CloseChatModal.tsx | 7 +- .../OmnichannelPreferencesPage.tsx | 9 +- .../PreferencesConversationTranscript.tsx | 8 +- apps/meteor/server/settings/omnichannel.ts | 13 +- .../server/lib/parseTranscriptRequest.spec.ts | 127 ++++++++++++++++++ packages/i18n/src/locales/en.i18n.json | 4 +- packages/i18n/src/locales/es.i18n.json | 2 +- packages/i18n/src/locales/fi.i18n.json | 1 - packages/i18n/src/locales/pt-BR.i18n.json | 2 + packages/i18n/src/locales/ru.i18n.json | 1 - packages/i18n/src/locales/sv.i18n.json | 1 - 15 files changed, 273 insertions(+), 48 deletions(-) create mode 100644 .changeset/shaggy-hats-raise.md create mode 100644 apps/meteor/app/livechat/server/lib/localTypes.ts create mode 100644 apps/meteor/app/livechat/server/lib/parseTranscriptRequest.ts create mode 100644 apps/meteor/tests/unit/app/livechat/server/lib/parseTranscriptRequest.spec.ts diff --git a/.changeset/shaggy-hats-raise.md b/.changeset/shaggy-hats-raise.md new file mode 100644 index 000000000000..40ee9f8fbb55 --- /dev/null +++ b/.changeset/shaggy-hats-raise.md @@ -0,0 +1,5 @@ +--- +"@rocket.chat/meteor": minor +--- + +Added a new setting `Livechat_transcript_send_always` that allows admins to decide if email transcript should be sent all the times when a conversation is closed. This setting bypasses agent's preferences. For this setting to work, `Livechat_enable_transcript` should be off, meaning that visitors will no longer receive the option to decide if they want a transcript or not. diff --git a/apps/meteor/app/livechat/server/lib/LivechatTyped.ts b/apps/meteor/app/livechat/server/lib/LivechatTyped.ts index 8330cdc9dc90..ccca7a8eb68e 100644 --- a/apps/meteor/app/livechat/server/lib/LivechatTyped.ts +++ b/apps/meteor/app/livechat/server/lib/LivechatTyped.ts @@ -71,6 +71,8 @@ import { parseAgentCustomFields, updateDepartmentAgents, validateEmail, normaliz import { QueueManager } from './QueueManager'; import { RoutingManager } from './RoutingManager'; import { isDepartmentCreationAvailable } from './isDepartmentCreationAvailable'; +import type { CloseRoomParams, CloseRoomParamsByUser, CloseRoomParamsByVisitor } from './localTypes'; +import { parseTranscriptRequest } from './parseTranscriptRequest'; import { sendTranscript as sendTranscriptFunc } from './sendTranscript'; type RegisterGuestType = Partial> & { @@ -80,36 +82,6 @@ type RegisterGuestType = Partial; - }; - pdfTranscript?: { - requestedBy: string; - }; - }; -}; - -export type CloseRoomParamsByUser = { - user: IUser | null; -} & GenericCloseRoomParams; - -export type CloseRoomParamsByVisitor = { - visitor: ILivechatVisitor; -} & GenericCloseRoomParams; - -export type CloseRoomParams = CloseRoomParamsByUser | CloseRoomParamsByVisitor; - type OfflineMessageData = { message: string; name: string; @@ -324,6 +296,9 @@ class LivechatClass { this.logger.debug(`DB updated for room ${room._id}`); + const transcriptRequested = + !!transcriptRequest || (!settings.get('Livechat_enable_transcript') && settings.get('Livechat_transcript_send_always')); + // Retrieve the closed room const newRoom = await LivechatRooms.findOneById(rid); @@ -338,13 +313,15 @@ class LivechatClass { t: 'livechat-close', msg: comment, groupable: false, - transcriptRequested: !!transcriptRequest, + transcriptRequested, ...(isRoomClosedByVisitorParams(params) && { token: chatCloser.token }), }, newRoom, ); - await Message.saveSystemMessage('command', rid, 'promptTranscript', closeData.closedBy); + if (settings.get('Livechat_enable_transcript') && !settings.get('Livechat_transcript_send_always')) { + await Message.saveSystemMessage('command', rid, 'promptTranscript', closeData.closedBy); + } this.logger.debug(`Running callbacks for room ${newRoom._id}`); @@ -356,15 +333,18 @@ class LivechatClass { void Apps.self?.getBridges()?.getListenerBridge().livechatEvent(AppEvents.ILivechatRoomClosedHandler, newRoom); void Apps.self?.getBridges()?.getListenerBridge().livechatEvent(AppEvents.IPostLivechatRoomClosed, newRoom); }); + + const visitor = isRoomClosedByVisitorParams(params) ? params.visitor : undefined; + const opts = await parseTranscriptRequest(params.room, options, visitor); if (process.env.TEST_MODE) { await callbacks.run('livechat.closeRoom', { room: newRoom, - options, + options: opts, }); } else { callbacks.runAsync('livechat.closeRoom', { room: newRoom, - options, + options: opts, }); } @@ -1880,3 +1860,4 @@ class LivechatClass { } export const Livechat = new LivechatClass(); +export * from './localTypes'; diff --git a/apps/meteor/app/livechat/server/lib/localTypes.ts b/apps/meteor/app/livechat/server/lib/localTypes.ts new file mode 100644 index 000000000000..c6acbbc5bcbd --- /dev/null +++ b/apps/meteor/app/livechat/server/lib/localTypes.ts @@ -0,0 +1,31 @@ +import type { IOmnichannelRoom, IUser, ILivechatVisitor } from '@rocket.chat/core-typings'; + +export type GenericCloseRoomParams = { + room: IOmnichannelRoom; + comment?: string; + options?: { + clientAction?: boolean; + tags?: string[]; + emailTranscript?: + | { + sendToVisitor: false; + } + | { + sendToVisitor: true; + requestData: NonNullable; + }; + pdfTranscript?: { + requestedBy: string; + }; + }; +}; + +export type CloseRoomParamsByUser = { + user: IUser | null; +} & GenericCloseRoomParams; + +export type CloseRoomParamsByVisitor = { + visitor: ILivechatVisitor; +} & GenericCloseRoomParams; + +export type CloseRoomParams = CloseRoomParamsByUser | CloseRoomParamsByVisitor; diff --git a/apps/meteor/app/livechat/server/lib/parseTranscriptRequest.ts b/apps/meteor/app/livechat/server/lib/parseTranscriptRequest.ts new file mode 100644 index 000000000000..76595a7ff640 --- /dev/null +++ b/apps/meteor/app/livechat/server/lib/parseTranscriptRequest.ts @@ -0,0 +1,61 @@ +import type { ILivechatVisitor, IOmnichannelRoom, IUser } from '@rocket.chat/core-typings'; +import { LivechatVisitors, Users } from '@rocket.chat/models'; + +import { settings } from '../../../settings/server'; +import type { CloseRoomParams } from './localTypes'; + +export const parseTranscriptRequest = async ( + room: IOmnichannelRoom, + options: CloseRoomParams['options'], + visitor?: ILivechatVisitor, + user?: IUser, +): Promise => { + const visitorDecideTranscript = settings.get('Livechat_enable_transcript'); + // visitor decides, no changes + if (visitorDecideTranscript) { + return options; + } + + // send always is disabled, no changes + const sendAlways = settings.get('Livechat_transcript_send_always'); + if (!sendAlways) { + return options; + } + + const visitorData = + visitor || + (await LivechatVisitors.findOneById>(room.v._id, { projection: { visitorEmails: 1 } })); + // no visitor, no changes + if (!visitorData) { + return options; + } + const visitorEmail = visitorData?.visitorEmails?.[0]?.address; + // visitor doesnt have email, no changes + if (!visitorEmail) { + return options; + } + + const defOptions = { projection: { _id: 1, username: 1, name: 1 } }; + const requestedBy = + user || + (room.servedBy && (await Users.findOneById(room.servedBy._id, defOptions))) || + (await Users.findOneById('rocket.cat', defOptions)); + + // no user available for backing request, no changes + if (!requestedBy) { + return options; + } + + return { + ...options, + emailTranscript: { + sendToVisitor: true, + requestData: { + email: visitorEmail, + requestedAt: new Date(), + subject: '', + requestedBy, + }, + }, + }; +}; diff --git a/apps/meteor/client/components/Omnichannel/modals/CloseChatModal.tsx b/apps/meteor/client/components/Omnichannel/modals/CloseChatModal.tsx index e74007e71cfd..7c028fb5c876 100644 --- a/apps/meteor/client/components/Omnichannel/modals/CloseChatModal.tsx +++ b/apps/meteor/client/components/Omnichannel/modals/CloseChatModal.tsx @@ -52,6 +52,7 @@ const CloseChatModal = ({ } = useForm(); const commentRequired = useSetting('Livechat_request_comment_when_closing_conversation') as boolean; + const alwaysSendTranscript = useSetting('Livechat_transcript_send_always'); const customSubject = useSetting('Livechat_transcript_email_subject'); const [tagRequired, setTagRequired] = useState(false); @@ -66,7 +67,7 @@ const CloseChatModal = ({ const transcriptPDFPermission = usePermission('request-pdf-transcript'); const transcriptEmailPermission = usePermission('send-omnichannel-chat-transcript'); - const canSendTranscriptEmail = transcriptEmailPermission && visitorEmail; + const canSendTranscriptEmail = transcriptEmailPermission && visitorEmail && !alwaysSendTranscript; const canSendTranscriptPDF = transcriptPDFPermission && hasLicense; const canSendTranscript = canSendTranscriptEmail || canSendTranscriptPDF; @@ -78,7 +79,7 @@ const CloseChatModal = ({ ({ comment, tags, transcriptPDF, transcriptEmail, subject }): void => { const preferences = { omnichannelTranscriptPDF: !!transcriptPDF, - omnichannelTranscriptEmail: !!transcriptEmail, + omnichannelTranscriptEmail: alwaysSendTranscript ? true : !!transcriptEmail, }; const requestData = transcriptEmail && visitorEmail ? { email: visitorEmail, subject } : undefined; @@ -98,7 +99,7 @@ const CloseChatModal = ({ onConfirm(comment, tags, preferences, requestData); } }, - [commentRequired, tagRequired, visitorEmail, errors, setError, t, onConfirm], + [commentRequired, tagRequired, visitorEmail, errors, setError, t, onConfirm, alwaysSendTranscript], ); const cannotSubmit = useMemo(() => { diff --git a/apps/meteor/client/views/account/omnichannel/OmnichannelPreferencesPage.tsx b/apps/meteor/client/views/account/omnichannel/OmnichannelPreferencesPage.tsx index 9abec01df1d0..8489b3cb4b8b 100644 --- a/apps/meteor/client/views/account/omnichannel/OmnichannelPreferencesPage.tsx +++ b/apps/meteor/client/views/account/omnichannel/OmnichannelPreferencesPage.tsx @@ -1,5 +1,5 @@ import { ButtonGroup, Button, Box, Accordion } from '@rocket.chat/fuselage'; -import { useToastMessageDispatch, useTranslation, useEndpoint, useUserPreference } from '@rocket.chat/ui-contexts'; +import { useToastMessageDispatch, useTranslation, useEndpoint, useUserPreference, useSetting } from '@rocket.chat/ui-contexts'; import type { ReactElement } from 'react'; import React from 'react'; import { useForm, FormProvider } from 'react-hook-form'; @@ -17,12 +17,17 @@ const OmnichannelPreferencesPage = (): ReactElement => { const t = useTranslation(); const dispatchToastMessage = useToastMessageDispatch(); + const alwaysSendEmailTranscript = useSetting('Livechat_transcript_send_always'); const omnichannelTranscriptPDF = useUserPreference('omnichannelTranscriptPDF') ?? false; const omnichannelTranscriptEmail = useUserPreference('omnichannelTranscriptEmail') ?? false; const omnichannelHideConversationAfterClosing = useUserPreference('omnichannelHideConversationAfterClosing') ?? true; const methods = useForm({ - defaultValues: { omnichannelTranscriptPDF, omnichannelTranscriptEmail, omnichannelHideConversationAfterClosing }, + defaultValues: { + omnichannelTranscriptPDF, + omnichannelTranscriptEmail: alwaysSendEmailTranscript || omnichannelTranscriptEmail, + omnichannelHideConversationAfterClosing, + }, }); const { diff --git a/apps/meteor/client/views/account/omnichannel/PreferencesConversationTranscript.tsx b/apps/meteor/client/views/account/omnichannel/PreferencesConversationTranscript.tsx index 11bf6634a0e9..a003861c8d57 100644 --- a/apps/meteor/client/views/account/omnichannel/PreferencesConversationTranscript.tsx +++ b/apps/meteor/client/views/account/omnichannel/PreferencesConversationTranscript.tsx @@ -1,6 +1,6 @@ import { Accordion, Box, Field, FieldGroup, FieldLabel, FieldRow, FieldHint, Tag, ToggleSwitch } from '@rocket.chat/fuselage'; import { useUniqueId } from '@rocket.chat/fuselage-hooks'; -import { useTranslation, usePermission } from '@rocket.chat/ui-contexts'; +import { useTranslation, usePermission, useSetting } from '@rocket.chat/ui-contexts'; import React from 'react'; import { useFormContext } from 'react-hook-form'; @@ -12,8 +12,10 @@ const PreferencesConversationTranscript = () => { const { register } = useFormContext(); const hasLicense = useHasLicenseModule('livechat-enterprise'); + const alwaysSendEmailTranscript = useSetting('Livechat_transcript_send_always'); const canSendTranscriptPDF = usePermission('request-pdf-transcript'); - const canSendTranscriptEmail = usePermission('send-omnichannel-chat-transcript'); + const canSendTranscriptEmailPermission = usePermission('send-omnichannel-chat-transcript'); + const canSendTranscriptEmail = canSendTranscriptEmailPermission && !alwaysSendEmailTranscript; const cantSendTranscriptPDF = !canSendTranscriptPDF || !hasLicense; const omnichannelTranscriptPDF = useUniqueId(); @@ -42,7 +44,7 @@ const PreferencesConversationTranscript = () => { {t('Omnichannel_transcript_email')} - {!canSendTranscriptEmail && ( + {!canSendTranscriptEmailPermission && ( {t('No_permission')} diff --git a/apps/meteor/server/settings/omnichannel.ts b/apps/meteor/server/settings/omnichannel.ts index bd7ac6f5cd82..ed1daa8ce228 100644 --- a/apps/meteor/server/settings/omnichannel.ts +++ b/apps/meteor/server/settings/omnichannel.ts @@ -404,12 +404,23 @@ export const createOmniSettings = () => enableQuery: [{ _id: 'FileUpload_Enabled', value: true }, omnichannelEnabledQuery], }); + // Making these 2 settings "depend" on each other + // Prevents us from having both as true and then asking visitor if it wants a Transcript + // But send it anyways because of send_always being enabled. So one can only be turned on + // if the other is off. await this.add('Livechat_enable_transcript', false, { type: 'boolean', group: 'Omnichannel', public: true, i18nLabel: 'Transcript_Enabled', - enableQuery: omnichannelEnabledQuery, + enableQuery: [{ _id: 'Livechat_transcript_send_always', value: false }, omnichannelEnabledQuery], + }); + + await this.add('Livechat_transcript_send_always', false, { + type: 'boolean', + group: 'Omnichannel', + public: true, + enableQuery: [{ _id: 'Livechat_enable_transcript', value: false }, omnichannelEnabledQuery], }); await this.add('Livechat_transcript_show_system_messages', false, { diff --git a/apps/meteor/tests/unit/app/livechat/server/lib/parseTranscriptRequest.spec.ts b/apps/meteor/tests/unit/app/livechat/server/lib/parseTranscriptRequest.spec.ts new file mode 100644 index 000000000000..a1b2ce52d1a3 --- /dev/null +++ b/apps/meteor/tests/unit/app/livechat/server/lib/parseTranscriptRequest.spec.ts @@ -0,0 +1,127 @@ +import { expect } from 'chai'; +import p from 'proxyquire'; +import sinon from 'sinon'; + +const modelsMock = { + Users: { + findOneById: sinon.stub(), + }, + LivechatVisitors: { + findOneById: sinon.stub(), + }, +}; + +const settingsGetMock = { + get: sinon.stub(), +}; + +const { parseTranscriptRequest } = p.noCallThru().load('../../../../../../app/livechat/server/lib/parseTranscriptRequest', { + '@rocket.chat/models': modelsMock, + '../../../settings/server': { settings: settingsGetMock }, +}); + +describe('parseTranscriptRequest', () => { + beforeEach(() => { + settingsGetMock.get.reset(); + modelsMock.Users.findOneById.reset(); + modelsMock.LivechatVisitors.findOneById.reset(); + }); + + it('should return `options` param with no changes when Livechat_enable_transcript setting is true', async () => { + settingsGetMock.get.withArgs('Livechat_enable_transcript').returns(true); + const options = await parseTranscriptRequest({} as any, {} as any); + expect(options).to.be.deep.equal({}); + }); + + it('should return `options` param with no changes when send always is disabled', async () => { + settingsGetMock.get.withArgs('Livechat_enable_transcript').returns(false); + settingsGetMock.get.withArgs('Livechat_transcript_send_always').returns(false); + const options = await parseTranscriptRequest({} as any, {} as any); + expect(options).to.be.deep.equal({}); + }); + + it('should return `options` param with no changes when visitor is not provided and its not found on db', async () => { + settingsGetMock.get.withArgs('Livechat_enable_transcript').returns(false); + settingsGetMock.get.withArgs('Livechat_transcript_send_always').returns(true); + modelsMock.LivechatVisitors.findOneById.resolves(null); + + const options = await parseTranscriptRequest({ v: { _id: '123' } } as any, {} as any); + expect(options).to.be.deep.equal({}); + }); + + it('should return `options` param with no changes when visitor is passed but no email is found', async () => { + settingsGetMock.get.withArgs('Livechat_enable_transcript').returns(false); + settingsGetMock.get.withArgs('Livechat_transcript_send_always').returns(true); + modelsMock.LivechatVisitors.findOneById.resolves({} as any); + + const options = await parseTranscriptRequest({ v: { _id: '123' } } as any, {} as any, { _id: '123' } as any); + expect(options).to.be.deep.equal({}); + }); + + it('should return `options` param with no changes when visitor is fetched from db, but no email is found', async () => { + settingsGetMock.get.withArgs('Livechat_enable_transcript').returns(false); + settingsGetMock.get.withArgs('Livechat_transcript_send_always').returns(true); + modelsMock.LivechatVisitors.findOneById.resolves({} as any); + + const options = await parseTranscriptRequest({ v: { _id: '123' } } as any, {} as any); + expect(options).to.be.deep.equal({}); + }); + + it('should return `options` param with no changes when no user is passed, room is not being served and rocketcat is not present', async () => { + settingsGetMock.get.withArgs('Livechat_enable_transcript').returns(false); + settingsGetMock.get.withArgs('Livechat_transcript_send_always').returns(true); + modelsMock.Users.findOneById.resolves(null); + modelsMock.LivechatVisitors.findOneById.resolves({ visitorEmails: [{ address: 'abc@rocket.chat' }] } as any); + + const options = await parseTranscriptRequest({ v: { _id: '123' } } as any, {} as any); + expect(options).to.be.deep.equal({}); + }); + + it('should return `options` param with `transcriptRequest` key attached when user is passed', async () => { + settingsGetMock.get.withArgs('Livechat_enable_transcript').returns(false); + settingsGetMock.get.withArgs('Livechat_transcript_send_always').returns(true); + modelsMock.LivechatVisitors.findOneById.resolves({ visitorEmails: [{ address: 'abc@rocket.chat' }] } as any); + + const options = await parseTranscriptRequest({ v: { _id: '123' } } as any, {} as any, undefined, { _id: '123' } as any); + + expect(modelsMock.LivechatVisitors.findOneById.getCall(0).firstArg).to.be.equal('123'); + expect(options).to.have.property('emailTranscript').that.is.an('object'); + expect(options.emailTranscript.requestData).to.have.property('email', 'abc@rocket.chat'); + expect(options.emailTranscript.requestData).to.have.property('subject', ''); + expect(options.emailTranscript.requestData.requestedBy).to.be.deep.equal({ _id: '123' }); + }); + + it('should return `options` param with `transcriptRequest` key attached when no user is passed, but theres an agent serving the room', async () => { + settingsGetMock.get.withArgs('Livechat_enable_transcript').returns(false); + settingsGetMock.get.withArgs('Livechat_transcript_send_always').returns(true); + modelsMock.Users.findOneById.resolves({ _id: '123', username: 'kevsxxx', name: 'Kev' } as any); + modelsMock.LivechatVisitors.findOneById.resolves({ visitorEmails: [{ address: 'abc@rocket.chat' }] } as any); + + const options = await parseTranscriptRequest({ v: { _id: '123' }, servedBy: { _id: '123' } } as any, {} as any); + + expect(modelsMock.Users.findOneById.getCall(0).firstArg).to.be.equal('123'); + expect(options).to.have.property('emailTranscript').that.is.an('object'); + expect(options.emailTranscript.requestData).to.have.property('email', 'abc@rocket.chat'); + expect(options.emailTranscript.requestData).to.have.property('subject', ''); + expect(options.emailTranscript.requestData.requestedBy).to.be.deep.equal({ _id: '123', username: 'kevsxxx', name: 'Kev' }); + }); + + it('should return `options` param with `transcriptRequest` key attached when no user is passed, no agent is serving but rocket.cat is present', async () => { + settingsGetMock.get.withArgs('Livechat_enable_transcript').returns(false); + settingsGetMock.get.withArgs('Livechat_transcript_send_always').returns(true); + modelsMock.Users.findOneById.resolves({ _id: 'rocket.cat', username: 'rocket.cat', name: 'Rocket Cat' } as any); + modelsMock.LivechatVisitors.findOneById.resolves({ visitorEmails: [{ address: 'abc@rocket.chat' }] } as any); + + const options = await parseTranscriptRequest({ v: { _id: '123' } } as any, {} as any); + + expect(modelsMock.Users.findOneById.getCall(0).firstArg).to.be.equal('rocket.cat'); + expect(options).to.have.property('emailTranscript').that.is.an('object'); + expect(options.emailTranscript.requestData).to.have.property('email', 'abc@rocket.chat'); + expect(options.emailTranscript.requestData).to.have.property('subject', ''); + expect(options.emailTranscript.requestData.requestedBy).to.be.deep.equal({ + _id: 'rocket.cat', + username: 'rocket.cat', + name: 'Rocket Cat', + }); + }); +}); diff --git a/packages/i18n/src/locales/en.i18n.json b/packages/i18n/src/locales/en.i18n.json index 6e98436712e4..27c8f2ecec62 100644 --- a/packages/i18n/src/locales/en.i18n.json +++ b/packages/i18n/src/locales/en.i18n.json @@ -3272,6 +3272,8 @@ "Livechat_email_transcript_has_been_requested": "The transcript has been requested. It may take a few seconds.", "Livechat_transcript_request_has_been_canceled": "The chat transcription request has been canceled.", "Livechat_transcript_sent": "Omnichannel transcript sent", + "Livechat_transcript_send_always": "Always send conversation transcript to visitors via email", + "Livechat_transcript_send_always_Description": "Once finished, send conversation transcript via email to visitors automatically, regardless of agent's preferences.", "Livechat_transcript_email_subject": "Custom email subject for transcript", "Livechat_transcript_email_subject_Description": "Allows to customize the email subject for transcripts sent via email. It can be overriden by passing a `subject` property when closing a room. Leave it empty to use default subject.", "Livechat_transfer_return_to_the_queue": "{{from}} returned the chat to the queue", @@ -6273,7 +6275,7 @@ "Always_send_the_transcript_to_contacts_at_the_end_of_the_conversations": "Always send the transcript to contacts at the end of the conversations.", "Export_conversation_transcript_as_PDF": "Export conversation transcript as PDF", "Omnichannel_transcript_email": "Send chat transcript via email.", - "Accounts_Default_User_Preferences_omnichannelTranscriptEmail_Description": "Always send the transcript to contacts at the end of the conversations.", + "Accounts_Default_User_Preferences_omnichannelTranscriptEmail_Description": "Always send the transcript to contacts at the end of the conversations. This preference may be overriden by an admin setting.", "Omnichannel_transcript_pdf": "Export chat transcript as PDF.", "Accounts_Default_User_Preferences_omnichannelTranscriptPDF_Description": "Always export the transcript as PDF at the end of conversations.", "Contact_email": "Contact email", diff --git a/packages/i18n/src/locales/es.i18n.json b/packages/i18n/src/locales/es.i18n.json index 6ddcf2b6479f..c556b470a372 100644 --- a/packages/i18n/src/locales/es.i18n.json +++ b/packages/i18n/src/locales/es.i18n.json @@ -4936,7 +4936,7 @@ "Always_send_the_transcript_to_contacts_at_the_end_of_the_conversations": "Envía siempre la transcripción a los contactos al final de las conversaciones.", "Export_conversation_transcript_as_PDF": "Exportar la transcripción de la conversación en PDF", "Omnichannel_transcript_email": "Enviar la transcripción del chat por correo electrónico.", - "Accounts_Default_User_Preferences_omnichannelTranscriptEmail_Description": "Envía siempre la transcripción a los contactos al final de las conversaciones.", + "Accounts_Default_User_Preferences_omnichannelTranscriptEmail_Description": "Envía siempre la transcripción a los contactos al final de las conversaciones. Esta preferncia puede ser sobreescrita por un administrador.", "Omnichannel_transcript_pdf": "Exporta la transcripción del chat en PDF.", "Accounts_Default_User_Preferences_omnichannelTranscriptPDF_Description": "Exporte siempre la transcripción en PDF al final de las conversaciones.", "Customer": "Cliente", diff --git a/packages/i18n/src/locales/fi.i18n.json b/packages/i18n/src/locales/fi.i18n.json index c8078bb2545f..fcc101ca57f7 100644 --- a/packages/i18n/src/locales/fi.i18n.json +++ b/packages/i18n/src/locales/fi.i18n.json @@ -5660,7 +5660,6 @@ "Always_send_the_transcript_to_contacts_at_the_end_of_the_conversations": "Lähetä tekstitallenne aina yhteyshenkilöille keskustelujen lopuksi.", "Export_conversation_transcript_as_PDF": "Vie keskustelun tekstitallenne PDF-muotoon", "Omnichannel_transcript_email": "Lähetä keskustelun tekstitallenne sähköpostitse.", - "Accounts_Default_User_Preferences_omnichannelTranscriptEmail_Description": "Lähetä tekstitallenne aina yhteyshenkilöille keskustelujen lopuksi.", "Omnichannel_transcript_pdf": "Vie keskustelun tekstitallenne PDF-muotoon.", "Accounts_Default_User_Preferences_omnichannelTranscriptPDF_Description": "Vie tekstitallenne aina PDF-muotoon keskustelujen lopuksi.", "Contact_email": "Yhteyshenkilön sähköpostiosoite", diff --git a/packages/i18n/src/locales/pt-BR.i18n.json b/packages/i18n/src/locales/pt-BR.i18n.json index d56243509469..6e5a59bda0a5 100644 --- a/packages/i18n/src/locales/pt-BR.i18n.json +++ b/packages/i18n/src/locales/pt-BR.i18n.json @@ -2713,6 +2713,8 @@ "Livechat_transcript_has_been_requested": "A transcrição da conversa foi solicitada.", "Livechat_transcript_request_has_been_canceled": "A solicitação da transcrição da conversa foi cancelada.", "Livechat_transcript_sent": "Transcrição de omnichannel enviada", + "Livechat_transcript_send_always": "Sempre enviar transcrição de conversas aos visitantes via email", + "Livechat_transcript_send_always_Description": "Assim que finalizada, enviar a transcrição da conversa via email aos visitantes automaticamente, independentemente das permissões do agente.", "Livechat_transfer_return_to_the_queue": "{{from}} retornou à conversa para a fila", "Livechat_transfer_to_agent": "{{from}} transferiu a conversa para {{to}}", "Livechat_transfer_to_agent_with_a_comment": "{{from}} transferiu a conversa para {{to}} com um comentário: {{comment}}", diff --git a/packages/i18n/src/locales/ru.i18n.json b/packages/i18n/src/locales/ru.i18n.json index f768764d0bc1..6f7b69447e29 100644 --- a/packages/i18n/src/locales/ru.i18n.json +++ b/packages/i18n/src/locales/ru.i18n.json @@ -5073,7 +5073,6 @@ "Conversational_transcript": "Экспорт содержимого диалога", "Always_send_the_transcript_to_contacts_at_the_end_of_the_conversations": "Всегда отправлять содержимое чата контактам по окончанию диалога.", "Omnichannel_transcript_email": "Отправить содержимое диалога по электронной почте.", - "Accounts_Default_User_Preferences_omnichannelTranscriptEmail_Description": "Всегда отправлять содержимое чата контактам по окончанию диалога.", "Omnichannel_transcript_pdf": "Экспорт содержимого чата в PDF.", "Accounts_Default_User_Preferences_omnichannelTranscriptPDF_Description": "Всегда экспортировать содержимое чата по окончанию диалога.", "User_Status": "Пользовательский статус", diff --git a/packages/i18n/src/locales/sv.i18n.json b/packages/i18n/src/locales/sv.i18n.json index aafb5a36c9dc..3436669c7e65 100644 --- a/packages/i18n/src/locales/sv.i18n.json +++ b/packages/i18n/src/locales/sv.i18n.json @@ -5664,7 +5664,6 @@ "Always_send_the_transcript_to_contacts_at_the_end_of_the_conversations": "Skicka alltid utskriften till kontakterna i slutet av samtalen.", "Export_conversation_transcript_as_PDF": "Exportera samtalsutskrift som PDF", "Omnichannel_transcript_email": "Skicka utskrift av chatten via e-post.", - "Accounts_Default_User_Preferences_omnichannelTranscriptEmail_Description": "Skicka alltid utskriften till kontakterna i slutet av samtalen.", "Omnichannel_transcript_pdf": "Exportera chattutskrift som PDF.", "Accounts_Default_User_Preferences_omnichannelTranscriptPDF_Description": "Exportera alltid utskriften som PDF-fil i slutet av samtalen.", "Contact_email": "Kontakt e-post",