搜尋開發指南

試試「多租戶」、「啟動」或「outbox」。
搜尋僅使用本站文件,不會傳送至外部服務。

文件目錄

NEXUSPRO / HANDBOOK

假勤管理

讀懂 leave type、年度 balance、canonical leave record,以及表單送審與考勤 effect 的資料邊界。

內容核對 2026-09-28·圖文指南

本章目標與前置條件

讀完後,你應能分辨假別目錄、假別政策、年度餘額、已核准紀錄與待審申請,知道本人查詢與 HR 查詢的授權差異,也知道哪些假別規則目前只保存、尚未執行。前置條件是理解 employee scope、年度快照、Asia/Taipei 日期區間與表單 workflow。

功能現況

區塊後端已存在的能力前端現況
假別治理/v1/attendance/leave-types list/get/create/update 與 /v1/attendance/leave-type-form-options;create 需 Idempotency-Key,會建立 local_ 開頭、以天計的本地假別;update 需 expected_version/workspace/leave-policy 經 features/leave-types 讀寫政策與 form options
員工目錄/v1/leave/types 只列 item 節點,名稱與啟停已套用本地政策,回應不含 id目前沒有前端呼叫;表單的假別選項走 GET /v1/forms/data-sources/leave_types,value 是假別 UUID
餘額/v1/leave/balances 必填 year、選填 employee_id(需 data scope 涵蓋),回 decimal-preserving 年度快照;員工不是 active 或 probation 時回 409 employee_ineligible/myleave 的 useLeaveBalances 先確認 identity 與 attendance.leave.read 權限點才發請求,reserved 與 remaining 分開顯示
本人紀錄/v1/leave/records 必填 from/to,只查 authenticated employee;status 只接受 approved/cancelled/corrected/myleave 的 useMyLeaveRecords 顯示日期/狀態;「全部狀態」會拆成三次查詢
HR 紀錄/v1/hr/employees/:employeeId/leave-records 再檢查 HR 與 leave read scope,status 可篩 pendingemployeeLeaveApi、EmployeeLeaveRecords 接 detail drawer
表單 effectcapability attendance.leave、attendance.leave_cancellation 經 outbox 由 worker 的 attendance effect 套用/myleave 以 leave-request 模板開 /forms?template=…;features/forms 顯示 workflow,effectStatus 只在 schema 解析、畫面尚未呈現
請假引擎000067 只有空帳務表;OpenAPI 另有 4 條 contract-first 路徑未註冊(見下文)無

核心概念

Balance 是年度快照,不是 UI 加總

LeaveBalance 以 decimal 字串保存 granted、used、reserved、remaining(wire 同名),domain 以 canonical decimal 驗證,不能使用浮點數自行加總。remaining 恆等於 granted − used(DB CHECK),不扣 reserved;待審期間的可用額度要以 remaining − reserved 表示。跨年請假整筆記在開始時間(Asia/Taipei)所屬的年度。

目前 live 路徑沒有餘額充足檢查:該年度沒有 balance 列時,effect 會先建一筆 granted 為 0 的本地列再扣,核准後 used 可以超過 granted,remaining 變成負數(tests/integration/postgres/attendance_effects_test.go 就斷言 -6.125001)。

表單回應的 quotaState 是表單模板的「送單次數」配額(none/reserved/consumed/released,額滿回 409 quota_exhausted),不是假別餘額。假別的預扣與扣抵看 /v1/leave/balances;核准後 effect 是否落地看表單的 effectStatus(pending 之後變 applied 或 failed)。

Record 是可追溯事實

LeaveRecord 的狀態詞彙是 pending、approved、cancelled、corrected,但目前的寫入路徑只產生兩種:核准 effect 寫 approved(source=local,form_instance_id 指回表單)、eHRMS 匯入寫 approved(source=ehrms),銷假 effect 再把本地紀錄改成 cancelled。本人 endpoint 預設 approved 並拒絕 pending。日期查詢以 Asia/Taipei 的 inclusive local date 轉成半開區間 [from, to 的隔日),以區間重疊比對,最多 366 天。

假別名稱是查詢時 join 目前的本地政策名或目錄名(COALESCE(p.name, t.name)):停用假別仍讀得到名稱,但改名會同步改變歷史畫面,紀錄本身不保存名稱快照。

假別政策與表單是兩個 owner

/v1/attendance/leave-types 回傳 canonical 假別與本地政策合成的 read model:code、unit、requires_balance、source 來自假別目錄;政策設定包含 name、chip、color、status、validity、quota_mode(unlimited/fixed/seniority_tier/form_field)、fixed_days、cycle_start、pay_mode、applicability(性別)、minimum_tenure_months、prerequisite_leave_type_id、linked_form_template_id/linked_form_version、form_field_id/form_field_unit、hours_per_day、tiers/increment;讀回另附 effective_status、linked_form_state、prerequisite_state 與 enforcement。

週期規則(000069 起):seniority_tier 必須有 cycle_start;fixed 只有在 validity 不是 unlimited 時才必填;unlimited 可選填;form_field 禁止週期,且必須有 form_field_id、form_field_unit=hours、hours_per_day 與 linked form。hours_per_day 同時是以小時請天制假別時的換算係數。

form capability 只宣告哪些欄位對應能力欄位:attendance.leave 必須綁定 leave_type、start_at、end_at、hours、reason,請假數量取 hours、單位固定為小時。表單負責輸入與流程,attendance service 負責 canonical effect,不能在前端直接扣 balance。

送審准入、預扣與銷假

  • 送出前,表單服務先以獨立 attendance 交易寫 leave admission(000059 的 attendance_leave_admissions),凍結政策版本與 hours_per_day:假別或政策停用回 409 leave_type_inactive(40300),以小時請天制假別但未設 hours_per_day 回 409 leave_unit_conversion_unconfigured(40303)。表單交易失敗會補償刪除 admission;commit 結果不明時保留。
  • 表單交易寫 outbox:送出發 form.leave.reservation_requested,核准發 form.leave.approved,拒絕/退回/撤回/管理員取消發 form.leave.reservation_released。worker 的 attendance effect 據此做 reserve(reserved 增加)、consume(轉成 used,並寫 approved leave record、approved interval 與每日投影)與 release。送出 API 回 200 的當下,reserved 可能還沒更新。
  • 銷假(000066):銷假單以 attendance.leave_cancellation 綁定從 my_leave_records 選出的本人紀錄;核准後 effect 只處理 source=local 的 approved 紀錄,改為 cancelled、取消 approved interval、扣回 used 並寫一筆 refund 分錄。

eHRMS 來源

  • HR 的 eHRMS 同步寫假別目錄與年度餘額(source=ehrms);remaining 一律重算為 granted − used,不採用上游的 remaining。worker 排程只在初始化與每日維護輪次同步這兩段。
  • 考勤同步把 eHRMS 請假明細寫成 source=ehrms、unit=minutes、status=approved 的 canonical record(依員工、假別與起訖產生穩定 ID),不改本地表單或餘額;跨日假先依歷史出勤班表拆成每日片段再寫入。

請假引擎尚未上線

  • 000067 只建立 8 張 opt-in 的空帳務表(leave_engines、leave_cycles、leave_grant_lots、leave_ledger 等),db/drafts/leave_engine_accounting.sql.draft 保留未完成設計;LeaveAccountingService 等引擎服務沒有被 API 或 worker 組裝。
  • api/openapi.yaml 的 /v1/attendance/leave-types/{leaveTypeId}/engine、/v1/forms/{formInstanceId}/leave/preview、/v1/leave/balance-cycles、/v1/leave/balance-ledger 標為 L0 contract-first; route not yet available,不在 internal/route/testdata/registry.golden。
  • 錯誤碼 40304–40311(insufficient_balance、leave_eligibility_denied、leave_overlap 等)已登錄在 api/error-codes.md,但 live 程式碼不會回傳。

請求鏈與範圍

架構流程圖 · Mermaid

正在繪製架構圖…

查看圖表原始碼
flowchart LR
  Submit["表單送出(attendance.leave)"] --> Admission["leave admission 交易"]
  Admission --> Tx["表單交易 + outbox"]
  Tx -- "reservation_requested" --> Effect["worker:attendance effect"]
  Tx --> Decision["簽核決策"]
  Decision -- "approved/reservation_released" --> Effect
  Effect --> Balance[(年度 balance)]
  Effect --> Record[(canonical leave record)]
  Sync["eHRMS 來源同步"] --> Balance
  Sync --> Record
  Balance --> Bal["GET /v1/leave/balances"]
  Record --> Self["GET /v1/leave/records"]
  Record --> HR["GET /v1/hr/employees/:employeeId/leave-records"]

本人 balance 可在 data scope 涵蓋時指定 employee_id,否則回 403 data_scope_denied;本人 records 沒有 employee_id 參數,帶入會得到 400。HR detail 查詢先由 hr.employee.read 的 target scope 判斷並以 HR 範圍重新查員工,接著 handler 以同一 target 向 PDP 要 attendance.leave.read、再比對一次 data scope(PDP 出錯一律拒絕),避免把 HR profile 權限當成完整假勤權限。路由權限點以 internal/route/testdata/registry.golden 為準。

實作步驟與示例

  1. 先查 api/openapi.yaml 的 Leave schemas、日期與狀態 enum(標 L0 contract-first 的請假引擎路徑尚未註冊),權限點看 internal/route/testdata/registry.golden,再讀 internal/api/v1/attendance_leave.go、internal/api/v1/attendance_leave_records.go 與 internal/api/v1/hr_leave_records.go。
  2. 若改 balance 或 record,先確認是 snapshot、append-only entry 還是 canonical record;現行扣抵只在 attendance effect(internal/service/attendance/effects_leave.go),不要新增第二套扣款計算,也不要把尚未接線的引擎帳務當成現行邏輯。
  3. 若改表單 capability,更新 form schema、種子 definition 的 binding(internal/domain/form/seed_catalog.go)、後端 validation 與 workflow effect contract,再做 revision/quota 負向測試。
  4. 若改假別政策欄位,同步 domain 驗證、DB CHECK、OpenAPI、features/leave-types adapter 與 /workspace/leave-policy 編輯器。部署定稿設定依 deploy/APPROVED-CONFIG.md 使用 tools/approved_config.py:先唯讀產生差異計畫,再帶 --apply 與核對過的 plan hash 套用,最後跑 --verify-only。
  5. 前端用 features/leave 的 zod schema 解析資料;查詢 key 要帶 identity,切換 employee 或 year 時重新讀取,不能保留上一個人的結果。
http
GET /v1/leave/balances?year=2026&page=1&page_size=20&sort=leave_type_code%20asc
GET /v1/leave/records?from=2026-09-01&to=2026-09-30&status=approved&page=1&page_size=20

上例只展示合法的 endpoint 與必要 query;不包含 token 或真實 employee id。預期驗證應包括未綁定 employee、scope 外 employee、from/to 缺失、超過 366 天、未知 status、本人端點帶 status=pending,以及停用假別仍可讀取名稱。

驗證矩陣

驗證問題程式碼/測試證據仍需另外做的驗收
balance scope 與 decimalinternal/service/attendance/leave_test.go、features/leave/api.test.tsx本地 DB 的年度快照與權限範圍
record 日期/狀態internal/api/v1/attendance_leave_records_test.go、internal/api/v1/hr_leave_records_test.go、internal/service/attendance/leave_records_test.goAsia/Taipei 邊界與實際歷史資料
leave policy contractinternal/api/v1/leave_type_policy_test.go、internal/domain/attendance/leave_policy_cycle_test.go、features/leave-types/adapter.test.tspolicy revision、disabled 與 linked form
表單 effect 與餘額internal/service/attendance/effects_test.go、tests/integration/postgres/attendance_effects_test.go、tests/integration/postgres/attendance_cancellation_test.go、tests/unit/forminstance/events_test.go拒絕/退回/撤回的 release、其他假別與正式環境
HR view 的雙重授權internal/api/v1/hr_leave_records_test.go、features/hr-employees/employeeLeaveApi.contract.test.tsx不同資料範圍角色的 API 負向案例

docs/reports/2026-09-22-attendance-live-flow-verification.md 記錄了一次本機真實 API、資料庫、簽核與 worker 的請假實測:送出後 reserved 由 0 變 1,核准且 effect applied 後 used 由 13 變 14、reserved 回到 0。該報告沒有驗拒絕、退回、撤回與全部假別,也不代表正式環境或瀏覽器自動刷新已通過。

常見錯誤

  • 用浮點數呈現或計算餘額,導致 0.1 類值與 server decimal 不一致。
  • 把 remaining 當成待審期間的可用額度,忘了再扣 reserved;或把表單的 quotaState(送單次數)當成假別餘額。
  • 以為假別政策的額度、資格、前置假已經生效;目前只有啟停會擋送審。
  • 把 pending form 當 approved leave record,讓未核准申請提前進入出勤投影。
  • 表單假別欄位送 code;attendance.leave 只接受 lookup 回傳的假別 UUID,送 code 會 422。
  • 只做 route permission,沒有在 service/repository 檢查 target employee 與 tenant。
  • 使用瀏覽器 UTC 日期截斷 from/to,在 Asia/Taipei 午夜附近查錯一天。
  • 將 fixture 的 quota 或表單 effect 當成正式資料庫結果;本機實測只涵蓋送出與核准路徑。

相關文件

內容來源與核實範圍

以下路徑相對於所列業務倉庫;核實層級為「已讀原始碼」,不是本輪業務測試或部署驗收。

  • nexus-pro-be-plus/internal/api/v1/attendance_leave.go
  • nexus-pro-be-plus/internal/api/v1/attendance_leave_records.go
  • nexus-pro-be-plus/internal/api/v1/hr_leave_records.go
  • nexus-pro-be-plus/internal/api/v1/leave_type_policy.go
  • nexus-pro-be-plus/internal/domain/attendance/leave.go
  • nexus-pro-be-plus/internal/domain/attendance/leave_policy.go
  • nexus-pro-be-plus/internal/domain/attendance/leave_policy_settings.go
  • nexus-pro-be-plus/internal/service/attendance/leave.go
  • nexus-pro-be-plus/internal/service/attendance/leave_records.go
  • nexus-pro-be-plus/internal/service/attendance/effects_leave.go
  • nexus-pro-be-plus/internal/service/attendance/effects_cancellation.go
  • nexus-pro-be-plus/internal/service/attendance/ehrms_leave_sync.go
  • nexus-pro-be-plus/internal/service/attendance/sync_worker.go
  • nexus-pro-be-plus/internal/repository/postgres/leave_admission.go
  • nexus-pro-be-plus/internal/repository/postgres/leave_type_policy_mapper.go
  • nexus-pro-be-plus/db/queries/attendance_leave_records.sql
  • nexus-pro-be-plus/db/migrations/000067_leave_engine_accounting.sql
  • nexus-pro-be-plus/db/migrations/000069_leave_policy_optional_cycles.sql
  • nexus-pro-be-plus/docs/reports/2026-09-22-attendance-live-flow-verification.md
  • nexus-pro-be-plus/deploy/APPROVED-CONFIG.md
  • nexus-pro-web-plus/features/leave/api.ts
  • nexus-pro-web-plus/features/leave/useMyLeaveRecords.ts
  • nexus-pro-web-plus/features/leave-types/api.ts
  • nexus-pro-web-plus/features/hr-employees/employeeLeaveApi.ts
  • nexus-pro-web-plus/features/hr-employees/EmployeeLeaveRecords.tsx
  • nexus-pro-web-plus/features/forms/api.ts
  • nexus-pro-web-plus/app/(platform)/myleave/page.tsx
  • nexus-pro-web-plus/app/(platform)/workspace/leave-policy/page.tsx
NexusPro 開發指南以程式碼為準 · 以驗證為據