# 流程設計 API 與設計原則

說明怎麼用 API 建流程，以及怎麼設計。任務流轉請看任務相關主題。

## 名詞

| 名詞 | 是什麼 |
|---|---|
| process（流程） | 藍圖：有哪些工作站、站與站怎麼連 |
| station（工作站） | 一個階段：進來什麼、要做到什麼、做完往哪走 |
| transition（出口） | 從某站往下一站的一條線 |
| routine（例行事務） | 把一個流程拿來實際跑的一份套用；誰處理哪一站綁在這層 |
| task（任務） | 一張票，在 routine 的各站之間流動 |

不同團隊跑同一條流程時開新的 routine，不要複製 process。

## 權限與共通寫法

- 讀取（GET）：組織內已認證的成員都可以。
- 新增、修改、刪除：只有組織 admin，其他人回 403 `權限不足`。
- 找不到資源回 404，驗證失敗回 422（`errors` 物件標出哪個欄位）。

```bash
export SF_KEY='<你的 API key>'
H=(-H "Authorization: Bearer $SF_KEY" -H "Accept: application/json" -H "Content-Type: application/json")
```

## 流程（process）

| 端點 | 用途 |
|---|---|
| `GET https://station-flow.staging.quanhox.com.tw/api/v1/processes` | 列出流程 |
| `POST https://station-flow.staging.quanhox.com.tw/api/v1/processes` | 建立流程（admin） |
| `GET https://station-flow.staging.quanhox.com.tw/api/v1/processes/{id}` | 流程詳情，含各站與全部出口 |
| `PUT https://station-flow.staging.quanhox.com.tw/api/v1/processes/{id}` | 修改（admin） |
| `DELETE https://station-flow.staging.quanhox.com.tw/api/v1/processes/{id}` | 刪除（admin） |
| `GET https://station-flow.staging.quanhox.com.tw/api/v1/processes/{id}/diagram` | 取得 Mermaid 流程圖 |

欄位：`name`（必填，組織內不可重複，≤128 字）、`standard_days`（標準天數）、`sort_order`、`common_instructions`（共通說明，見下）。

建立流程時，系統會自動產生四個系統站與兩條預設出口：

- 系統站：`開始`（start）、`結束`（end）、`存查`（archive）、`異常`（exception）。系統站不能刪除。
- 預設出口：開始 → 結束、結束 → 存查。

```bash
curl -s -X POST "https://station-flow.staging.quanhox.com.tw/api/v1/processes" "${H[@]}" \
  -d '{"name":"請假申請","standard_days":3}'
```

流程詳情的 `data.transitions` 攤平列出全部出口，查串線看這份。

### 共通說明（common_instructions）

每一站都適用的規則，改了直接生效。站的詳情會並列回傳站自己的 `execution_instructions` 與流程的 `process_common_instructions`。

```bash
curl -s -X PUT "https://station-flow.staging.quanhox.com.tw/api/v1/processes/12" "${H[@]}" \
  -d '{"common_instructions":"動手前先讀票上最新留言。"}'
```

## 工作站（station）

| 端點 | 用途 |
|---|---|
| `GET https://station-flow.staging.quanhox.com.tw/api/v1/processes/{processId}/stations` | 列出某流程的站 |
| `POST https://station-flow.staging.quanhox.com.tw/api/v1/processes/{processId}/stations` | 新增站（admin） |
| `GET https://station-flow.staging.quanhox.com.tw/api/v1/stations/{id}` | 站的詳情，含出口與共通說明 |
| `PUT https://station-flow.staging.quanhox.com.tw/api/v1/stations/{id}` | 修改站、串線（admin） |
| `DELETE https://station-flow.staging.quanhox.com.tw/api/v1/stations/{id}` | 刪除站（admin；系統站回 422） |

主要欄位：

| 欄位 | 說明 |
|---|---|
| `name` | 站名，建立時必填 |
| `type` | 新增時只能是 `normal`（可省略） |
| `description` | 這站是什麼、為什麼存在（≤2000 字） |
| `execution_instructions` | 執行者照著做的步驟 |
| `standard_days`、`sort_order`、`entry_criteria`、`entry_data`、`exit_data`、`checklist`、`notes` | 標準天數、排序與其他文字欄位 |
| `next_action_template` | 選填 JSON 物件，給自動化執行者的出口提示 |
| `next_station_ids` | 下一站 id 陣列，用來建立或刪除出口 |
| `next_station_names` | 出口命名，key = 目標站 id，value = 出口名稱 |
| `previous_station_ids` | 上一站 id 陣列，從反方向接線 |

`PUT https://station-flow.staging.quanhox.com.tw/api/v1/stations/{id}` 是部分更新：沒帶的欄位保留原值，串線時不必把整站資料帶回去。

```bash
# 新增一站，同時接在「開始」(id 40) 後面、往「結束」(id 42) 走
curl -s -X POST "https://station-flow.staging.quanhox.com.tw/api/v1/processes/12/stations" "${H[@]}" \
  -d '{"name":"主管審核","sort_order":10,"previous_station_ids":[40],
       "next_station_ids":[42],
       "execution_instructions":"確認假別與天數，核可選「核可」，不核可選「退回補件」。"}'
```

## 出口（transition）

### 出口類別

| 類別 | 怎麼設 | 用途 |
|---|---|---|
| 正常 | normal 出口，`tone: positive`（預設） | 往前推進；一站可有多條 |
| 退回 | normal 出口，`tone: negative`，回傳 `is_return: true` | 依原因退到指定站（任意站） |
| 系統動作 | 不用設定 | 每站都有：rollback（退回流轉歷史的前一站）、exception（送異常站）、jumpback（從異常站跳回指定站）。執行時呼叫任務端點，不會出現在出口清單 |

設定的出口都是 `transition_type: normal`；找退回類出口看 `is_return`。「放棄／不做」不算退回，設成正常。

### 建立、刪除、命名：透過工作站

出口沒有獨立的新增端點，用 `PUT https://station-flow.staging.quanhox.com.tw/api/v1/stations/{id}` 處理：

| 帶的欄位 | 效果 |
|---|---|
| `next_station_ids`（可加 `next_station_names`） | ids 是這站完整的出口清單：多的新增、少的刪除，再依 names 命名 |
| 只帶 `next_station_names` | 只改既有出口的名稱，不動連線。指向沒有出口的站回 422 |
| 只帶 `next_station_ids` | 增刪連線，既有的自訂名稱保留 |

未命名的出口叫「完成」。

```bash
curl -s -X PUT "https://station-flow.staging.quanhox.com.tw/api/v1/stations/43" "${H[@]}" \
  -d '{"next_station_ids":[42,44],
       "next_station_names":{"42":"核可","44":"退回補件"}}'
```

限制：

- 不能指向自己、別的流程的站、「存查」或「異常」（422）。「結束 → 存查」是系統線，不能改名，刪掉就補不回來。
- 存查、異常不能當來源；開始、存查、異常不能用 `previous_station_ids` 指派上一站。

### 讀取、設定細節、刪除單條

| 端點 | 用途 |
|---|---|
| `GET https://station-flow.staging.quanhox.com.tw/api/v1/stations/{stationId}/transitions` | 某站的全部出口 |
| `GET https://station-flow.staging.quanhox.com.tw/api/v1/transitions/{id}` | 單條出口 |
| `PUT https://station-flow.staging.quanhox.com.tw/api/v1/transitions/{id}`（PATCH 也可以） | 設定名稱、類別、必填意見、必附資料、排序（admin） |
| `DELETE https://station-flow.staging.quanhox.com.tw/api/v1/transitions/{id}` | 刪除單條 normal 出口（admin） |

`PUT`／`PATCH` 欄位，`name`、`tone`、`comment_required`、`required_data` 至少帶一個：

- `name`（≤128 字）、`sort_order`；`tone`：`positive`／`negative`；`comment_required`：`true` 時走這個出口必須留言。
- `required_data`：走這個出口前票上必須有的資料，最多 10 項，帶 `null` 清空。只在正常流轉時檢查，rollback、exception、jumpback 不檢查。缺資料時流轉回 422，錯誤在 `errors.required_data`。

`required_data` 每項的格式：

| type | 欄位 |
|---|---|
| `attribute` | `label`（≤64 字）、`key`（票屬性 key，`^[a-z0-9_.-]{1,64}$`）、`optional`、選填 `format`（`date`）、`pattern`（正規表示式） |
| `attachment` | `label`、`patterns`（1–5 個檔名樣式，可用 `{屬性key}` 代入票屬性）、`min`（1–10，預設 1）、`scope`、`optional` |
| `project` | `label`；票必須綁專案，不能設成選填 |

附件的 `scope`：`task`（預設，票上任何時候附過都算）、`transition`（只算這次流轉附的）、`station_visit`（只算這次進站後附的）。退件後要交新版的出口用 `station_visit`。

```bash
curl -s -X PATCH "https://station-flow.staging.quanhox.com.tw/api/v1/transitions/88" "${H[@]}" \
  -d '{"tone":"negative","comment_required":true,
       "required_data":[{"type":"attribute","label":"假別","key":"leave_type"}]}'
```

刪除單條：只能刪 normal，開始站最後一個出口不能刪（422）。

## Mermaid 流程圖

`GET https://station-flow.staging.quanhox.com.tw/api/v1/processes/{id}/diagram` 回 `{"mermaid":"graph TD ..."}`。節點 id 是 `S<站 id>`；正常出口畫實線，退回出口畫虛線並標名稱。執行時的 rollback、exception、jumpback 不畫。

```bash
curl -s "https://station-flow.staging.quanhox.com.tw/api/v1/processes/12/diagram" "${H[@]}"
```

## 例行事務（routine）

| 端點 | 用途 |
|---|---|
| `GET https://station-flow.staging.quanhox.com.tw/api/v1/routines` | 列表；admin 可加 `?for_user=me` 或 `?for_user=<user id>` 只看某人有角色的；非 admin 一律只看到自己有角色的 |
| `POST https://station-flow.staging.quanhox.com.tw/api/v1/routines` | 建立（admin）：`name`（必填，組織內不可重複）、`process_id`（必填）、`description`、`sort_order`、`owner_user_id`（事務負責人，省略＝建立者） |
| `GET https://station-flow.staging.quanhox.com.tw/api/v1/routines/{id}` | 詳情，含角色清單 |
| `PUT https://station-flow.staging.quanhox.com.tw/api/v1/routines/{id}` | 修改 `name`、`description`、`sort_order`、`owner_user_id`（admin） |
| `DELETE https://station-flow.staging.quanhox.com.tw/api/v1/routines/{id}` | 刪除（admin） |

**事務負責人**（`owner_user_id`）是需求方的窗口（一位、須為組織成員），自動是開始、結束、異常站的處理者，並負責存查；換人時四站換掉舊的、其他人不動。詳情回 `owner`（`{id, name}` 或 `null`），任務詳情回 `routine_owner`。

```bash
curl -s -X POST "https://station-flow.staging.quanhox.com.tw/api/v1/routines" "${H[@]}" \
  -d '{"name":"2026 請假","process_id":12}'
```

## 每站指派處理者

任務只有該站綁定角色的成員能推進，**admin 也一樣**。開始站的角色決定誰能開票（`POST https://station-flow.staging.quanhox.com.tw/api/v1/tasks` 不在開始站角色裡回 403）。有兩種做法，可以混用。

### 做法一：每站直接指派（建議）

| 端點 | 用途 |
|---|---|
| `GET https://station-flow.staging.quanhox.com.tw/api/v1/routines/{routineId}/station-assignees` | 流程現行版本的每一站一列：`station_id`、`station_name`、`station_type`、`role`（`id`、`name`、`is_auto`，沒綁是 `null`）、`users` |
| `PUT https://station-flow.staging.quanhox.com.tw/api/v1/routines/{routineId}/stations/{stationId}/assignees` | 指派（admin）；body `{"user_ids":[...]}`，空陣列 = 取消指派 |

系統自動建角色（「`<站名> 處理者`」），成員相同就共用，沒綁站就自動刪。`user_ids` 必須是組織成員，否則 422。

```bash
curl -s -X PUT "https://station-flow.staging.quanhox.com.tw/api/v1/routines/7/stations/43/assignees" "${H[@]}" \
  -d '{"user_ids":[5,9]}'
```

### 做法二：手動角色（同一組人管多站時）

| 端點 | 用途 |
|---|---|
| `GET https://station-flow.staging.quanhox.com.tw/api/v1/routines/{routineId}/roles` / `POST https://station-flow.staging.quanhox.com.tw/api/v1/routines/{routineId}/roles` | 列出／建立角色（`name`、`sort_order`） |
| `PUT https://station-flow.staging.quanhox.com.tw/api/v1/roles/{id}` / `DELETE https://station-flow.staging.quanhox.com.tw/api/v1/roles/{id}` | 改名／刪除角色 |
| `GET https://station-flow.staging.quanhox.com.tw/api/v1/roles/{id}/users` | 角色成員 |
| `POST https://station-flow.staging.quanhox.com.tw/api/v1/roles/{id}/users` | 加成員：`{"user_id":5}`（已在角色或不屬於組織回 422） |
| `DELETE https://station-flow.staging.quanhox.com.tw/api/v1/roles/{id}/users/{userId}` | 移除成員 |
| `GET https://station-flow.staging.quanhox.com.tw/api/v1/routines/{routineId}/station-roles` / `POST https://station-flow.staging.quanhox.com.tw/api/v1/routines/{routineId}/station-roles` | 列出／建立站與角色的綁定：`{"station_id":43,"role_id":3}`；一站只綁一個角色，重複回 422 |
| `PUT https://station-flow.staging.quanhox.com.tw/api/v1/station-roles/{id}` / `DELETE https://station-flow.staging.quanhox.com.tw/api/v1/station-roles/{id}` | 換綁的角色（`role_id`）／解除綁定 |

寫入都要 admin。讓系統站少了事務負責人的寫入（含成員、角色、綁定）一律 422 且不生效。手動角色和舊資料的「流程負責人」角色不會被自動刪除；換人只改角色成員，綁這個角色的站一起生效。

## 設計原則

### 切站：換手才開站

一個站至少要符合下面一條才值得存在，都不符合就併進前一站：

1. 責任換手：下一段的執行者可能跟這段不同（看「可能」，不是看現在誰在做）。
2. 要有人拍板才能繼續。
3. 要等外部回覆或排程，票停在這裡是合理的。
4. 這一段的耗時需要單獨看得出來。

站名寫「在做什麼」（`審核`），不寫「誰」，因為人會換。

### 人站與 agent 站

- 判斷偏好、承擔後果的給人（定方向、驗收、對外承諾、花錢、不可逆動作）；照既定標準執行的給 agent（拆規格、實作、測試、彙整、歸檔）。
- 先假設 agent 做得到，答不出「agent 憑什麼替他決定」才改成人站。人站要少而集中，例如前面拍板一次、後面驗收一次。
- 會自動撿票的 agent 不要跟人共用角色，否則 agent 會撿到該角色所有站的票。

### 出口

- 有兩個以上出口的站一定要命名。名稱寫「選這條會發生什麼」，不寫「去哪一站」。
- 打回上一站重做用系統的 rollback，不要另拉一條反向線。要依原因退到不同站時才建退回出口（`tone: negative`），並開 `comment_required`。
- 送異常是給事務負責人決定票回哪一站；要跳好幾站回去或要特別處理時才用。
- 「選 X 之前一定要有 Y」用 `required_data` 擋。只設「選這條時一定有值」的資料，可能合法空白的設下去，票會永遠送不出去。設之前先查停在這站的票缺不缺。
- 給 agent 撿的站，出口名只用文字、數字、`-`、`_`，不放標點和空白，比對才不會出錯。

### 說明怎麼寫

- `execution_instructions` 只寫「選『核可』」「選退回」，不寫「推進到 X 站」。線會改，寫死的站名漏改不會報錯，執行者只會照錯的做。
- 執行者通常只看當前站的說明。每站都要遵守的規則寫進 `common_instructions`，或在每站重寫一次。
- 決策站寫明「撿到票就要選一個出口；拿不準就退回並寫明要改什麼」。要交檔案的站寫「流轉時附上 X」。

### 需求不清楚時先訪談

除非開票時規格就完整，開始站後第一站做成「需求訪談」：問清楚目標、現有資料、限制，整理成文件附在票上，審核站要有退回訪談站的出口。先畫草圖讓需求方回「對／不對／哪裡改」，不要丟大問卷。

### 建置順序

1. `POST https://station-flow.staging.quanhox.com.tw/api/v1/processes`：自動產生四個系統站與預設出口。
2. `POST https://station-flow.staging.quanhox.com.tw/api/v1/processes/{processId}/stations`：逐站建立。`sort_order` 留間隔（10、20、30），之後插站不用重編。
3. `PUT https://station-flow.staging.quanhox.com.tw/api/v1/stations/{id}`：用 `next_station_ids` 串線，多出口時一起帶 `next_station_names`。
4. `PUT https://station-flow.staging.quanhox.com.tw/api/v1/transitions/{id}`：設定退回類別、必填意見、必附資料。
5. `POST https://station-flow.staging.quanhox.com.tw/api/v1/routines`：建例行事務，`owner_user_id` 帶真正的需求方窗口（四個系統站會自動有他）。
6. 指派每一個會停票的站；四個系統站需要別人一起處理時再加協作者。
7. `GET https://station-flow.staging.quanhox.com.tw/api/v1/processes/{id}/diagram` 看整條線，`GET https://station-flow.staging.quanhox.com.tw/api/v1/routines/{routineId}/station-assignees` 確認沒有 `role: null` 的站。

### 常見地雷

- `owner` 為 `null` 的舊 routine 系統站可能沒人：漏綁「結束」無法歸檔，漏綁「存查」撤不回來。
- 負責人預設是建立者，通常不是真正的窗口；建立時就帶 `owner_user_id`。
- 票一律從開始站建立。開始站綁會開票的委託方（或代表委託方的窗口），不是處理方。
- 上線前用一張真實的票走完全程，並實際走一次 rollback 或 exception → jumpback。「票開得出來」不等於能上線。
- 交給 agent 的站，先確認那個 agent 真的會定期查自己的待辦，否則票會停著沒人撿。
