需求方窗口
需求方(組織身份 requester)在 /r 前台留言提需求,一則需求是一條對話(conversation)。組織裡的窗口負責回覆、開票、回報進度。這份說明給當窗口的人或 agent 用。
誰是窗口#
- 接待窗口:組織設定的一位(
GET https://station-flow.staging.quanhox.com.tw/api/v1/auth/me回的organization.requester_reception_user_id)。還沒開票的對話、被退回的對話都歸他。 - 例行事務窗口=該例行事務的事務負責人(
GET https://station-flow.staging.quanhox.com.tw/api/v1/routines/{id}回的owner)。開票後對話轉給這條 routine 的負責人。 - 負責人不是本組織 admin 或 staff(被移出組織、或改成需求方)時,由接待窗口代接。
- 換事務負責人時,舊負責人手上這條 routine 還沒結束的對話會一起轉給新負責人。
- 每條對話的
handler_user_id是目前的負責窗口。只有他能回覆、開票、轉手;其他人回 409not_current_handler。
被叫醒:事件游標#
curl -s "https://station-flow.staging.quanhox.com.tw/api/v1/me/requester-events?since=0" \
-H "Authorization: Bearer $SF_KEY" -H "Accept: application/json"
回 data(事件陣列)、next_since、has_more。下次把 next_since 帶進 since;has_more 是 true 就馬上再抓一頁(一頁 100 筆)。事件只回 5 秒前建立的,不會漏。
每筆事件有 id、type、conversation_id、task_id、created_at,不帶內文,要看內容再打對話詳情。自己做的動作不會叫醒自己。
| type | 什麼時候 |
|---|---|
new_request |
需求方開了新對話 |
requester_post |
需求方在對話裡留言 |
handoff |
對話轉給你(開票轉手、退回、換接待窗口、換事務負責人) |
unbound_4h |
對話超過 4 小時還沒開票 |
handler_ack_overdue |
需求方留言超過 15 分鐘負責窗口還沒回 |
progress_updated |
票的進度更新 |
questionnaire_answered |
需求方答完問卷 |
confirm_clicked/confirm_rejected |
需求方按了確認卡的確認/不同意 |
confirm_overdue |
確認卡逾時沒回 |
對話 API#
以下都在 https://station-flow.staging.quanhox.com.tw/api/v1/requester/conversations 底下,帶同樣兩個 header。讀不到的對話回 404。
列表與詳情
GET https://station-flow.staging.quanhox.com.tw/api/v1/requester/conversations:你的收件匣,需求方在等回覆的排最上。unbound=1只看未開票,project=<id>篩專案。每列有handler_user_id、task_id、awaiting_handler、unread。GET https://station-flow.staging.quanhox.com.tw/api/v1/requester/conversations/{id}:詳情與貼文。帶since=<上次的 last_post_id>只抓新貼文。貼文的visibility是requester(需求方看得到)或internal(內部備註)。POST https://station-flow.staging.quanhox.com.tw/api/v1/requester/conversations/{id}/read:標已讀,可帶post_id,省略=讀到最新。
回覆
curl -s -X POST "https://station-flow.staging.quanhox.com.tw/api/v1/requester/conversations/42/posts" \
-H "Authorization: Bearer $SF_KEY" -H "Accept: application/json" \
-F "body=收到,我們今天下午回您" -F "files[]=@draft.png"
POST https://station-flow.staging.quanhox.com.tw/api/v1/requester/conversations/{id}/posts:body必填;internal=1存成內部備註;files[]最多 10 個、每個 50MB。POST https://station-flow.staging.quanhox.com.tw/api/v1/requester/conversations/{id}/replied:不留言,只標「已回覆」。
開票與分流
curl -s -X POST "https://station-flow.staging.quanhox.com.tw/api/v1/requester/conversations/42/tickets" \
-H "Authorization: Bearer $SF_KEY" -H "Accept: application/json" -H "Content-Type: application/json" \
-d '{"tickets":[{"name":"廣告 A","routine_id":7,"project_id":3}],"handoff_summary":"需求原話、已問到的資訊、選這條流程的理由"}'
POST https://station-flow.staging.quanhox.com.tw/api/v1/requester/conversations/{id}/tickets:tickets1–20 張,每張要name、routine_id、project_id。開票後對話轉給該 routine 的窗口;換人時handoff_summary必填(422handoff_summary_required)。同一人不用寫。已開過票回 409already_bound;你不在開始站角色、又不是 admin/接待窗口/事務負責人回 403routine_forbidden。POST https://station-flow.staging.quanhox.com.tw/api/v1/requester/conversations/{id}/split:選幾則貼文(post_ids)拆成新對話+新票,tickets只取第一張。POST https://station-flow.staging.quanhox.com.tw/api/v1/requester/conversations/{id}/handoff:已開票的對話重新交給該 routine 的現任窗口,可帶handoff_summary。沒開票回 422not_bound。POST https://station-flow.staging.quanhox.com.tw/api/v1/requester/conversations/{id}/return:覺得不是你的,退回接待窗口,reason必填。沒開票或你就是接待窗口回 409cannot_return。
從票開對話(票是窗口自己開的、沒有需求方對話時)
POST https://station-flow.staging.quanhox.com.tw/api/v1/requester/tasks/{task}/conversations:替這張票對指定需求方開一條對話並綁票。requester_user_id必填,body(第一則訊息,10000 字內)可省略。回 201{"data":{"conversation_id":…,"task_id":…}}。你就是這條對話的負責窗口,之後照常用上面的posts/cards/progress。
curl -s -X POST "https://station-flow.staging.quanhox.com.tw/api/v1/requester/tasks/120/conversations" \
-H "Authorization: Bearer $SF_KEY" -H "Accept: application/json" -H "Content-Type: application/json" \
-d '{"requester_user_id":31,"body":"今天的題目請您確認"}'
- 只有該票 routine 的事務負責人(owner)或組織 admin 能開,其他人 403
task_forbidden。票或需求方不在你目前的組織回 404。 requester_user_id可以是本組織的需求方,或開了「兼任需求方」開關的內部成員(admin/staff,見下)。兩者都不是回 422(errors.requester_user_id)。- 票已經有對話(不論是不是同一位需求方)回 409
conversation_exists,回應多帶conversation_id,直接沿用那條,不會重建也不會改綁。票已掛另一位需求方回 409requester_mismatch。 - 需求方在
/r會多一列,看得到票的狀態與進度,收到推播(有開推播時)。
內部成員兼任需求方(同一個帳號,不改角色)
PUT https://station-flow.staging.quanhox.com.tw/api/v1/organization/members/{user}/requester-access,body{"enabled": true|false}:只有目前組織的 admin 能設,其他人 403;對象不在本組織 404;對象本身是需求方回 422(不需要開關)。回 200{"data":{"user_id":…,"role":…,"requester_access":…}}。- 開了之後,這位成員可以被上面的端點指定為需求方,收到
conversation_opened事件與/r推播;他從後台登入後開/r,只看得到自己是需求方的對話。後台權限不受影響。 - 關掉後立刻失效,下一個 request 起
/r就進不去。目前沒有設定畫面,只能用 API。
進度、交付、卡片
POST https://station-flow.staging.quanhox.com.tw/api/v1/requester/conversations/{id}/progress:body(500 字內)、expected_on(預計日期,可省略)。POST https://station-flow.staging.quanhox.com.tw/api/v1/requester/conversations/{id}/deliverables:把對話裡的附件(attachment_id)標成交付物,放進專案的公開目錄。票沒掛專案回 422project_required。POST https://station-flow.staging.quanhox.com.tw/api/v1/requester/conversations/{id}/cards:發問卷或確認卡。
curl -s -X POST "https://station-flow.staging.quanhox.com.tw/api/v1/requester/conversations/42/cards" \
-H "Authorization: Bearer $SF_KEY" -H "Accept: application/json" -H "Content-Type: application/json" \
-d '{"kind":"questionnaire","body":"請選尺寸","questions":[{"label":"尺寸","options":["1:1","9:16"]}]}'
kind 是 questionnaire 或 confirm。問卷最多 20 題、每題 2–20 個選項;確認卡要先開票(422 not_bound)。
卡片綁定發卡當下的票和站。票離開那一站(送出口、退回、改綁別張票),還沒答的卡就失效:GET 對話時卡片帶 stale: true,需求方前台顯示「已處理,不需要回覆」,按不了;硬送回 409 card_handled。開票前發的問卷沒綁票,不會失效。
需求方前台每題會自動多一個「其它」選項(不會寫進 questions[].options,發卡時別自己加)。需求方選其它時可寫文字、附檔或貼上圖片。答完後 GET 對話,card 帶:
-
answers[i]:選項字串;選其它時是"其它"。 -
others:物件,題號 →{"text": "…或 null", "attachment_ids": [12]};沒有人選其它時是null。附件在同一次GET的需求方回覆貼文posts[].attachments裡,用 id 對得上。 -
有
attribute_key的題目選了其它,票屬性寫文字;只有附件時寫其它(見附件)。 -
等需求方回答時,別先送出口。答案回來(
questionnaire_answered/confirm_clicked/confirm_rejected事件)再推進。 -
已經轉站了,就在新站重發一張,舊卡不用管。
需求方看到的狀態:站屬性 requester_dialog#
站的 requester_dialog(PUT https://station-flow.staging.quanhox.com.tw/api/v1/stations/{id},admin 設)決定需求方在 /r 看到什麼。只能是 discuss、deliver 或 null:
discuss:討論站。票停在這站時,需求方看到「等您回覆」。deliver:交付站。票從這站走正常出口(不是退回)離開,就算交付。null:內部站(預設)。需求方只看到「製作中」。
綁票後的狀態,依序判斷:
- 曾經從交付站走正常出口離開=已交付。之後被退回或取消也不變。
- 票在結束站(
end)或存查站(archive):最後一次進結束站時走的是取消類出口=已取消,其他=已交付。流程沒設交付站也一樣。最後一次進結束站時,若來源站只有一條出口(例如事務報告),就往前看進入那一站的出口,直到遇到有選擇的出口。 - 票在討論站,或有需求方還沒答的問卷/確認卡=等您回覆。
- 票在開始站=受理中;其他=製作中。
取消類出口:出口 tone 是 negative,或出口的來源站是異常站(type=exception)。要讓需求方看到「已取消」,往結束站的取消出口要設 tone: negative,或從異常站接過去。
錯誤#
錯誤回 {"code": "...", "message": "..."}。常見:
| 狀態 | code | 意思 |
|---|---|---|
| 403 | — | 需求方的 key 打不進這組 API |
| 404 | — | 這條對話你讀不到 |
| 409 | not_current_handler |
你不是目前的負責窗口 |
| 409 | conversation_exists |
這張票已經有對話,回應帶 conversation_id |
| 409 | card_handled |
卡片已經處理過(需求方每張卡只能答一次),或票已離開發卡那一站 |
| 422 | 見上 | 缺欄位或順序不對 |
限制#
- 需求方只能用
/r前台,API 不開放。 - 推播給需求方的通知只說「有新訊息/新進度」,不帶內文。