班表整合 · 07
將班表以 API 同步到 HR 人資系統
依門市與組別查詢 FREONE 最終班表,轉換後送往 HRIS API。
完成結果HRIS 取得台北信義店外場組 2026-10-07 的最終工時、班次、崗位與部分休假,並能用 shift id 與 updatedTime 安全重跑。
實際案例
某餐飲集團要將台北信義店外場組的班表同步到 HRIS。整合程式先建立 FREONE 門市/組別索引,以示意 mapping 將「台北信義店」對應到 HRIS 店號 TPE-X01,再查詢日期區間內的 Shift。
本例中 S0001 王小明原排 13:00–17:00,核實後改為 13:00–16:00,並於 16:00–18:00 使用部分特別休假。HRIS 應採用 actual,同時保留 planned 與核實回覆供稽核。
資料流
- 1建立可存取範圍索引整理 Merchant、Squad、Position 與 Group;問卷設定不參與本次同步。
GET /v1/squads - 2套用門市 mapping將 FREONE 台北信義店對應到 HRIS 店號 TPE-X01。
integration mapping - 3查詢班表區間依 squadId 與包含起訖日的日期範圍取得 Shift。
GET /v1/shifts/{squadId} - 4轉換最終班表以 actual 產生 HRIS payload,保留 duties、leaves 與跨日資訊。
transform - 5可重跑寫入以 shift id 作為識別,updatedTime 判斷是否需要更新 HRIS。
HRIS API
門市與組別索引
GET /v1/squads 可組出:
| 層級 | FREONE 欄位 | 本例值 |
|---|---|---|
| 店名/店 ID | merchant.name、merchant.id | 台北信義店/tkfv5t |
| 組別名/組別 ID | name、id | 外場/sqmmhw5vonCzo |
| 排序 | order | 1 |
| 崗位與需求人數 | positions[].name、headcount | 值班 1、櫃檯 1、跑菜 3、機動 2 |
| 群組與排序 | groups[].name、sortKey | A區 1、B區 2 |
串接端另行維護門市 mapping:
FREONE merchant.name | FREONE ID | HRIS 店號 |
|---|---|---|
| 台北信義店 | tkfv5t | TPE-X01 |
Request
GET /v1/shifts/sqmmhw5vonCzo?from=2026-10-07&to=2026-10-07
Response
{
"data": [
{
"id": "687e4726-7f46-4249-9cf1-ea4c808d69e4",
"assignee": {
"id": "a571cbd6-8795-4ccd-924b-63097c77314c",
"name": "王小明",
"alias": "小明",
"externalId": "S0001",
"identityTag": "正職",
"parttime": false
},
"squad": {
"id": "sqmmhw5vonCzo",
"name": "外場"
},
"merchant": {
"id": "tkfv5t",
"name": "台北信義店",
"timezone": "Asia/Taipei"
},
"date": "2026-10-07",
"createdBy": {
"id": "0dab2cd6-702a-4e94-876d-4a91c514a76e",
"name": "林小米店長"
},
"createdTime": "2026-09-20T02:25:00Z",
"updatedBy": {
"id": "0dab2cd6-702a-4e94-876d-4a91c514a76e",
"name": "林小米店長"
},
"updatedTime": "2026-10-07T10:00:00Z",
"publishedBy": {
"id": "0dab2cd6-702a-4e94-876d-4a91c514a76e",
"name": "林小米店長"
},
"publishedTime": "2026-09-25T03:25:00Z",
"planned": {
"dayOff": false,
"leaveType": null,
"duties": [
{
"slot": {
"name": "午班",
"start": "13:00",
"end": "17:00",
"breakDuration": 30,
"breakStart": null,
"overnight": false,
"externalId": "MID_1300-1700"
},
"positions": [
"值班"
]
}
],
"leaves": [
{
"slot": {
"start": "16:00",
"end": "18:00",
"overnight": false
},
"leaveType": {
"id": "47706123-8094-4c7f-ac69-2e59d638241f",
"name": "特別休假",
"abbreviation": "特",
"externalId": "ANNUAL"
}
}
],
"workingMinutes": 210
},
"actual": {
"dayOff": false,
"leaveType": null,
"duties": [
{
"slot": {
"name": "午班",
"start": "13:00",
"end": "16:00",
"breakDuration": 0,
"breakStart": null,
"overnight": false,
"externalId": "MID_1300-1600"
},
"positions": [
"值班"
]
}
],
"leaves": [
{
"slot": {
"start": "16:00",
"end": "18:00",
"overnight": false
},
"leaveType": {
"id": "47706123-8094-4c7f-ac69-2e59d638241f",
"name": "特別休假",
"abbreviation": "特",
"externalId": "ANNUAL"
}
}
],
"workingMinutes": 180
},
"latestAdjustmentConfirmation": {
"state": "replied",
"comment": "請確認核實後的下班時間"
},
"support": null
}
]
}
HRIS 欄位 mapping
| HRIS 資料 | FREONE 來源 | 本例輸出 |
|---|---|---|
| 店號 | merchant.name → mapping | TPE-X01 |
| 員工編號 | assignee.externalId | S0001 |
| 日期 | date | 2026-10-07 |
| 最終工時 | actual.workingMinutes | 180 分鐘 |
| 班次代碼/名稱 | actual.duties[].slot.externalId/name | MID_1300-1600/午班 |
| 上班時間 | actual.duties[].slot.start | 13:00 |
| 下班時間 | actual.duties[].slot.end | 16:00 |
| 休息時長 | actual.duties[].slot.breakDuration | 0 分鐘 |
| 崗位 | actual.duties[].positions | ['值班'],內容直接是名稱 |
| 部分休假 | actual.leaves[] | 16:00–18:00、假別 ANNUAL |
| 同步識別 | id、updatedTime | Shift ID+最後更新時間 |
休假與班表判讀
| 情境 | dayOff | leaveType | duties[] | leaves[] |
|---|---|---|---|---|
| 純休假 | true | 有值 | 空 | 通常為空 |
| 有班+部分休假 | false | null | 有值 | 有值 |
| 休假日加班 | true | 有值 | 有值 | 通常為空 |
若同日有多段 duty,保留 actual.duties[] 原始順序;若 overnight=true,該段 end 的日期是 Shift date + 1 天。
驗收結果
- HRIS 收到店號
TPE-X01、員工S0001、日期 2026-10-07。 - 同步採用
actual.workingMinutes=180,但仍保存planned=210供稽核。 - 部分休假沒有被誤判成整日休假,崗位也沒有當成 Position ID 再查一次。
- 相同 shift id 與 updatedTime 重跑時不重複建立資料。
關鍵提醒
planned 是預排、actual 是最終班表,並非打卡出勤;多數時候兩者相同,兩者不同才代表當日以前的 Shift 曾經核實調整,可能同時有工時差異與 comment。latestAdjustmentConfirmation.state=replied 是介面上的「發問」且 comment 有值;acknowledged 是「確認」且 comment 為空字串。憑證與同步邏輯應放在後端服務,不要由瀏覽器直接呼叫 FREONE API。