本卡屬 FR-144(母卡見下方連結),第 1 棒:後端清單查詢與選單登記。會改碼。
做一支「列出跟我有關的每一份稽核計畫」的查詢,和一支「我有幾份待填寫」的計數,讓前端清單頁有資料可顯示;同時在左側選單登記一個新項目「稽核計畫」並綁好權限。做完用 Swagger 打得到正確的列就算完成,前端頁面是下一棒。
新檔
infra/readmodel/audit/audit_plan_list_query.py 讀模型(raw SQL)
app/readmodel/service/audit_plan_list_service.py app service(@transaction)
api/project/routes/audit_plan_list_route.py POST /audit-plans/my/list、GET /audit-plans/my/count
api/project/serializers/audit_plan_list.py request/response schema
scripts/sql/2026-10-xx-fr144-audit-plan-list-route.sql ui_routes seed(phase=seed)
改檔
di_containers/flow_control/flow_control_containers.py 加 Factory(照 :692 auditor_dashboard_service 那段)
api/project/__init__.py 或既有 route 註冊處 add_resource 兩條
照抄形狀(唯讀參考,不改)
infra/readmodel/audit/auditor_dashboard_query.py @property session、raw SQL、.mappings() 不外流、回 list[dict]
app/readmodel/service/auditor_dashboard_service.py
scripts/sql/2026-10-15-fr139-version-history-routes.sql seed 四段(INSERT/綁能力點/DO 驗證/schema_migrations)
compliance.audit_round_passes(round_id, pass_no, audit_nature, nature_seq, status, assessment_plan_id, updated_at)JOIN compliance.project_audit_rounds(round_no, current_pass_id, workflow_execution_uid, project_id)JOIN compliance.projects(deleted_at IS NULL)JOIN 登入者的 project_participants(role IN ('auditor','manager','reviewer'))。狀態白名單 audit_planning, auditing, remediation, pending_reverify, closed(D2)。oscal.ap_reviewed_controls.control_objective_selections 形狀是 [{"include-objectives":[{"objective-id":"<part_id>"},…]}]——外層陣列長度恆為 1,分母要數內層:SELECT count(*) FROM jsonb_array_elements(control_objective_selections) s, jsonb_array_elements(s->'include-objectives') o。DEV 實查三份 AP(1569/1570/1571)都是 7。oscal.ap_assessment_activities 一條 AO 一筆,steps 是陣列、每個 step 的 props 有 {"name":"method","value":"INTERVIEW"};算「steps 內任一 props name=method 且 value 非空」的 activity 數。oscal.ap_tasks.timing->'within-date-range'->>'start' / 'end'(日期時間字串,一份 AP 一筆 task)。compliance.job_executions.due_at;填寫站 job 的 template_job_id 是 UserTask_ap_authoring/UserTask_internal_ap_authoring/UserTask_external_ap_authoring 之一;用 job_executions.workflow_execution_id → compliance.workflow_executions.id,再 workflow_executions.uid = project_audit_rounds.workflow_execution_uid 對到輪次,取最新一筆。DEV 樣本 due_at 多為 NULL,要容忍。oscal.parties type=person、props 含 {"name":"responsible-role","value":"assessor"};用 WHERE metadata_id = ANY(:ids) 第二句批次查,Python 組回每列(保留順序)。空陣列=未指派。project_participants role='manager' 第三句批次查,nickname 走 common/util/audit_nickname.py 既有 enrich。audit_nature='external',「內稽」「內部」→ internal(app service 層翻)。CASE status WHEN 'audit_planning' THEN 0 … END, plan_due NULLS LAST, pass_updated_at DESC;可排 project/pass(round_no, nature, nature_seq)/status/progress/due/audit/manager/updated。排序與分頁在 SQL 做。GROUP BY status,五個狀態都回(沒有的補 0)。ap_uid null 時進度三欄與日期都 null。can_edit = status=='audit_planning' and is_current and ('auditor' in my_roles or 'manager' in my_roles);needs_assign = (ap_uid is None or not auditors) and 'manager' in my_roles。AuditPlanListService.list_my_audit_plans(user_id, status, keyword, page, size, sort) 回 {items, total, status_counts};count_my_pending(user_id) 只數 audit_planning 且 my_roles 含 auditor 或 manager。兩支都 @transaction。