手冊首頁 / 溝通協作 / 問卷填寫 / 預覽

問卷填寫 / 預覽(Survey Fill / Preview)

功能群:問卷|資料模型與雙生命週期先讀 功能群總覽

事實基準:2026-07-05 從 FE(views/survey-v2/SurveyPreview.vuecomposables/useSurveySocket.js)/ BE(api/task_surveyapp/task_survey)code 掃出;表結構取自 db_schema.jsonsurvey schema);error code 對 common/code/error_code.py

1. 功能描述

本頁是問卷消費端:同一個 SurveyPreview.vueroute.query.mode 服務五種模式,涵蓋兩條 route——

  • 預覽 /survey/preview/:id(作者端):唯讀渲染問卷結構,不落資料、不查權限、不連 socket(由設計器/管理頁的預覽鈕開啟)。
  • 填寫 /survey/fill?taskSurveyUid=…&mode=fill(消費端):稽核任務執行者對綁定的問卷快照作答;載入 task_survey + 既有答案 + 權限,連 socket 即時協作,答案持久化。另有 review(審核人給分 / 回饋)、view / history(唯讀檢視 / 看歷史版本)子模式。

填寫的是綁定任務時的問卷快照is_snapshot=1),非 live 範本(見 _overview §4)。

主要使用者:預覽 = 問卷作者;填寫 = 任務執行者(受稽方);審核 = 任務指派的審核人(is_approver)。

角色速覽(消費端角色,見 _overview §5):

角色 一句話
填寫者 任務執行者(受稽方)——canEdit;作答、送審、補充
審核人 任務指派時 is_approver 的人——canVerify;給分 / 回饋 / 結案
預覽者 問卷作者——preview 模式唯讀,無持久化

1.1 功能總覽(本頁全部功能)

# 功能 說明 位置 詳述
1 模式分流 preview / fill / review / view / history 五模式共用一個 view 進頁 §10
2 題目渲染(依型別) text / textarea / radio / checkbox / dropdown / fileupload / link 中欄 §10
3 選項補充輸入 選項勾選後展開補充欄(ext_answer) 中欄 §10
4 檔案上傳題 上傳(≤50MB)/ 下載 / 預覽(PDF·文字 iframe) 中欄 §6.1
5 進度導覽 完成度百分比 + 頁面樹(已答 / 未答) 左欄 §5
6 頁面切換 上一頁 / 下一頁 / 頁碼點 中欄底 §5
7 儲存草稿 checkpoint status=1 暫存 頂列 UC-SVF-01
8 即時暫存(單題) patch 單題(無歷史、不改狀態;socket 同路徑) 作答時 UC-SVF-04
9 送出 / 送補充 填寫者送審(→2)/ 補充後重送(3→2) 頂列 UC-SVF-01
10 審核給分 / 回饋 審核人評分(★)+ 回饋,退回補充(2→3) 中欄審核區 / 頂列 UC-SVF-01
11 結案 免審核直接結案 / 審核通過結案(→9) 頂列 UC-SVF-01
12 即時協作 多人同填:presence、編輯鎖、答案廣播 socket UC-SVF-02
13 歷史版本 / 還原 看版本、載入檢視、還原到某版 歷史側欄 UC-SVF-03
14 討論串 問卷討論訊息 討論側欄 §6.1
15 匯入答案 / 範本 Excel 批次匯入答案、下載範本 (任務層) §6.1

UC(§2)已展開本頁所有具實質後端寫入邏輯的功能:7-11(狀態機 checkpoint,UC-SVF-01)、8(單題 patch,UC-SVF-04)、12(協作,UC-SVF-02)、13(歷史還原,UC-SVF-03)。純顯示 / 導覽類(1 模式分流、2 題目渲染、3 選項補充、5 進度導覽、6 頁面切換)見 §5 / §10;檔案上傳(4)、討論串(14)、答案 Excel 匯入 / 範本(15)屬常規 CRUD / 任務層,細節見 §6.1 / GAI-SD-02;preview 模式無作答 / 持久化。

2. Use Case

角色速覽(消費端角色,見 _overview §5):

角色 一句話
填寫者 任務執行者(受稽方)——canEdit;作答、送審、補充
審核人 任務指派時 is_approver 的人——canVerify;給分 / 回饋 / 結案
預覽者 問卷作者——preview 模式唯讀,無持久化

canEdit / canVerifyPOST /task-assignees 載入指派名單後判定。流程圖視覺慣例:菱形 = 判斷、橘底 = 例外、紅框 = 錯誤(標 error code)、綠框 = 成功終點、虛線 = 可選路徑。

UC-SVF-01 填答 → 送審 → 審核 → 結案

項目 內容
角色 填寫者(作答 / 送審 / 補充)、審核人(回饋 / 結案)
前置條件 fill 模式、task_survey 存在;動作鈕依 status + canEdit / canVerify + approval_required 顯示
產出 / 後置條件 暫存走 patch(無歷史);狀態變更走 checkpoint(交易內補齊空列 + 寫歷史 + 通知)
uc_svf_01 UC-SVF-01 填答 → 送審 → 審核 → 結案(狀態機 + 持久化) s0 待填寫 (0) s1 編輯中 (1) s0->s1 開始作答(儲存草稿 status=1) s1->s1 儲存草稿 / patch 暫存 s2 待審核 (2) s1->s2 送出(需審核 approval_required) s9 完成 (9) s1->s9 結案(免審核 canEdit) patch 即時暫存(單題) POST .../answers/{uid}/patch (無歷史、不改狀態;socket 也走這條) s1->patch cp checkpoint(批次 + 狀態) POST .../answers/{uid}/checkpoint 交易內:套 patch → 補齊空列 → 改 status → 寫歷史+明細 s1->cp s3 待補充說明 (3) s2->s3 送回饋(審核人 canVerify → 退回補充) s2->s9 結案(審核人 canVerify) s3->s2 送補充(填寫者重送審)
UC-SVF-01:0→1→2→(3⇄2)→9 狀態機;patch 暫存 vs checkpoint 落狀態

UC-SVF-02 多人即時協作

項目 內容
角色 填寫者 / 審核人(`canEdit
前置條件 fill 模式;room = taskSurveyUid
產出 / 後置條件 join 時 dirty-aware 合併快照(本地未存答案不被覆蓋);update 廣播權威覆蓋;還原觸發全房 reload
uc_svf_02 UC-SVF-02 多人即時協作(socket /socket/fill-survey,room = taskSurveyUid) join 進入 fill → join {user, room} joined broadcast joined (>1 人時附 DB 最新快照) FE dirty-aware 合併:本地未存的答案不被覆蓋 join->joined edit 開始編輯某題 → edit broadcast lock(他人看到「X 正在編輯 q123」) joined->edit update 作答變更 → update(debounce 200ms) BE patch_task_survey_answer 落庫 broadcast updated(權威、直接覆蓋他端) edit->update endedit 結束編輯 → endedit → broadcast unlock update->endedit reload 他人還原歷史 → broadcast reload → 全房重新 GET answers update->reload 還原事件 leave 離開 → leave → broadcast left endedit->leave
UC-SVF-02:join/lock/update 廣播/reload socket 協作

UC-SVF-03 歷史版本檢視 / 還原

項目 內容
角色 填寫者 / 審核人(非最新且 status≠9 才可還原)
前置條件 有歷史版本(每次 checkpoint 寫一版)
產出 / 後置條件 還原套回當前答案 + 寫新歷史(type=R)+ 廣播 reload
uc_svf_03 UC-SVF-03 歷史版本檢視 / 還原 open 開歷史側欄 menu GET /project-survey/histories/menu/{uid} 列版本(建立時間 / 使用者 / 當前·還原標記) open->menu load 載入某版 POST /project-survey/history-details → 填回畫面(唯讀檢視) menu->load revertq 還原此版? load->revertq err TASK_SURVEY_404003 / 404004 歷史主檔 / 明細不存在 load->err 查無 revert POST /project-survey/history/revert {task_survey_uid, history_uid} 套回當前答案 + 寫新歷史 type=R revertq->revert 是(非最新且 status≠9) done 畫面更新為還原後答案 revertq->done 否(只檢視) socket broadcast reload → 全房重載 revert->socket socket->done
UC-SVF-03:歷史清單→載入檢視→還原(寫 type=R 歷史 + reload)

UC-SVF-04 單題即時暫存(patch,不改狀態 / 不寫歷史)

項目 內容
角色 填寫者 / 審核人(canEdit || canVerify;作答變更即觸發)
前置條件 fill 模式、task_survey 存在;題目屬本問卷
產出 / 後置條件 單題 upsert 到 question_answers不寫歷史、不改 task_survey.status;socket update 廣播走同一路徑
uc_svf_04 UC-SVF-04 即時暫存(單題 patch,不改狀態 / 不寫歷史) start 填寫者作答變更(某題) dirty markDirty + debounce (避免每次鍵入都送) start->dirty patch POST /project-survey/answers/{uid}/patch patches[] = {question_uid, answer{...}} dirty->patch upsert 單題 upsert 不寫歷史、不改 task_survey.status patch->upsert err TASK_SURVEY_404006 題目不存在 patch->err 查無題目 socket socket update 廣播同房 (權威覆蓋;走同一 patch 邏輯) upsert->socket fill 協作時 done 本地 state 同步(暫存完成) 狀態變更另走 checkpoint(UC-SVF-01) upsert->done
UC-SVF-04:作答變更→markDirty+debounce→patch 單題 upsert(不改狀態 / 不寫歷史;socket update 同路徑)

3. 權限矩陣

操作 FE 判定 BE 強制 BE 檢查位置
預覽(preview) route meta.requiresAuth @jwt_required();只讀問卷結構 api/survey/routes/survey_route.py::SurveyRoute.get
載入填答 fill 模式帶 taskSurveyUid @jwt_required();task_survey 不存在擋 404 api/task_survey/routes/task_survey_route.py
作答 / 儲存 / 送審 canEdit(填寫者,依 task-assignees) @jwt_required();狀態由送入的 status 決定(無 BE 狀態轉換守門,見 §12) question_answer_service.py::checkpoint_task_survey_answer
給分 / 回饋 / 結案 canVerify(審核人 is_approver)+ status===2 @jwt_required() 同上
歷史還原 非最新版且 status≠9 @jwt_required() question_answer_history_service.py::revert_question_answer_from_history

⚠️ 可否編輯 / 審核的判定在 FEcanEdit / canVerify 由前端算),BE 端點僅 @jwt_required()、不重新驗證角色與狀態合法性——繞過 FE 可送任意 status。收進 §12。

4. 狀態機與前置條件

消費端填答狀態機(task_surveys.statusTaskSurveyStatusCode):

狀態值 名稱 誰可動作 出處
0 待填寫 UNFILLED 填寫者開始作答 app/task_survey/common/task_survey_code.py
1 編輯中 EDITING 填寫者作答 / 儲存草稿 / 送審 同上
2 待審核 UNDER_REVIEW 審核人給分 / 回饋 / 結案 同上
3 待補充說明 ADD_NOTES 填寫者補充後重送審 同上
9 完成 COMPLETED 終態,動作鈕停用 同上

轉換:0/1→2(送審,需 approval_required)、1→9(免審核結案)、2→3(退回補充)、3→2(重送審)、2→9(審核通過結案)。轉換合法性只在 FE 動作鈕條件把關,BE checkpoint 直接寫入送入的 status(§12)。

gate 效果 出處
checkpoint 交易 套 patch → 補齊所有題的空列(score=0/answer=null,供乾淨還原)→ 改 status → 每題寫歷史 + 明細 question_answer_service.py::checkpoint_task_survey_answer
status 變更 呼叫 notify_task_survey_assignee_status_change 通知對應角色 同上
審核區顯示 status>1 且 mode≠preview 且 approval_required 才顯示評分 / 回饋;可編輯需 canVerify && status===2 SurveyPreview.vue showReviewFields

5. UI 設計

📸 實際畫面截圖待補(一律亮色模式拍攝;截圖另有專責 session)。預計補到 assets/img/: ① fill 整頁(左進度 + 中題目)fill-overview.png ② 各題型渲染(單選 + 補充 / 檔案上傳 / 連結)fill-question-types.png ③ 審核區(評分 + 回饋)fill-review-section.png ④ 歷史側欄 / 討論側欄 fill-sidebars.png ⑤ preview 模式唯讀 preview-readonly.png

版面骨架與區塊職責(頂列狀態+動作、左進度導覽、中題目作答、歷史/討論側欄;彩色區塊可點跳對應小節錨點,API 僅標代表性):

跳至 §2 Use Case(UC-SVF-01 填答→送審→審核→結案 狀態機) Header:狀態徽章(0→1→2→3⇄→9)+ 動作鈕(儲存草稿/送審/補充/給分回饋/結案) GET /task-survey/{uid} ・ POST /project-survey/answers/{uid}/checkpoint 作答工作區 — 五模式共用 SurveyPreview.vue(preview/fill/review/view/history) 左進度導覽 完成度% 頁面樹(已答/未答) 頁碼點・上/下一頁 GET /survey/{uid} GET …/answers/{uid} 跳至 §2 Use Case(UC-SVF-04 單題即時暫存 patch) 中欄題目作答(依型別渲染) text/radio/checkbox/dropdown 選項補充輸入・檔案上傳・連結 審核區:評分★/回饋(status>1) POST /project-survey/answers/{uid}/patch (單題 upsert,不寫歷史/不改狀態) → 點我跳 §2 UC-SVF-04 跳至 §2 Use Case(UC-SVF-03 歷史版本檢視/還原) 歷史版本側欄 版本清單/載入檢視 還原(寫 type=R + reload) POST /project-survey/ history/revert → §2 UC-SVF-03 討論側欄/協作 問卷討論串 socket presence/編輯鎖 答案廣播(UC-SVF-02) socket join/lock/update (fill 模式即時協作) 彩色=可點跳本頁小節錨點(框內標錨點+代表性 API)|灰=本頁描述|虛線框=依角色/模式才出現的側欄層 API 僅列代表性 2~3 條,完整規格見 §6
問卷填寫頁版面 wireframe:頂列狀態+動作、左進度導覽、中題目作答、歷史側欄與討論/協作側欄

模式分流:

svf_mode_split 同一個 SurveyPreview.vue 服務五種模式(route.query.mode || 'preview') r1 /survey/preview/:id (設計器 / 管理頁預覽鈕) preview preview 模式 只 getSurveyById(id) 唯讀渲染、不落資料 不查權限、不連 socket r1->preview r2 /survey/fill?taskSurveyUid=…&mode=fill (任務抽屜新分頁開啟) fill fill / review / view / history 模式 載入 task_survey + answers + 權限 canEdit / canVerify 決定可否編輯 連 socket 即時協作 r2->fill fill2 review:審核人給分 / 回饋(status=2) view / history:唯讀檢視 / 看歷史版本 fill->fill2
同一 SurveyPreview.vue 服務五模式(preview / fill / review / view / history)

狀態呈現:頂列動作鈕依 status + 角色動態顯示;題目依 type 分支渲染(唯讀 vs 可編輯由 canEdit / disabled 控);連結題唯讀時 sanitizeHrefjavascript: / data:;線上使用者頭像來自 socket presence。有 socket(fill 模式即時協作)。

6. API 規格

Envelope:成功 {"status": true, "data": …}、失敗 {"error_code": "…", "msg": "…"}。消費端走 api.task_survey 模組(/task-survey*/project-survey/*)。

6.1 總清單(本頁呼叫的全部 endpoint)

分類 Method + Path 說明 完整規格
結構 GET /survey/{uid} 問卷(快照)結構供渲染 designer §6.2
任務問卷 GET /task-survey/{uid} task_survey 中繼(survey_uid / status / approval_required / task_uid) §6.2
任務問卷 PUT /task-survey/{uid}・PUT /task-survey/configured/{uid} 更新 task_survey / 審核設定 GAI-SD-02
答案 GET /project-survey/answers/{uid} 載入全部答案 {question_uid: 答案} §6.2
答案 POST /project-survey/answers/{uid}/patch 單題暫存(無歷史 / 不改狀態) §6.2
答案 POST /project-survey/answers/{uid}/checkpoint 批次 + 狀態(寫歷史) §6.2
歷史 GET /project-survey/histories/menu/{uid}・POST /project-survey/histories 版本清單 GAI-SD-02
歷史 POST /project-survey/history-details 某版明細 §6.2
歷史 POST /project-survey/history/revert 還原到某版 §6.2
討論 POST /survey-discussions(載入)・POST /survey-discussion(送出) 問卷討論串 GAI-SD-02
指派 POST /task-assignees 載入指派名單 → 判 canEdit / canVerify GAI-SD-02
匯入 GET /task-survey/import-template/{task_uid}・POST /task-survey/import-answers 答案 Excel 範本 / 匯入 GAI-SD-02
檔案 POST /upload-file・GET /download-file/{uid}・GET pdf-preview 檔案上傳題 GAI-SD-02

6.2 核心 endpoint(欄位對照 serializer / service,出處標段末)

[GET] /project-survey/answers/{uid} — 載入答案

回一個 map:key = question_uid,value = 該題答案物件(取最新,DISTINCT ON 去重)。value 物件欄位:

欄位 型別 說明
answer jsonb|null 答案
ext_answer jsonb|null 補充答案
feedback jsonb|null 回饋
ext_feedback jsonb|null 補充回饋
score int 得分(null 正規化為 0)

task_survey 不存在擋 TASK_SURVEY_404001。出處:app/task_survey/service/question_answer_service.py::get_answer_list_by_task_survey_uid:47

[POST] /project-survey/answers/{uid}/patch — 單題暫存

Request:

{
  "patches": [
    {
      "question_uid": "string",
      "answer": {
        "answer": "string",       // 選填
        "ext_answer": "string",   // 選填
        "feedback": "string",     // 選填
        "ext_feedback": "string", // 選填
        "score": 0                // 選填
      }
    }
  ]
}

單題 upsert,不寫歷史、不改狀態(socket update 也走此邏輯)。題目不存在擋 TASK_SURVEY_404006。出處:question_answer_service.py::patch_task_survey_answer:313、serializer api/task_survey/serializers/question_answer_patch.py

[POST] /project-survey/answers/{uid}/checkpoint — 批次 + 狀態

Request:

{
  "status": "string",
  "patches": []  // 同上(見上一個 endpoint  patches 結構)
}

交易內:① 套用 patch ② 為每題補空列(score=0 / answer=null,確保「所有題都有列」利於乾淨還原)③ 更新 task_survey.status ④ 每題寫 question_answer_histories + _details ⑤ status 變更時通知指派人。分數 null → 0 正規化。出處:question_answer_service.py::checkpoint_task_survey_answer:167、serializer api/task_survey/serializers/question_answer_checkpoint.py

[POST] /project-survey/history/revert — 還原

Request:

{
  "task_survey_uid": "string",
  "history_uid": "string"
}

把該版明細套回當前答案(現存題才套),寫一筆新歷史 type=R,socket 廣播 reload。歷史主檔 / 明細查無擋 TASK_SURVEY_404003 / 404004。出處:app/task_survey/service/question_answer_history_service.py::revert_question_answer_from_history:44

7. 前端檔案地圖(compliance-manager-fe/)

檔案 角色
src/views/survey-v2/SurveyPreview.vue 主檢視(五模式:預覽 / 填寫 / 審核 / 檢視 / 歷史)
src/views/survey-v2/composables/useSurveySocket.js 即時協作 socket(join / lock / update / reload;dirty-aware 合併)
src/views/survey-v2/composables/useSurveyApi.js 問卷結構 / 題型常數
src/config/router/index.js:725-739 route survey-v2-preview/survey/preview/:id)+ survey-v2-fill/survey/fill
src/config/api/api.js endpoint 常數(TASK_SURVEY / PROJECT_SURVEY_ANSWERS* / ..._CHECKPOINT / ..._HISTORY_* / SOCKET_FILL_SURVEY …)
src/config/locales/i18n/*/survey-v2.json 頁面 i18n

8. 後端檔案地圖(本頁核心鏈路)

鏈路 Route App Service 底層
task_survey api/task_survey/routes/task_survey_route.py app/task_survey/service/task_survey_service.py(含 link_task_survey 綁定、通知) domain/task_survey + infra/task_survey
答案 load / patch / checkpoint api/task_survey/routes/question_answer_route.py app/task_survey/service/question_answer_service.py question_answers repo
歷史 / 還原 question_answer_history_route.py..._detail_route.py question_answer_history_service.py..._detail_service.py question_answer_histories(_details) repo
socket 協作 app/task_survey/handler/fill_survey_socketio_handler.py (同 question_answer_service patch) Redis presence + question_answers

DDD 提醒:消費端在主專案自有 DDD 分層(非 jedi-survey);answer / history 走 domain service → repo。checkpoint 以 @transaction 包住多表寫入(答案 + 歷史 + 明細 + status)。

9. DB

9.1 資料表總清單(本頁讀寫的全部表)

讀/寫 說明 欄位詳述
survey.surveys(快照列) 問卷結構(is_snapshot=1,題目來源) manage §9.3
survey.task_surveys 讀 / (status) 任務×問卷×設備執行期追蹤 §9.3
survey.question_answers 讀 / 每題答案(answer / feedback / score jsonb) §9.3
survey.question_answer_histories 答案版本主檔(type U / R) §9.3
survey.question_answer_history_details 每版每題答案明細 GAI-SD-03
survey.survey_discussions 讀 / 問卷討論串 GAI-SD-03

9.2 ER 圖(本頁讀寫範圍)

svf_er 問卷填寫頁讀寫範圍(survey schema · 消費端) snap survey.surveys(快照列) is_snapshot=1(題目結構來源) ts survey.task_surveys 讀 / 寫 task_id·main_project_id status·approval_required snap->ts survey_id disc survey.survey_discussions 讀 / 寫 問卷討論串 snap->disc survey_id qa survey.question_answers 讀 / 寫 answer·feedback·score jsonb ts->qa task_survey_id hist survey.question_answer_histories type U=更新 / R=還原 ts->hist task_survey_id hdet survey.question_answer_history_details 每題答案快照 hist->hdet history_id
問卷填寫讀寫範圍:快照→task_survey→answers→histories/details + discussions

9.3 核心表欄位(取自 DEV DB dump,含 DB comment)

survey.task_surveys — 任務對應問卷(17 欄)

欄位 型別 說明
uid varchar(36) 唯一識別碼
main_project_id int 主專案 ID
task_id / task_uid int / — 稽核任務(GRC job)
process_id / definition_id int|null 流程 / 定義 ID
survey_id int 問卷 ID(指向快照列)
status int 填答狀態(0/1/2/3/9)
approval_required bool|null 是否需審核(決定是否送審路徑)
device_id / department_id int|null 設備 / 部門(survey×device 展開)
job_evidence_id int|null GRC job evidence soft-ref
is_delete bool|null 軟刪除

survey.question_answers — 答案(15 欄)

欄位 型別 說明
uid varchar(36) 唯一識別碼
task_survey_id int 任務問卷 ID
question_id int 題目 ID(→ survey_questions.id)
survey_id / page_id int|null 問卷 / 頁面 ID(冗餘)
score int 得分(null 正規化為 0)
answer / ext_answer jsonb|null 答案 / 補充答案
feedback / ext_feedback jsonb|null 回饋 / 補充回饋

survey.question_answer_histories — 答案版本主檔

uid / task_survey_id / typeU=更新 / R=還原)/ 審計欄位。明細在 question_answer_history_detailshistory_id + 每題 question_id / score / answer / feedback …)。

10. 頁面邏輯與資料對應

模式分流mode = route.query.mode || 'preview'。preview 只 getSurveyById(id) 唯讀;fill / review / view / history 載入 task_survey + answers + task-assignees(判 canEdit/canVerify)+ socket。

題型渲染 ↔︎ 答案answers[questionId] = {answer, ext_answer, feedback, ext_feedback, score}):

題型 輸入元件 答案欄
text / textarea InputText / Textarea answer(字串)
radio / dropdown RadioButton / Dropdown(+ 選項補充) answer(選項)+ ext_answer(補充)
checkbox 多選 answer(陣列)
fileupload 上傳 / 下載 / 預覽 answer(檔案參照)
link 唯讀 <a> / InputText answer(URL)
審核區 Rating(★)+ Textarea score + feedback

持久化:作答變更 → markDirty + debounce → 單題 patch(socket update 同路徑);頂列動作 → checkpoint(帶目標 status,寫歷史)。

錯誤對應TASK_SURVEY_404001(任務問卷不存在)、TASK_SURVEY_404006(題目不存在)、TASK_SURVEY_404003/404004(歷史主檔 / 明細不存在)、TASK_SURVEY_400002(legacy PUT 缺 status/answers)→ toast。

11. 背景行為與外部依賴

類型 內容
通知 checkpoint 改 status 時 notify_task_survey_assignee_status_change 通知填寫者 / 審核人
Socket /socket/fill-survey(room = taskSurveyUid)——presence、編輯鎖、答案廣播、還原 reload;presence 存 Redis fill-survey:{room}:users
Job 無背景 job;匯入答案為同步解析
jedi-* 套件 jedi-auth(JWT);消費端 answer / history domain 在主專案(非 jedi-survey)
系統參數 無 feature flag;socket 僅 `canEdit

12. 邊界情況與已知坑

  1. 狀態轉換合法性只在 FE:checkpoint 直接寫入送入的 status,BE 不驗證 0→92→1 等是否合法——繞過 FE 動作鈕可送任意狀態。改狀態機時需補 BE 守門。
  2. canEdit / canVerify 在 FE 算:可否作答 / 審核由前端依 task-assignees 判定,BE 端點僅 @jwt_required() 不重驗——與作者端一致的守門缺口。
  3. checkpoint 補齊空列:每次 checkpoint 為所有題補空列(score=0 / answer=null),確保還原乾淨;統計 / 匯出時「有列 ≠ 有作答」,需以 answer 是否為 null 判斷實際填答。
  4. 分數 null→0 不對稱:FE 未評分送 null,BE 正規化為 0;「0 分」與「未評分」在 DB 無法區分,報表勿把 0 當已評分。
  5. socket dirty-aware 只在 joined 快照:join 時的 DB 快照合併會保護本地未存答案;但 updated 廣播是權威、直接覆蓋——兩人同時改同題,後到的廣播覆蓋前者,靠編輯鎖(lock/unlock)降低衝突而非強一致。
  6. 填的是快照非 live 範本task_survey.survey_id 指向綁定當下的快照(is_snapshot=1);作者事後改範本 / 改版不影響進行中的填答(見 _overview §4)。
  7. preview 模式無持久化:預覽路徑不載 task_survey、不落資料——別把 preview 當「試填」,作答不會存。
  8. 還原寫新歷史(type=R):還原不是刪除後續版本,而是把舊版套回並記一筆 R 歷史;版本鏈只增不減。

13. 開發與驗證

  • 跑起來:BE python main_socketio.py(port 8000,socket 需經 eventlet,log 在 log/app.log);FE 在 compliance-manager-fe/ 起 Vite dev server。BE 改 service code 後必須重啟(無 hot reload)
  • 測試帳號:dev 環境登入帳號密碼見 .env / 部署文件
  • 導航路徑:預覽 = 設計器 / 管理頁「預覽」鈕(/survey/preview/:id);填寫 = 專案任務抽屜「填寫問卷」新分頁開 /survey/fill?taskSurveyUid=…&mode=fill
  • 前置資料:填寫需先有綁定問卷的稽核任務(規劃頁 job_type=survey 綁問卷 → 產生 task_survey 快照,見 project-task-edit.md §6.2);多人協作需兩個帳號同開同 taskSurveyUid
  • E2Ecompliance-manager-test/ repo(Cucumber + Playwright);以 問卷填寫 / survey fill 搜尋 feature 檔
  • 相關文件:範本設計見 問卷設計器、任務綁定與快照見 project-task-edit.md §6.2 / §12、資料模型見 功能群總覽;DB / API 全量見 GAI-SD-02/03