创建自定义集成以将劳动力管理系统与排班同步
概述
将排班(Microsoft Teams 中的计划管理应用)与劳动力管理 (WFM) 系统集成。 此集成允许一线员工直接在排班中查看和管理其日程安排。
本文介绍如何使用Microsoft图形 API创建连接器,以便实现此集成。
可以为单向数据同步或双向数据同步设置集成。
单向同步 (WFM 系统到排班) :在此设置中,WFM系统中的计划数据将同步到排班。 连接器读取WFM系统中的数据,并将其写入 Shifts。 但是,用户在排班中所做的任何更改不会反映在WFM系统中。
双向同步 (WFM 系统和排班) :此设置允许双向同步。WFM系统中的计划数据将同步到排班,用户对排班所做的任何更改将同步回WFM系统。 在将更改写入排班之前,连接器会根据WFM系统强制实施的业务规则验证和批准用户在排班中所做的更改。
注意
如果使用 UKG Pro WFM、Blue Yonder WFM 或 Reflexis WFM,还可以使用托管连接器将 Shifts 与 WFM 系统集成。 若要了解详细信息,请参阅 Shifts 连接器。
本文中使用的术语
术语 | 描述 |
---|---|
连接器 | 在WFM系统和排班之间同步计划数据的应用。 |
劳动力集成 | 一个实体,用于定义通信的加密方法、连接器的回调 URL 和要同步的 Shifts 实体。 |
开始之前
先决条件
- 根据业务需求确定要同步的数据。
- 了解Microsoft 标识平台中的身份验证和授权概念。 请参阅 身份验证和授权基础知识。
- 管理员所需的角色:
- 至少由云应用程序管理员在 Microsoft Entra 管理中心 中注册应用
- 用于注册劳动力集成的全局管理员
熟悉集成过程
下面是集成步骤的概述。 查看此信息以了解整个过程,包括执行每个步骤的人员。
步骤 | 单向同步 | 双向同步 | 执行此步骤的人员 |
---|---|---|---|
1 | 创建连接器:
|
创建连接器:
|
开发人员版 |
2 | 在Microsoft Entra 管理中心中注册应用 | 在Microsoft Entra 管理中心中注册应用 | 至少是云应用程序管理员的帐户 |
3 | 创建用于同步的团队和计划 | 创建用于同步的团队和计划 | 开发人员或 Teams 管理员 |
4 | 注册并启用员工集成:
|
注册并启用员工集成:
|
步骤 4a:全局管理员 步骤 4b:开发人员 |
步骤 1:创建连接器
若要创建连接器,请完成以下步骤:
步骤 1a:将排班中所做的更改同步到WFM系统
若要将连接器设置为接收和处理来自排班的请求,需要实现以下终结点:
确定基 URL 和终结点 URL
webhook) (基 URL 为 {url}/v{apiVersion}
,其中 url 和 apiVersion 是注册劳动力集成时在 workforceIntegration 对象中设置的属性。
终结点 URL 的相对路径如下所示:
- /连接:
/connect
- /更新:
/teams/{teamid}/update
- /读:
/teams/{teamid}/read
例如,如果 url 为 https://contosoconnector.com/wfi
, apiVersion 为 1
:
- 基 URL 为
https://contosoconnector/com/wfi/v1
。 - /connect 终结点为
https://contosoconnector/wfi/v1/connect
。 - /update 终结点为
https://contosoconnector/wfi/v1/teams/{teamid}/update
。 - /read 终结点为
https://contosoconnector/wfi/v1/teams/{teamid}/read
。
加密
所有请求都使用 AES-256-CBC-HMAC-SHA256 进行加密。 注册劳动力集成时,可以指定共享密钥。 发回班次的响应不应加密。
终结点
POST /connect
Shifts 会在 注册劳动力集成时调用此终结点来测试连接。 仅当此终结点返回 HTTP 200 OK
响应时,才会返回成功响应。
示例
请求
ConnectRequest
{
"tenantId": "a1s2s355-a2s3-j7h6-f4d3-k2h9j4mqpz",
"userId": "4fbc12d7-1234-56ef-8a90-bc123d45678f"
}
响应
返回 HTTP 200 OK
POST /teams/{teamid}/update
班次调用此终结点,以便在为劳动力集成启用的计划中对排班实体进行更改时获得批准。 如果此终结点批准请求,则更改将保存在排班中。
由于WFM系统是记录系统,因此当连接器收到对此终结点的请求时,它应首先尝试在WFM系统中进行更改。 如果更改成功,则返回成功。 否则返回失败。
班次针对每个更改 (包括从连接器/WFM系统) 发起的更改)调用此终结点。 如果连接器使用 图形 API 向 Shifts 发送了更新并添加了X-MS-WFMPassthrough: workforceIntegratonId
标头,则传入此终结点的请求将具有相同的标头,这允许你适当地识别和处理这些请求。 例如,返回成功,但不在WFM系统中进行与冗余相同的更改,并可能导致连接器停滞在无限循环中。
下图显示了数据流。
注意
有关请求和响应模型的详细信息,请参阅本文终结点参考部分中的 WfiRequest。
返回响应代码
来自集成的任何响应(包括错误)都必须具有 HTTP 响应代码 200 OK
。 响应正文必须具有反映相应子调用错误状态的状态和错误消息。 来自集成以外的 200 OK
任何响应都被视为错误,并返回到调用方 (客户端或Microsoft Graph) 。
如果要设置单向同步,请将 Shifts 设置为只读
对于单向同步,必须将排班设置为只读,以便用户无法在排班中进行更改。 若要使班次为只读,请返回来自排班的所有请求的失败响应。
例如,若要阻止用户对计划中的班次进行更改,此终结点必须在收到有关 shift
实体的请求时返回失败响应。
示例
请求
WfiRequestContainer
以下示例显示了来自班次的请求,该请求询问是否可以在班次中保存其 ID 为 SHFT_12345678-1234-1234-1234-1234567890ab 且具有 正文中列出的属性的班次。 当用户在排班中创建班次时,会触发此请求。
{
"requests": [
{
"id": "SHFT_12345678-1234-1234-1234-1234567890ab",
"method": "POST",
"url": "/shifts/SHFT_12345678-1234-1234-1234-1234567890ab",
"headers": {
"X-MS-Transaction-ID": "1",
"X-MS-Expires": "2024-10-11T21:27:59.0134605Z"
},
"body": {
"draftShift": {
"activities": [],
"isActive": true,
"startDateTime": "2024-10-12T15:00:00.000Z",
"endDateTime": "2024-10-12T17:00:00.000Z",
"theme": "Blue"
},
"isStagedForDeletion": false,
"schedulingGroupId": "TAG_a3e0b3f1-4a5c-4c2e-8eeb-5b8c3d1e3f8b",
"userId": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"createdDateTime": "2024-10-11T21:27:28.762Z",
"lastModifiedDateTime": "2024-10-11T21:27:28.762Z",
"lastModifiedBy": {
"user": {
"id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"displayName": "Adele Vance"
}
},
"id": "SHFT_12345678-1234-1234-1234-1234567890ab"
}
}
]
}
响应
WfiResponse
成功:返回 HTTP 200 OK
此示例显示终结点批准请求时返回的响应。 在此方案中,班次保存在排班中,用户可以在计划中查看班次。
{
"responses": [
{
"id": "SHFT_12345678-1234-1234-1234-1234567890ab",
"status": 200,
"body": {
"eTag": "3f4e5d6c-7a8b-9c0d-1e2f-3g4h5i6j7k8l",
"error": null,
"data": null
}
}
]
}
失败:返回 HTTP 200 OK
此示例显示终结点拒绝请求时返回的响应。 在此方案中,用户在排班中收到“无法添加班次”错误消息。
{
"responses": [
{
"id": "SHFT_12345678-1234-1234-1234-1234567890ab",
"status": 500,
"body": {
"error": {
"code": "500",
"message": “Could not add the shift”
},
"data": null
}
}
]
}
POST /teams/{teamid}/read
此终结点处理来自排班的请求,以便为用户的交换请求提取符合条件的休假原因或符合条件的班次。
注意
自 2024 年 10 月起,此终结点仅在Microsoft图形 API的 beta 版本中受支持。 注册劳动力集成时,还必须指定 eligibilityFilteringEnabledEntities 属性的值。
下图显示了数据流。
返回响应代码
来自集成的任何响应(包括错误)都必须具有 HTTP 响应代码 200 OK
。 响应正文必须包含反映相应子调用错误状态的状态和错误消息。 来自集成以外的 200 OK
任何响应都被视为错误,并返回到调用方 (客户端或Microsoft Graph) 。
示例:TimeOffReason
请求
以下示例显示了来自班次的请求,该请求询问用户 (用户 ID aa162a04-bec6-4b81-ba99-96caa7b2b24d) 的休假原因。 当用户在排班中请求休假时,会触发此请求。
{
"requests": [
{
"id": "aa162a04-bec6-4b81-ba99-96caa7b2b24d",
"method": "GET",
"url": "/users/aa162a04-bec6-4b81-ba99-96caa7b2b24d/timeOffReasons?requestType=TimeOffReason"
}
]
}
响应
成功:返回 HTTP 200 OK
以下响应显示用户的合格休假原因 ID 为“TOR_29f4a110-ae53-458b-83d6-00c910fe2fbc”和“TOR_8c0e8d07-ac1a-48dc-b3af-7bc71a62ff7d”。 在此方案中,用户在排班中看到相应的休假原因可供选择。
{
"responses": [
{
"id": "aa162a04-bec6-4b81-ba99-96caa7b2b24d",
"status": 200,
"body": {
"data": [
"TOR_29f4a110-ae53-458b-83d6-00c910fe2fbc",
"TOR_8c0e8d07-ac1a-48dc-b3af-7bc71a62ff7d"
],
"error": null
}
}
]
}
失败:返回 HTTP 200 OK
在此示例中,返回错误响应是因为连接器无法访问WFM系统来检索用户的休假原因。
{
"responses": [
{
"id": "aa162a04-bec6-4b81-ba99-96caa7b2b24d",
"status": 503,
"body": {
"data": null,
"error": {
"code": "503",
"message": "Could not reach WFM"
}
}
}
]
}
示例:SwapRequest
请求
以下示例显示了来自班次的请求,该请求询问哪些班次符合交换条件,其 ID 为 2024-4a66-83ea-1bbbf81ac029(该班次的 ID 为 SHFT_5e2b51ac-dc47-4a66-83ea-1bbbf81ac029)的班次交换 10-01T04:00:00.0000000Z 和 2024-11-01T03:59:59.9990000Z。
{
"requests": [
{
"id": "SHFT_5e2b51ac-dc47-4a66-83ea-1bbbf81ac029",
"method": "GET",
"url": "/shifts/SHFT_5e2b51ac-dc47-4a66-83ea-1bbbf81ac029/requestableShifts?requestType=SwapRequest&startTime=2024-10-01T04:00:00.0000000Z&endTime=2024-11-01T03:59:59.9990000Z"
}
]
}
响应
成功:返回 HTTP 200 OK
以下响应显示,该班次可与 ID 为 SHFT_98e96e23-966b-43be-b90d-4697037b67af 的班次交换。
{
"responses": [
{
"id": "SHFT_5e2b51ac-dc47-4a66-83ea-1bbbf81ac029",
"status": 200,
"body": {
"data": ["SHFT_98e96e23-966b-43be-b90d-4697037b67af"],
"error": null,
}
}
]
}
失败:返回 HTTP 200 OK
在此示例中,返回错误响应是因为连接器无法访问WFM系统来检索用户的交换请求的合格班次。
{
"responses": [
{
"id": "SHFT_5e2b51ac-dc47-4a66-83ea-1bbbf81ac029",
"status": 503,
"body": {
"data": null,
"error": {
"code": "503",
"message": "could not reach WFM"
}
}
}
]
}
步骤 1b:将数据从WFM系统同步到排班
使用 Microsoft Graph 中的排班 API 从WFM系统读取计划数据,并将数据写入排班。
例如,若要将班次添加到班次,请使用 创建班次 API。
请参阅排班管理下列出的排班 API 图形 API v1.0 Microsoft参考。
注意
标头 MS-APP-ACTS-AS
在请求中是必需的,并且必须包含应用代表的用户的 ID (GUID) 。 建议在更新计划时使用团队所有者的用户 ID。
下图显示了数据流。
初始同步
对于第一次同步,连接器应读取WFM系统中的数据,并将数据写入排班。 建议同步两周的未来数据。
初始同步后
首次同步后,可以选择:
使用WFM系统中的更改同步更新班次:针对WFM系统中所做的每一项更改,向排班发送更新。
使用WFM系统中的更改异步更新班次:通过写入特定时间范围内WFM系统中发生的所有更改来执行定期同步 (例如,) 10 分钟) 班次。
对 Shifts 的所有写入操作(包括连接器发起的写入操作)都会触发对连接器的 /update 终结点的调用。 建议将所有写入调用的标头包括在内
X-MS-WFMPassthrough: workforceIntegratonId
,以便连接器可以适当地识别和处理它们。 例如,如果WFM系统启动了更改,请在不对WFM系统应用更新的情况下批准更改。注意
如果要为WFM系统和排班之间的双向数据同步设置连接器,请排除定期同步中从排班发起的更改。这些更改已写入排班。
步骤 2:在Microsoft Entra 管理中心中注册应用
按照以下步骤在 Microsoft 标识平台中为连接器注册应用,配置应用访问 Microsoft Graph 的权限,并获取访问令牌。
至少以云应用程序管理员身份登录到Microsoft Entra 管理中心。
注册应用。 有关步骤,请参阅使用 Microsoft 标识平台注册应用程序。
将 Schedule.ReadWrite.All应用程序权限 分配给应用,以便进行仅限应用的访问权限,并获取访问令牌。
有关分步指南,请参阅 在没有用户的情况下获取访问权限。
访问令牌验证你的应用是否有权使用 Schedule.ReadWrite.All 权限使用自己的标识调用 Microsoft Graph。 它必须包含在请求的 Authorization 标头中。
步骤 3:创建用于同步的团队和计划
在 Teams 中设置要同步的团队。可以使用现有团队或创建新团队。
在 Teams 中设置团队以与WFM系统中的团队和位置对应。 确保将以下人员添加到每个团队:
- 作为团队所有者的一线经理。 请确保将标头中的
MS-APP-ACTS-AS
用户添加为每个各自团队的团队所有者。 - 一线工作人员作为团队成员。
- 作为团队所有者的一线经理。 请确保将标头中的
在每个团队的排班中创建计划。 若要了解详细信息,请参阅 创建或替换计划。
将计划组添加到每个团队的计划。 计划组用于根据团队中的共同特征对员工进行分组。 例如,计划组可以是部门或作业类型。 若要了解详细信息,请参阅 schedulingGroup 资源类型。
将员工添加到每个计划组。 若要了解详细信息,请参阅 替换 schedulingGroup。
步骤 4:注册并启用员工集成
劳动力集成定义了用于排班和连接器之间通信的加密设置、来自排班的回调的 URL 以及要同步的实体类型。
若要注册并启用劳动力集成,请完成以下步骤:
步骤 4a:在租户中注册劳动力集成
你必须是全局管理员才能执行此步骤。
使用 创建 workforceIntegration API 在租户中注册劳动力集成。
下面是请求的示例。
POST https://graph.microsoft.com/v1.0/teamwork/workforceIntegrations/
{
"displayName": "Contoso integration",
"apiVersion": 1,
"encryption": {
"protocol": "sharedSecret",
"secret": "secret-value"
},
"isActive": true,
"url": "https://contosoconnector.com/wfi",
"supportedEntities": "Shift,SwapRequest,UserShiftPreferences,Openshift,OpenShiftRequest,OfferShiftRequest”,
}
有关详细信息,请参阅下表。 若要了解详细信息,请参阅 workforceIntegration 资源类型。
属性 | 更多信息 |
---|---|
apiVersion | 回调 URL 的 API 版本。 基 URL 由 url 属性和此属性组成。 |
加密 | 将 协议 设置为 sharedSecret 。
机密值必须正好为 64 个字符。 使用机密解密从排班发送到连接器终结点的加密 JSON 有效负载。 有效负载使用 AES-256-CBC-HMAC-SHA256 进行加密。 你的应用应安全地保留此机密。 例如,在密钥保管库中。 |
supportedEntities | 指定希望连接器支持同步的 Shifts 实体。 当这些实体中的任何一个发生更改时,Shifts 都会调用连接器的 /update 终结点,以便你可以批准或拒绝更改。 有关可能值的列表,请参阅 workforceIntegration 资源类型 注意 此列表是 一个可演变的枚举。 必须使用 Prefer: include-unknown-enum-members 请求标头来获取所有值。 |
eligibilityFilteringEnabledEntities |
注意:从 2024 年 10 月开始,此终结点仅在Microsoft图形 API的 beta 版本中受支持。 指定要连接以支持资格筛选的 Shifts 实体。 可能的值是:
Prefer: include-unknown-enum-members 请求标头来获取所有值。 |
url | 班次回调的劳动力集成 URL。 基 URL 由此属性和 apiVerson 属性组成。 |
步骤 4b:为团队计划启用劳动力集成
根据要管理的计划启用员工集成。 为此,请使用 创建或替换计划 API 创建或更新团队的计划。
下面是请求的示例。
POST https://graph.microsoft.com/v1.0/teams/{teamId}/schedule
{
enabled: true,
timezone: “America/New_York”,
workforceIntegrationIds: [ “workforceIntegrationId”]
}
- 指定 注册劳动力集成时生成的 workforceIntegrationId。
- 一个计划最多可以启用一次员工集成。 如果在请求中包含多个 workforceIntegrationId,则使用第一个。
疑难解答
Connector
当连接器响应来自 Shifts 的请求时,如果返回 200 以外的响应代码,会发生什么情况? 如果在响应正文中返回 200 以外的状态,是否会有所作为?
这两种方案之间存在差异。
- 如果连接器返回 200 以外的响应代码,则 Shifts 会尝试多次重试 /read 和 /update 终结点。 最终,排班显示“出现问题。 团队中的劳动力集成设置响应了无效数据。“错误消息。
- 如果连接器在响应正文中返回 200 以外的状态,则 Shifts 将显示“出错。 很抱歉,无法完成更改,“错误消息并停止重试终结点。
如果连接器在响应正文中返回无效数据,会发生什么情况?
Shifts 尝试多次重试 /read 和 /update 终结点。 最终,排班显示“出现问题。 团队上设置的劳动力集成响应了无效数据。“错误消息。
如何实现确定请求最初是在排班还是WFM系统中发出,以防止无限循环?
将 X-MS-WFMPassthrough: workforceIntegratonId
标头添加到所有写入和更新调用,以标识/忽略连接器触发的更改。 此标头用于指示由于连接器先前调用而发出请求,该调用图形 API将数据从WFM系统同步到排班。
劳动力集成注册
我注册了劳动力集成并指定了“eligibilityFilteringEnabledEntities”,包括“SwapRequest、OfferShiftRequest 和 TimeOffReason',但响应正文未显示”eligibilityFilteringEnabledEntities“列表。
当前支持通过 https://graph.microsoft.com/beta
终结点(而不是 https://graph.microsoft.com/v1
终结点)筛选资格。
我注册了劳动力集成并添加了“supportedEntities”,但收到 400 错误请求响应和“有效负载无效:请求的值 'shift, ....' 找不到。“消息
确保列表请求正文中的每个 supportedEntities
Shifts 实体都以大写字母开头。 例如,"supportedEntities":"Shift,SwapRequest,OpenShift"
。
我注册了劳动力集成并实现了 /connect、/update 和 /read 终结点,但 Webhook 不起作用。
确保已针对团队计划启用员工集成。 此外,检查 url 和 apiVersion 属性正确。
终结点参考
请求
ConnectRequest
属性 | 类型 | 说明 |
---|---|---|
tenantId | String | 劳动力集成的租户 ID |
userId | String | 劳动力集成的用户的 ID |
{
"tenantId": "string",
"userId": "string"
}
WfiRequestContainer
属性 | 类型 | 说明 |
---|---|---|
请求 | WfiRequest 集合 | WfiRequests 列表 |
{
"requests": [
{
"id": "string",
"method": "string",
"url": "string",
"headers": {
"X-MS-Transaction-ID": "string",
"X-MS-Expires": "string (DateTime)"
},
"body": "ShiftsEntity"
}
]
}
请求中的元素数:
- 在大多数情况下,请求有一个元素。
- 某些请求(如交换班次请求审批)有五个元素:一个 PUT 交换请求,两个 DELETE 班次 (现有班次) ,两个 POST 班次 (新班次) 。
WfiRequest
属性 | 类型 | 说明 |
---|---|---|
id | String | 实体的 ID |
方法 | String | 对项调用的方法。 例如、POST 、PUT GET 、DELETE 。 |
url | String | 指示实体的类型和操作详细信息。 |
标头 | WfiRequestHeader | 标题 |
body | ShiftsEntity | 与请求相关的实体的正文。 |
对于 POST /teams/{teamId}/update
属性 | 类型 | 说明 |
---|---|---|
id | String | 实体的 ID |
方法 | String |
POST 创建实体, PUT 更新实体, DELETE 删除实体。 |
url | String | 格式为 /{EntityType}/{EntityId} 。 的{EntityType} shifts 可能值为 、、swapRequests 、timeoffReasons 、openshiftrequests offershiftrequests openshifts 、timesoff 、 。 timeOffRequests 例如,/shifts/SHFT_12345678-1234-1234-1234-1234567890ab 。 |
标题 | WfiRequestHeader | 标头 |
body | ShiftsEntity | 必须在 url 属性中匹配{EntityType} 。 使用 shift、swapShiftsChangeRequest、timeOffReason、openshift、openShiftChangeRequest、offerShiftRequests、timeOffRequest。 例如,/shifts/SHFT_12345678-1234-1234-1234-1234567890ab 。 |
对于 POST /teams/{teamsId}/read
属性 | 类型 | 说明 |
---|---|---|
id | String | 实体的 ID |
方法 | String | 始终为 GET 。 |
url | String |
|
标题 | WfiRequestHeader | 标头 |
body | ShiftsEntity | 始终为 null 。 |
WfiRequestHeader
属性 | 类型 | 说明 |
---|---|---|
X-MS-Transaction-ID | String | 事务 ID |
X-MS-Expires | String (DateTime) | 事务过期日期时间 |
X-MS-WFMPassthrough: workforceIntegratonId
不会包含在 WfiRequestHeader 中。 它应从 HttpRequest 中提取。
响应
WfiResponseContainer
属性 | 类型 | 说明 |
---|---|---|
反应 | WfiResponse 集合 | WfiResponses 列表 |
{
"responses": [
{
"id": "string",
"status": "string",
"body": {
"eTag": "string",
"error": {
"code": "string",
"message": "string"
},
"data": ["string1", "string2"]
}
}
]
}
WfiResponse
属性 | 类型 | 说明 |
---|---|---|
id | String | 实体的 ID |
status | String | 操作的结果 |
body | WfiResponseBody | WfiResponseBody |
WfiResponseBody
属性 | 类型 | 说明 |
---|---|---|
eTag | String | eTag |
error | WfiResponseError | 有关错误的详细信息 |
data | String | 读取请求的请求数据 () |
WfiResponseError
属性 | 类型 | 说明 |
---|---|---|
code | String | 错误代码 |
message | String | 错误消息 |