使用 Salesforce Omni-Channel API 实现程序化座席状态管理

背景与应用场景

作为一名 Salesforce 开发人员,我们经常需要处理复杂的业务需求,尤其是在 Service Cloud 实施项目中。Omni-Channel(全渠道)是 Service Cloud 的核心功能之一,它能够智能地将工单 (Cases)、潜在客户 (Leads) 或其他工作项 (Work Items) 实时推送给最合适、最空闲的座席。标准的 Omni-Channel 小组件 (Widget) 提供了开箱即用的功能,允许座席更改他们的在线状态 (Presence Status) 并接受或拒绝工作。

然而,在许多企业级应用场景中,标准组件的功能可能不足以满足定制化的需求。例如:

  • 统一的座席工作台:企业希望构建一个高度定制化的 Lightning 页面或应用,将 Omni-Channel 的状态控制与 CTI 软电话、知识库文章 (Knowledge Articles) 和其他第三方系统集成在同一个界面中,而不是让座席在多个小组件之间切换。
  • 流程自动化:在某些业务流程中,我们希望根据座席的特定操作自动更新其状态。例如,当座席点击“处理完成”按钮提交一个复杂的工单时,系统自动将其状态从“忙碌”切换到“总结中 (Wrap-Up)”,并在设定的时间后自动变回“在线 (Online)”。
  • 与外部系统同步:当企业使用外部的呼叫中心系统时,可能需要将座席在外部系统的状态(如“通话中”)与 Salesforce Omni-Channel 的状态进行同步,确保工作分配的一致性。

为了应对这些挑战,Salesforce 提供了 Omni-Channel Toolkit API。这是一个强大的客户端 JavaScript API,允许我们作为开发人员,通过编写 Lightning Web Components (LWC) 来程序化地控制 Omni-Channel 的各种行为,从而实现深度定制和流程自动化。本文将深入探讨如何利用此 API 来构建灵活、高效的座席状态管理解决方案。


原理说明

Omni-Channel Toolkit API 是一组专为 Lightning Experience 设计的 JavaScript 方法和事件。它被封装在 lightning/omniToolkitApi 模块中,只能在 LWC 中使用,并且该 LWC 必须运行在支持 Omni-Channel 的环境中,例如 Salesforce Console(控制台应用)。

它的核心工作原理是提供一个与 Salesforce 后端 Omni-Channel 引擎交互的客户端接口。当我们调用 API 中的方法时(例如,更改座席状态),LWC 会通过这个接口向 Salesforce 前端框架发送一个请求,该框架再与后端的 Omni-Channel 服务通信,最终完成状态更新或工作分配等操作。整个过程是异步的,因此 API 方法通常返回一个 Promise,允许我们处理成功或失败的回调。

主要 API 方法解析

要使用该 API,我们首先需要在 LWC 的 JavaScript 文件中导入所需的方法:

import {
    isOmniToolkitReady,
    login,
    logout,
    getServicePresenceStatusIds,
    setServicePresenceStatus,
    acceptWork,
    declineWork
} from 'lightning/omniToolkitApi';

以下是几个关键方法的详细说明:

  • isOmniToolkitReady(): 在执行任何 Omni-Channel 操作之前,检查 API 是否已加载并准备就绪。这非常重要,因为 API 的加载依赖于控制台中的 Omni-Channel 小组件。此方法返回一个解析为布尔值的 Promise。
  • login({statusId}): 使用指定的在线状态 ID 将当前用户登录到 Omni-Channel。这个 statusIdPresence Status 记录的 Salesforce ID。
  • logout(): 将当前用户从 Omni-Channel 中注销。
  • getServicePresenceStatusIds({statuses: [...]}): 这是一个辅助方法,用于根据状态的开发者名称 (Developer Name) 获取其对应的 Salesforce ID。例如,你可以传入 {statuses: ['Online', 'Busy']} 来获取“Online”和“Busy”这两个状态的 ID。这避免了在代码中硬编码 ID。
  • setServicePresenceStatus({statusId}): 更改当前已登录座席的在线状态。这是最常用的方法之一,用于实现程序化的状态切换。
  • acceptWork({workId}): 接受一个传入的工作项。workId 是待处理工作项的 ID,通常通过监听 Omni-Channel 事件来获取。
  • declineWork({workId, reason}): 拒绝一个传入的工作项,并可以附带一个拒绝原因。

除了这些方法,Omni-Channel Toolkit API 还提供了一系列可以监听的事件,如 lightning/omniChannelWorkAssignedlightning/omniChannelWorkAccepted 等,它们让我们的组件能够实时响应 Omni-Channel 的状态变化,从而构建出交互性极强的应用。


示例代码

下面,我们将创建一个简单的 LWC 组件,该组件提供按钮来登录 Omni-Channel、将状态设置为“在线 (Available)”或“处理中 (Busy)”,并注销。这个示例严格遵循 Salesforce 官方文档的实践。

customOmniController.html

<template>
    <lightning-card title="自定义 Omni-Channel 控制器" icon-name="standard:live_chat">
        <div class="slds-p-around_medium">
            <template if:true={isOmniApiReady}>
                <p class="slds-m-bottom_small">API 已就绪。请选择操作。</p>
                <lightning-button
                    label="登录 Omni-Channel"
                    onclick={handleLogin}
                    disabled={isAgentLoggedIn}
                    class="slds-m-right_x-small">
                </lightning-button>
                <lightning-button
                    label="设置为在线"
                    onclick={handleSetAvailable}
                    disabled={!isAgentLoggedIn}
                    class="slds-m-right_x-small">
                </lightning-button>
                <lightning-button
                    label="设置为忙碌"
                    onclick={handleSetBusy}
                    disabled={!isAgentLoggedIn}
                    class="slds-m-right_x-small">
                </lightning-button>
                <lightning-button
                    label="注销"
                    variant="destructive"
                    onclick={handleLogout}
                    disabled={!isAgentLoggedIn}>
                </lightning-button>
            </template>
            <template if:false={isOmniApiReady}>
                <p>正在等待 Omni-Channel Toolkit API 初始化...</p>
            </template>

            <div if:true={error} class="slds-text-color_error slds-m-top_small">
                <b>发生错误:</b> {error}
            </div>
        </div>
    </lightning-card>
</template>

customOmniController.js

import { LightningElement, track, wire } from 'lwc';
import { ShowToastEvent } from 'lightning/platformShowToastEvent';
import {
    isOmniToolkitReady,
    getServicePresenceStatusIds,
    login,
    logout,
    setServicePresenceStatus
} from 'lightning/omniToolkitApi';

export default class CustomOmniController extends LightningElement {
    @track isOmniApiReady = false; // 跟踪 API 是否准备就绪
    @track isAgentLoggedIn = false; // 跟踪座席登录状态
    @track error; // 用于显示错误信息

    availableStatusId; // 存储“在线”状态的 ID
    busyStatusId; // 存储“忙碌”状态的 ID
    initialLoginStatusId; // 存储初始登录时使用的状态 ID

    // 使用 @wire 响应式地检查 API 是否就绪
    // 这是一种最佳实践,可以确保在 API 可用时自动更新 UI
    @wire(isOmniToolkitReady)
    onOmniToolkitReady(result) {
        if (result.data === true) {
            console.log('Omni Toolkit API is ready.');
            this.isOmniApiReady = true;
            // API 就绪后,获取所需的状态 ID
            this.fetchPresenceStatusIds();
        } else if (result.error) {
            console.error('Omni Toolkit API error:', result.error);
            this.error = 'Omni Toolkit API 加载失败。请确保您在 Salesforce 控制台应用中,并且已启用 Omni-Channel。';
        }
    }

    // 异步方法,获取我们需要的在线状态 ID
    async fetchPresenceStatusIds() {
        try {
            // 注意:'Available' 和 'Busy' 必须与您在 Salesforce 设置中定义的
            // Presence Status 的开发者名称 (Developer Name) 完全匹配。
            const result = await getServicePresenceStatusIds({ statuses: ['Available', 'Busy'] });
            
            // 将获取到的 ID 存储在组件属性中
            this.availableStatusId = result.statusIds.Available;
            this.busyStatusId = result.statusIds.Busy;

            // 我们将使用“Available”状态作为首次登录时的默认状态
            this.initialLoginStatusId = this.availableStatusId;

            console.log('Fetched Status IDs: ', JSON.stringify(result.statusIds));

            if (!this.availableStatusId) {
                this.error = "无法找到开发者名称为 'Available' 的在线状态。请检查您的 Omni-Channel 配置。";
            }
        } catch (e) {
            console.error('getServicePresenceStatusIds error: ', e);
            this.error = '获取在线状态 ID 时出错。';
        }
    }

    // 处理登录按钮点击事件
    async handleLogin() {
        if (!this.initialLoginStatusId) {
            this.showToast('错误', '无法登录,未找到有效的初始在线状态。', 'error');
            return;
        }
        try {
            const result = await login({ statusId: this.initialLoginStatusId });
            if (result) {
                console.log('Login successful: ', result);
                this.isAgentLoggedIn = true;
                this.showToast('成功', '已成功登录到 Omni-Channel。', 'success');
            } else {
                this.showToast('警告', '登录操作未返回成功确认。', 'warning');
            }
        } catch (e) {
            console.error('Login error: ', e);
            this.error = '登录失败。请确保您的用户已正确配置 Omni-Channel。';
            this.showToast('错误', `登录失败: ${e.message}`, 'error');
        }
    }

    // 处理设置为在线按钮点击事件
    async handleSetAvailable() {
        try {
            await setServicePresenceStatus({ statusId: this.availableStatusId });
            this.showToast('成功', '状态已更新为“在线”。', 'success');
        } catch (e) {
            console.error('Set Available Status Error: ', e);
            this.error = '设置状态为“在线”时失败。';
            this.showToast('错误', '状态更新失败。', 'error');
        }
    }

    // 处理设置为忙碌按钮点击事件
    async handleSetBusy() {
        try {
            await setServicePresenceStatus({ statusId: this.busyStatusId });
            this.showToast('成功', '状态已更新为“忙碌”。', 'success');
        } catch (e) {
            console.error('Set Busy Status Error: ', e);
            this.error = '设置状态为“忙碌”时失败。';
            this.showToast('错误', '状态更新失败。', 'error');
        }
    }

    // 处理注销按钮点击事件
    async handleLogout() {
        try {
            await logout();
            this.isAgentLoggedIn = false;
            this.showToast('成功', '已从 Omni-Channel 注销。', 'success');
        } catch (e) {
            console.error('Logout error: ', e);
            this.error = '注销时发生错误。';
            this.showToast('错误', '注销失败。', 'error');
        }
    }

    // 通用的 Toast 通知方法
    showToast(title, message, variant) {
        const event = new ShowToastEvent({
            title,
            message,
            variant,
        });
        this.dispatchEvent(event);
    }
}

注意事项

在使用 Omni-Channel Toolkit API 时,我们必须注意以下几个关键点:

  1. 权限与配置:
    • 用户许可证:执行操作的用户必须被分配 "Service Cloud User" 和 "Omni-Channel User" 功能许可证 (Feature License)。
    • 权限集 (Permission Set):确保用户所在的简档 (Profile) 或权限集拥有对他们需要访问的 Presence Statuses 的访问权限。
    • 服务渠道 (Service Channel) 与路由配置 (Routing Configuration):用户必须被分配到一个或多个有效的服务渠道,并且这些渠道与路由配置相关联。
    • 在线状态配置:用户必须被分配到一个包含所需在线状态的 Presence Configuration 中。
  2. API 运行环境:

    此 API 严格依赖于客户端环境。这意味着包含此 LWC 的页面必须在 Salesforce 控制台应用(例如 Service Cloud Console)中打开,并且该应用的页脚必须添加并启用了 Omni-Channel 工具。如果尝试在标准导航应用或 Lightning App Builder 预览中使用此组件,isOmniToolkitReady 将永远不会返回 true,API 调用也会失败。

  3. API 限制:

    这是一个客户端 API,不能从服务器端的 Apex 代码中调用。所有逻辑必须在 LWC 的 JavaScript 控制器中实现。如果需要服务器端逻辑来决定座席的状态(例如,基于复杂的后台计算),您可以使用平台事件 (Platform Events) 或其他异步机制从服务器向客户端 LWC 发送通知,然后由 LWC 调用 Omni-Channel Toolkit API 来执行状态变更。

  4. 错误处理:

    API 调用是异步的,并且可能因为各种原因(如配置错误、网络问题、无效的 ID)而失败。如示例代码所示,必须使用 try...catch 块(对于 async/await)或 .catch()(对于 Promise)来捕获并优雅地处理这些错误。向用户提供清晰的错误信息对于提升用户体验至关重要。

  5. 状态 ID 的获取:

    绝对不要在代码中硬编码 Salesforce 记录 ID。不同环境(沙盒、生产)中的 ID 是不一样的。始终使用 getServicePresenceStatusIds 方法,通过唯一的开发者名称 (Developer Name) 动态获取状态 ID。这是确保代码可移植性和可维护性的最佳实践。


总结与最佳实践

Salesforce Omni-Channel Toolkit API 为我们开发人员打开了一扇通往深度定制 Service Cloud 座席体验的大门。通过它,我们可以摆脱标准组件的限制,构建完全符合业务流程、高度集成且自动化的座席工作台。

最佳实践总结:

  • 先检查,再执行:始终使用 isOmniToolkitReady 来确认 API 可用性,然后再执行任何相关操作。使用 @wire 服务可以简化这一过程。
  • 动态获取 ID:坚持使用 getServicePresenceStatusIds 来获取状态 ID,避免硬编码,增强代码的健壮性。
  • 全面的错误处理:为每个 API 调用实施周全的错误处理逻辑,并向用户提供有意义的反馈。
  • 状态管理与 UI 反馈:在您的 LWC 内部维护座席的当前状态(例如,一个布尔值 isLoggedIn)。根据这个状态来禁用或启用 UI 元素(如按钮),为用户提供清晰的视觉指引。
  • 结合平台事件:对于复杂的服务器端触发的自动化场景,可以结合使用平台事件。例如,一个 Apex 触发器在满足某个条件时发布一个平台事件,您的 LWC 订阅该事件,并在接收到事件后调用 Toolkit API 更改座席状态。
  • 组件化设计:将您的 Omni-Channel 控制逻辑封装成一个独立的、可重用的 LWC。这样,您可以轻松地将其嵌入到任何控制台页面或布局中,提高开发效率。

作为 Salesforce 开发人员,掌握 Omni-Channel Toolkit API 意味着我们能够交付更智能、更高效的服务解决方案,真正赋能客服座席,提升客户满意度。这不仅仅是编写代码,更是通过技术手段优化业务流程、创造商业价值的体现。

评论

此博客中的热门博文

Salesforce 登录取证:深入解析用户访问监控与安全

Salesforce Experience Cloud 技术深度解析:构建社区站点 (Community Sites)

Salesforce Einstein AI 编程实践:开发者视角下的智能预测