本章目標與前置條件
讀完後,你應能實作或驗證本人工作紀錄與個人待辦,理解 owner scope、client request id、optimistic version,以及為何月度 summary 不能由前端拼接分頁資料。前置條件是熟悉 ISO month、分頁、JSON closed object 與 409 conflict。
功能現況
| 區塊 | 後端已核實能力 | 前端現況 |
|---|---|---|
| Worklog options | categories、products(key 與 label 都是 service 內的常數詞彙)、duration_step_minutes(30)、daily_target_minutes(420,僅為顯示目標,不限制填寫) | readWorklogOptions 載入建立表單選項與頁首的時數說明 |
| Worklog entries | month list、create、summary、update、delete;create 必填 client_request_id,且帳號需有 employee association | features/worklog/api.ts 與 features/worklog/useWorklog.ts 提供讀寫與快照確認;列表以 fetchAllPages 讀完整月(每頁 100),UI 不分頁 |
| Todo | list、create、update、delete;可用 done=true/false 篩選;create 同樣必填 client_request_id,不需要 employee | tasks 頁面/TodoList 讀取全部 todo(含已完成)並顯示完成狀態 |
| 冪等與版本 | 同 body request key replay 201;不同 body 或原資源已刪除 409/10801;stale base_version 409/10800 | hook 對 uncertain POST 重試同 UUID,讀回後要求明確 acknowledge |
| Summary | 回傳該月每個 Gregorian day(含 0 activity)與完整統計 | 月合計與每日合計都取自 summary,不從列表 rows 自己加總 |
| 範圍 | 只有 /v1/me/**;部門彙總、代填、核准不在本次範圍 | /tasks(page workflow.task)依 worklog.entry.read、worklog.todo.read 分別顯示兩個區塊,沒有 fixture 模式 |
容易混淆的相鄰資料:GET /v1/workspace/overview/todos 屬於 HR 工作區總覽(hr.workspace_todo),與個人待辦無關;features/work-hours 的 WorkHoursPanel(/workspace/attendance)讀的是 /v1/attendance/days 出勤資料,也不是 worklog。
核心概念
「我」由 authentication 決定
API 以 /me 命名只是提醒 scope 來源,不是讓 client 傳 account_id。每條 SQL 都同時限定 tenant_id 與 owner_account_id,RLS 只是資料庫後備。只有 worklog create 會 resolve authenticated account 的 employee association 並寫入 employee_id;沒有 employee association 回 422 10802 worklog_employee_required。list、summary、update、delete 與所有 todo 操作都不查 employee。所有 read/write 都在 tenant context 內進行。
request key 與版本各自解決不同問題
client_request_id(entry 與 todo create 都必填,須為非零 UUID)解決網路重試造成的重複 create:相同 normalized body 回原 201;不同 body,或原本建立的資源已被刪除,回 409 10801 worklog_request_conflict。base_version 解決兩個畫面同時編輯:update 放在 body,delete 放在 query(?base_version=);舊版本回 409 10800 worklog_version_conflict,呼叫端必須重讀後再決定是否覆寫。update 是全量替換,所有可編輯欄位都要送,nullable 欄位要明確送 null;delete 成功回 200 與 {id, deleted: true}。
Summary 是完整月度讀模型
list API 必須傳 month(YYYY-MM,年份 2000–2100),以 work_date DESC, created_at DESC, id DESC 排序並分頁(sort 只接受 -work_date,page_size 最大 100);summary API 則一次回傳該月所有日,包含沒有 activity 的日子,並附 daily_target_minutes。月度總計不可用一頁資料或 UI 當前顯示列推導;前端雖以 fetchAllPages 讀完整月列表,合計仍只取 summary。
Todo 狀態有時間語意
todo create 的 done=false、completed_at=null 由 server 初始化。false → true 會寫入一次完成時間;true → false 會清除;持續 true 不刷新 timestamp。UI 需保留 completed item,不能將完成項誤當不存在。list 依 created_at DESC, id DESC 排序(sort 只接受 -created_at),不帶 done 時回傳全部。
欄位驗證規則
- entry:
nametrim 後 1–200 個 Unicode 字元;work_date為 2000-01-01 至 2100-12-31 之間的實際日期;category限design、meeting、discussion、development、testing、research、other;product可為null,否則限ai_product、nexus、koir、other;duration_minutes為 30–1440 且是 30 的倍數;note可為null,trim 後為空視同null,最多 2000 字。 - todo:
texttrim 後 1–500 字;due_date可為null,否則套用相同的日期範圍。 - body 是 closed object:
product、note、due_date必須出現(可為null),未知欄位與型別錯誤都以 422/10430 field errors 回報。 - 前端 payload schema 使用
.strict(),送出前就擋下未知欄位;response schema 使用.passthrough()。
一條請求鏈
正在繪製架構圖…
查看圖表原始碼
flowchart LR
Tasks["TasksPageContent"] --> Options["GET /v1/me/worklog-options"]
Tasks --> Entries["/v1/me/worklogs"]
Tasks --> Summary["GET /v1/me/worklogs/summary"]
Tasks --> Todos["/v1/me/todos"]
Entries --> Owner["authenticated account(owner_account_id)"]
Summary --> Owner
Todos --> Owner
Entries -.->|"僅 create"| Employee["employee association"]
Owner --> Tx[("tenant transaction + audit")]frontend 的 fetcher/mutator 依 schema 解析結果。SWR key 依資源類型區分:entries 與 summary 為 ['worklog', identity, 'entries', month]、['worklog', identity, 'summary', month],options 與 todos 不帶 month;TasksPageContent 另以 tenant:account 為 key 包一層獨立 cache 的 SWRConfig,換帳號即重建。mutation 成功後刷新受影響月份的 entries 與 summary(改日期跨月時兩個月都刷新)或 todos;不確定的結果(無回應、5xx 或 response schema 不符)先讀回,再由使用者確認 snapshot,避免無聲覆蓋。
實作步驟與示例
- 先讀
api/openapi.yaml的 Worklog schemas、month/done query 與 conflict codes,再讀 handler request decoder;route policy(worklog.entry.*、worklog.todo.*、pageworkflow.task、delete 為 high risk)以internal/route/testdata/registry.golden為準。 - 新增 entry 欄位時同步 domain validation、JSON codec、request closed object、service、OpenAPI、frontend schema 與 contract test。
- create 使用每次操作唯一
client_request_id,retry 必須重送相同 body;update 在 body、delete 在 query 帶最新base_version。 - UI 顯示月摘要時直接使用 summary days/totals,列表只負責列出項目;遇到 409 要保留本地編輯並引導 re-read。
GET /v1/me/worklogs?month=2026-09&page=1&page_size=20&sort=-work_date
GET /v1/me/worklogs/summary?month=2026-09
GET /v1/me/todos?done=false&page=1&page_size=20
DELETE /v1/me/todos/:todoId?base_version=3正式 create/update body 以 OpenAPI WorklogEntryCreateRequest、WorklogEntryUpdateRequest、PersonalTodoCreateRequest、PersonalTodoUpdateRequest 為準;不在文件內填人員、專案或憑證值。驗證要覆蓋沒有 employee、相同 key replay、改 body conflict、stale version 與 completed_at 轉換。
驗證矩陣
| 驗證問題 | 程式碼/測試證據 | 仍需另外做的驗收 |
|---|---|---|
| closed body/field validation | internal/api/v1/worklog_test.go、internal/service/worklog/validation_test.go、features/worklog/api.contract.test.ts | live API 欄位錯誤與 payload size |
| owner scope/month paging | internal/service/worklog/service_test.go、tests/integration/postgres/worklog_repository_test.go、worklog_service_real_test.go、E2E tests/e2e/worklog_routes_test.go(管理員也不能經 /me 覆寫他人資料)、features/worklog/useWorklog.test.tsx | 部署環境的 employee link 與跨租戶負向案例 |
| idempotent create | internal/service/worklog/service_test.go、tests/integration/postgres/worklog_concurrency_test.go、features/worklog/useWorklog.test.tsx | network timeout 後真實重試與 readback |
| optimistic update/delete | internal/service/worklog/service_test.go、tests/integration/postgres/worklog_concurrency_test.go、features/worklog/api.contract.test.ts | 兩瀏覽器/兩 session 競態 |
| summary/todo semantics | internal/service/worklog/calendar_test.go、internal/service/worklog/service_test.go、features/worklog/api.contract.test.ts | 月末、閏年與部署時區 |
常見錯誤
- create retry 產生新
client_request_id,造成一個使用者動作寫入兩筆。 - 收到 409 version conflict 仍直接重送舊 payload,覆蓋掉別人的最新版本。
- 用列表 page 的 rows 相加當月總計,漏掉其他 page 與 0 activity days。
- 把 todo
done=true直接移除,造成 completed item 無法查回或反映完成時間。 - 以為 todo 也需要 employee 綁定,或因為 todo 能用就認定可以建立 worklog;只有 worklog create 需要 employee(422/10802)。
- 把
WorkHoursPanel(出勤工時)或/v1/workspace/overview/todos(HR 工作區)當成 worklog 或個人待辦的資料來源。 - 把前端
useWorklog測試或 contract test 成功當成部署 acceptance;worklog 前端沒有 fixture 模式,本文沒有執行正式 API 或使用真實帳號。