績效考核管理系統 — AI 開發規格文件

本文件只保留開發時必須知道的規範與業務規則。 參考資料已搬至 Docs/dev/,AI 按需讀取即可。


系統概覽

  • 系統名稱:績效考核管理系統(Performance Appraisal Management, P.A.M)
  • 後端:C# / .NET 8 + Web API(7 個 Controller,170+ 支 API)
  • 前端:React 19 + TypeScript + Vite + Tailwind CSS + TanStack Query + Zustand
  • 資料庫:Microsoft SQL Server(MSSQL),EF Core
  • 部署:公司內部伺服器(Intranet)

技術架構

後端

層級說明
ControllersAuthController、HrController、ReviewController、NotificationController、BugReportController、AnnouncementController、TaglineController
ServicesEmailService(SMTP,含附件)、ExcelService(ClosedXML)、AuditLogService(稽核日誌)、InterviewPdfService(面談表 PDF 套表)、OverdueNotificationService(催繳排程)、GradingService(核心公式單一來源)ReviewValidator(提交前驗證)SettlementService(年度結算)ProtectionService(人員管理保護)GradeReviewService(部門確認/退回)ReviewerAccountService(主管帳號 lifecycle)ReviewerTransferService(主管交接:保留記錄換配置 + Shift 遞補)Hrms104Repository(104 HRMS 直連)
HelpersHelpers/HttpContextExtensions.cs.GetRealClientIp() — 取得真實 client IP(優先 X-Forwarded-For / X-Real-IP)
TestsExamSystem.Tests/(xUnit + EF InMemory + Moq,223 筆:GradingService 36 / ReviewValidator 58 / ReviewController 19 / HrController 32 / ParticipantImportService 14 / GradeReviewService 18 / TaglineController 13 / AnnouncementController 13 / NotificationController 16 / 其他 4)
ModelsModels/Models.cs — 所有 Entity 類別
DataData/ExamDbContext.cs — EF Core DbContext
AuthJWT Bearer Token(8 小時過期)+ BCrypt 密碼雜湊

前端

目錄說明
client-app/src/pages/hr/HR 管理頁面(17 頁,含 AnnualDetailPage、AnnouncementsPage、TaglinesPage)
client-app/src/pages/review/主管考核頁面(7 頁,含 TodayPage 今日要事)
client-app/src/components/AnnualDetailModal.tsx年度考核總覽彈窗(年度結算頁點擊員工使用)
client-app/src/pages/auth/登入、修改密碼、忘記密碼
client-app/src/components/ui/共用 UI 元件
client-app/src/lib/api.tsAPI 呼叫封裝(apiCall, apiUpload)
client-app/src/stores/Zustand 狀態管理(authStore)

開發工具

檔案用途
restart-backend.bat關閉舊後端 → 重新編譯啟動
restart-frontend.bat關閉 port 5173 → 重啟 Vite
restart-all.bat一次重啟前後端
gen_import.py從 104 HRMS 產出考核匯入名單 Excel
gen_interview_pdf.py面談表 + 追蹤表 PDF 套表產出
gen_exam_result_pdf.py考核結果通知書 PDF 產出(年中/年終/年度)

使用者角色(僅 4 種,一般員工不使用系統)

角色代碼說明
HR人事管理員,全流程管理
Reviewer1初核主管,填寫 A 分
Reviewer2複核主管,填寫 B 分
Approver初審/複審/三審/終審主管

考核審核流程

初核(A) → 複核(B) → 初審 → 複審 → 三審 → 終審 → HR等第分配 → 年度結算
  • Excel 名單中有填哪幾關審核主管,系統就執行哪幾關
  • 沒填的關卡自動跳過,最多 6 關,最少 2 關
  • 任何一關可退回上一關,退回時必須填寫退回原因

退回流程邏輯(重要)

退回時系統執行以下操作:

  1. CurrentStep--(HR 退回已完成案件時 CurrentStep 不變)
  2. Step >= 退回步驟 的舊 ExamRecord 標記為 IsReturned = true
  3. 新增一筆 IsReturned = true 的退回記錄(含退回原因)
  4. 清除 FinalGradeFinalScoreScoreBeforeAdjustIsLocked
  5. 清除 ForceFlag = "Normal"ForceFlagReason = null

查詢 ExamRecords 的關鍵規則:

  • validRecords:必須過濾 !r.IsReturned && r.Step <= p.CurrentStep
  • reviewer2Record:當 CurrentStep < 2 時必須設為 null
  • latestRecord(取 D 分):必須從 validRecords 中取
  • 前一關分數(prevA / prevRecord):必須加 r.Step < p.CurrentStep
  • 歷史紀錄排序:依 SubmittedAt(非 Step)

計算公式

// 當次平均分數 C
if (初核主管 == 複核主管) → C = A
else → C = (A * 0.5) + (B * 0.5)

// 當次考核成績 D
D = C + 出勤扣分(負數)

// 全年度成績
全年度 = (年中D + 年終D) ÷ 2 + 獎懲加減分
// 若只有年終:全年度 = 年終D + 獎懲加減分

核心公式單一來源(GradingService)

所有等第相關計算必須呼叫 Services/GradingService.cs,禁止各處自行實作重複邏輯。

方法用途
CalcGrade(score)分數 → 等第(特優/優等/甲等/乙等/丙等)
GradeRank(grade)等第 → 排序分數(比較大小用)
ComputeScoreC(A, B, isMerged)當次平均分數 C(合併主管→C=A;否則(A+B)/2)
ComputeScoreD(A, B, isMerged, attendance)當次成績 D = C + 出勤扣分
ComputeAttendanceDeduction(hours)事假時數 → 扣分(每小時 -0.125)
ComputeByRate(n) / QuotaSuggestion人數 → 建議配額(餘額法,sum 永遠等於 n)
GetSuggested(quota, grade)從 QuotaSuggestion 取對應等第建議數
CalcCeiling(...)等第天花板(懲處/假勤/年資 → 天花板等第 + 原因)

HrController / ReviewController 的 CalculateGrade 與各處 fallback 皆已改為呼叫 GradingService。 新功能若需要等第判定或配額計算,一律在 GradingService 補方法,不在 Controller 重寫。

分數精度(重要)

所有分數欄位 DB schema 為 decimal(6,3)(原為 decimal(5,2),於 2026-04-17 遷移加寬)。允許 3 位小數精度,支援 0.125 公式(事假每小時扣 0.125 分)產生的分數如 -0.375 / 69.625 / 75.125。

  • 涉及欄位:ExamRecord.Score[A-I]ExamRecord.AttendanceDeductionExamParticipant.FinalScoreExamParticipant.ScoreBeforeAdjustAnnualResult.*ScoreGradeAdjustment.OriginalScore/AdjustedScore
  • 遷移:Migrations/20260417113907_WidenScoreDecimalsTo6_3
  • 歷史資料重算:Docs/dev/recalc_scores.sql(修正舊 decimal(5,2) 累積的精度損失;亦可透過 POST /api/hr/recalc-scores 重算)
  • 前端顯示統一用 fmtScore(3 位小數,尾數 0 時降為 2 位)

列表分數顯示(即時重算,避免過時快照)

/review/my-listlatestScoreMap:Step 1/2 的 ScoreD 為提交當時的快照,若事假時數後續異動(104 重同步)會與考核表單的即時計算不一致。Step 1/2 記錄改用 ScoreC + 當前事假 × -0.125 即時重算;Step 3+ 才直接取 E~I(審核強制/繼承分數)。

草稿估算只套用自己draftEstMap 只有在 isMyTurn && 我有草稿 時才覆蓋顯示,避免下游審核主管看到上游複核 WIP 的估算分數。

等第分配自動平衡(TryAutoBalanceCounts)

HR 已核定的等第分配人數(OutstandingCount/ExcellentCount/…)遇到 TotalCount 異動(部門主管 toggle、人員排除/恢復、DesignatedReviewer 異動)時,自動將差額吸收到甲等(最大桶),保持 IsConfirmed = true。僅差額過大致甲等變負數無法平衡才重置。位於 HrController.TryAutoBalanceCounts,套用於 ApplyTotalCount / GenerateGradeDistributionsAsync / RecalculateGradeDistributionAsync / GetGradeDistributionAsync 四處。

出勤扣分:僅事假扣分,每小時 -0.125(病假不扣分,僅影響等第天花板判定)

104 假勤同步假別:事假 S0001-1 + 事假-專案 S0001-3 + 半薪病假 S0002-1 + 無薪病假 S0015-1

考核表單年中假勤顯示

年終考核表單中,所有角色(含初核)都能看到年中的假勤數據(事假/病假時數),因為假勤是參考資訊非機密。但年中考核分數(D分、C分)僅複核以上可見。

獎懲加減分:大功 +9、小功 +3、嘉獎 +1、大過 -9、小過 -3、申誡 -1


等第審核與年度結算流程

完整流程(三階段)

第一階段:考核評分(per 週期)
  初核(A) → 複核(B) → 初審 → 複審 → 三審 → 終審 → 部門全員完成

第二階段:等第審核(per 虛擬部門 × 週期)
  部門全員完成 → 虛擬部門主管調整等第 → 提交 → HR確認 → 考核等第已確認
  (全部虛擬部門都確認 → 週期鎖定 Locked)

第三階段:年度結算(per 虛擬部門)
  考核等第已確認 → 虛擬部門主管調整年度總分 → 提交 → HR確認 → 年度考核已完成
  (全部虛擬部門都確認 → 執行結算 Settle → 確認鎖定 Lock)

關鍵概念

  • DepartmentConfirmation.ConfirmationType"GradeReview"(等第審核)vs "Settlement"(年度結算)
  • DepartmentConfirmation.StatusPendingSubmittedConfirmed(可 Returned 退回)
  • 等第審核和年度結算使用同一 API 端點,透過 ?type=GradeReview / ?type=Settlement 區分
  • 年度結算不需等到週期 Locked,只需該虛擬部門的等第審核已 Confirmed 即可開始
  • 年度結算鎖定:全部虛擬部門 Settlement 都 Confirmed 後才可鎖定專案(LockProjectAsync 驗證)
  • 年度結算解鎖(退回):需輸入 HR 管理員密碼(SystemSettings Sync104:Password),非使用者登入密碼
  • 等第審核解鎖(退回):Confirmed 狀態 HR 點鎖頭按鈕退回時也需 HR 管理員密碼(比照年度結算);週期若已 Locked 會自動解鎖為 Active
  • 年度等第檢查站AnnualNarrativeCheckedAt / AnnualNarrativeCheckedBy(獨立於週期等第檢查站 NarrativeCheckedAt
  • 年度結算確認前須通過兩道檢查站:(1) 年終考核等第檢查站 (2) 年度調分等第檢查站

分數調整記錄(GradeAdjustment)

欄位說明
Scope"MidYear" / "YearEnd" / "Annual" — 區分調分來源
ParticipantId關聯到 ExamParticipant
OriginalScore / AdjustedScore調整前後分數
OriginalGrade / AdjustedGrade調整前後等第

查詢規則

  • 考核表單審核歷程:排除 Scope == "Annual"(年度調分不屬於週期考核)
  • 年度結算頁面:優先顯示 Scope == "Annual" 的調分說明
  • 等第審核頁面:顯示 Scope != "Annual" 的調分說明

ExamParticipant 調分欄位

欄位說明
ScoreBeforeAdjust等第調整前的原始 FinalScore(首次調整時記錄,多次調整不覆蓋)
AdjustedAnnualScoreHR 調整的年度總分
AdjustedAnnualGradeHR 調整的年度等第
AnnualNarrativeCheckedAt年度等第檢查站通過時間(HR 確認年度調分後的特優/乙丙評語)
AnnualNarrativeCheckedBy年度等第檢查站確認人

退回時清除FinalScoreFinalGradeScoreBeforeAdjustIsLockedForceFlag

年度結算 API

POST /api/hr/projects/{projectId}/settle

  1. 以年終已完成參與者為基準(僅限等第審核已確認的虛擬部門)
  2. 逐人查詢年中成績 + 獎懲加減分
  3. HR 調整值優先:AdjustedAnnualScore > 公式計算值
  4. 等第判定:AdjustedAnnualGrade > 依分數自動判定
  5. Upsert 至 AnnualResults,專案狀態設為 Settled

年度考核總覽 API

GET /api/hr/projects/{projectId}/annual-detail/{employeeId} — 整合年中 + 年終 + 獎懲 + 調分歷程

年度等第檢查站 API

  • POST /api/hr/participants/{id}/annual-narrative-check — 通過年度等第檢查
  • DELETE /api/hr/participants/{id}/annual-narrative-check — 撤銷年度等第檢查

年度考核總覽分數顯示

  • 考核已完成:使用 FinalScore
  • 考核進行中:使用 latestScoreD(最新 ExamRecord 的 ScoreD)作為 fallback
  • HR 角色不分狀態都可查看年度考核總覽

列表頁分數顯示規則(重要)

  • 考核已完成(Status == “Completed”):永遠使用 FinalScoreFinalGrade
  • 考核進行中:初核主管看自己的 ScoreA;其他主管看最新提交分數
  • isReviewer1WithScoreA 條件必須排除 Status == "Completed",否則等第調整後列表不會更新

考核表單等第分配(GradeDistPanel)

  • 位置:ScoreSummary 與 AttendanceDisciplinaryPanel 之間
  • 分組邏輯:初核主管 → Reviewer1 == myName;複核主管 → Reviewer2 == myName;HR/審核主管 → VirtualDeptName
  • 顯示該主管負責的全部考核人員的等第分布

出勤匯入頁面(AttendancePage)

  • 假勤 tab:依考核週期同步,統計區間設定於週期設定(LeaveCalcStartDate / LeaveCalcEndDate
  • 獎懲 tab:依年度專案同步(無週期選擇器),統計區間設定於專案(DisciplinaryCalcStartDate / DisciplinaryCalcEndDate
  • 獎懲匯入後顯示:匯入 N 筆、對應成功 N 筆、N 筆未對應
  • 未對應的獎懲紀錄(員工不在考核名單中)以琥珀色警告區塊顯示,含員工姓名與獎懲類型
  • 假勤統計區間:顯示匯入資料的起迄日期
  • 獎懲統計區間:唯讀顯示專案設定值(不在此頁輸入)

獎懲同步架構(重要)

  • 獎懲為年度性質,儲存在專案層級(ExamProject.DisciplinaryCalcStartDate / DisciplinaryCalcEndDate
  • 同步 API:POST /hr/projects/{projectId}/sync-104-disciplinary(非 period 層級)
  • Replace 模式清除同專案所有週期的獎懲記錄(allProjectPeriodIds
  • 獎懲統計區間設定:在考核週期「設定作業時間」對話框中輸入,但儲存至專案(PUT /hr/projects/{id}/disciplinary-dates
  • PeriodCard 顯示專案的獎懲統計區間(與假勤統計並列)

考核表單對應規則

string formType = (department == "業務部", jobLevel) switch
{
    (true, "管理職")  => "BizManager",
    (true, "一般職")  => "BizGeneral",
    (false, "管理職") => "GenManager",
    (false, "一般職") => "GenGeneral",
};
  • 評分選項(固定 10 個):0.5, 1.0, 1.5, 2.0, 2.5, 3.0, 3.5, 4.0, 4.5, 5.0
  • 強制修正分數 / 調整分數:0.5 單位 四捨五入(前端 onBlur 自動 round,例:75.3333 → 75.5)
  • 具體事蹟(NarrativeReason ≥50 字)必填:只看主管自己打的原始分數(初核=A、複核=B、合併=A)落在特優/乙等/丙等時觸發。扣分造成的 D drop 不觸發(事由明確為出勤)。
    • 例 1:A=70,B=70(甲等),D=69.625(乙等)→ 不強制具體事蹟(B 在甲等)
    • 例 2:A=77.5,B=68(乙等),D=72.75(甲等)→ 強制具體事蹟(B 在乙等)
  • 主管評語(Comment)必填:初核甲等(70-89)時必填;複核完全選填
  • 內容驗證執行時機:存草稿時即擋(與天花板相同機制,參見 SaveDraftAsync);單筆送出 / 批次送出再次驗證為 safety net

等第規則

等第分數區間比率目標上限下限
特優90+8%12%5%
優等80-8912%15%8%
甲等70-7975%80%70%
乙等60-693%5%0%
丙等<602%5%0%

等第天花板法則(CalculateGradeCeiling

  • 有申誡以上懲處 → 不得列特優
  • 有記過以上懲處 → 不得列優等
  • 任職未滿 1 年 → 不得列特優
  • 部門未滿 10 人:特優至多 1 人,優等以上不超過 1/4
  • 事假超過 14 天 → 天花板為甲等
  • 病假超過 30 天 → 天花板為甲等

天花板計算以年度為單位:假勤天數與獎懲紀錄合計同專案所有週期(年中+年終),非單一週期。

年資參考日以「評分當下(今天)」為準:避免使用未來的 period.EndDate 讓尚未滿一年者被放行。若週期已結束才取 EndDate(歷史視角)。位於 ReviewController.GetGradeCeilingAsync

天花板檢查範圍:年中/年終皆硬性阻擋(SaveDraftAsync / SubmitReviewAsync / BatchSubmitAsync),不再只限 YearEnd。

前端天花板即時警告

  • 考核進行中:使用即時計算的分數(watchedScores),打分時即時顯示,不需存檔
  • 已完成案件:使用伺服器端 finalScore / finalGrade
  • 位置:ScoreSummary(分數摘要)下方
  • 後端 GetGradeCeilingAsync:取得考核表詳情時即時計算天花板(即使從未存過檔也會回傳)

超額說明歷程(GradeOverrideLog)

超額/不足/主動填寫原因累積寫入 GradeOverrideLog(退回重送不覆蓋)。SourceType 區分 ReviewerBatch / DeptSupervisor / HrReturn

📖 完整規格:OVERRIDE_LOG.md


異常標記(ForceFlag)

欄位 ExamParticipant.ForceFlag,預設 "Normal",非 Normal 時列入 HR 儀表板異常標記區。

Flag 值觸發條件
特優需評語評分 ≥ 90 且主管未填評語
乙丙需評語評分 < 70 且主管未填評語
懲處不得特優有申誡以上懲處,但等第為特優
懲處不得優等有記過以上懲處,但等第為優等
年資不得特優任職未滿 1 年,但等第為特優
事假超量年度事假合計 ≥ 14 天(112 小時),天花板為甲等
病假超量年度病假合計 > 30 天(240 小時),天花板為甲等
等第超標該虛擬部門某等第人數超過上限比率
分數等第不符最終分數與最終等第的分數區間不一致
審核主管調分審核主管覆寫了初核/複核分數

處理:HR 可退回主管修正 / 強制調整等第 / 確認無誤後清除標記


考核結案報表頁(ExamReportPage)

HR 查看各部門考核成績 + 等第統計,三個 Tab:各部門結案報告 / 週期/年度報告書 / 104 回寫 PDF 存檔

Tab 1 含「單位 × 公司別」統計表(取代原等第統計)+ 人員清單;Excel 匯出 2 sheet(統計表 + 人員清單)、5 欄簽核、頁尾製表單位。

📖 完整規格:EXAM_REPORT.md


人員管理保護機制(CheckProtectedAsync)

週期/專案鎖定或虛擬部門已確認時,HR 修改人員管理需 HR 管理員密碼;驗證通過後自動解鎖週期/作廢確認。8 個受保護端點 + 前端兩層閘門(預檢 + fallback)。

📖 完整規格:PROTECTION.md


孤立 ExamRecord 防護

TotalReviewSteps 減少(如移除審核主管)時,可能產生 Step > TotalReviewSteps 的孤立 ExamRecord,導致 FinalScore 錯誤。

防護機制(3 層)

  1. UpdateReviewersAsync:減少關卡時自動刪除孤立 ExamRecord + ExamAnswers,重算 FinalScore
  2. SubmitDepartmentAsync:提交前檢查並自動修復孤立記錄,記錄稽核日誌
  3. 查詢規則validRecords 過濾 r.Step <= p.TotalReviewSteps

FinalScore 重算保護(重要)

孤立記錄清理後重算 FinalScore 時,必須優先使用 GradeAdjustment 調分值

  • 有 GradeAdjustment → 使用最後一筆的 AdjustedScore/AdjustedGrade(保護等第調整)
  • 沒有 GradeAdjustment → 從 ExamRecord 重算 ScoreD(原本行為)

跨 Controller 共用方法

  • ReviewController.GetFinalScore(record, totalSteps, isMerged)internal static,根據 TotalReviewSteps 取最終分數
  • ReviewController.CalculateGrade(score)internal static,分數→等第轉換
  • HrController 有自己的 CalculateGrade(private static),功能相同

強制面談流程

候選人(乙丙等)→ HR確認面談名單 → 自動產PDF → 待通知 → 發信(附PDF)→ 待面談 → 掃描上傳 → 已完成

InterviewPdfService 呼叫 gen_interview_pdf.py 產 PDF;gen_import.py 依 JOB_CODE 決定管理職/一般職 + 審核鏈,排除特定主管與到職未滿 90 天。

📖 完整規格(含 PDF 套表欄位、gen_import 匯出規則):INTERVIEW.md


帳號管理(SystemUser 池)

帳號池架構

  • SystemUser 是全域帳號池,匯入過的主管永遠存在,不會自動刪除
  • PeriodUserMapping 記錄「帳號 ↔ 週期」對應關係,用於追蹤通知狀態(NotifiedAtPasswordSentAt
  • HR/Admin 帳號:以 EmployeeNo 登入
  • 考核主管帳號:以姓名或 EmployeeNo 登入

同步帳號生命週期(SyncAccountsAsync

全域操作 — 影響所有非 HR/Admin 帳號:

  • 在本期考核名單中 → 啟用 + 重設密碼為 @2026
  • 不在本期名單中 → 停用

⚠️ 同一時間只能有一個進行中的週期,否則同步帳號會互相覆蓋

帳號管理頁面

  • 預設「全部帳號」模式:不帶 periodId/projectId,顯示完整帳號池
  • 選擇專案/週期後:只顯示 HR/Admin + 該週期的主管
  • 帳號檢查(check-missing):偵測該週期需要但缺少/已停用的帳號

專案刪除

  • **進行中(Active)**專案不可刪除(前端隱藏按鈕)
  • **已鎖定(Locked)/ 已結算(Settled)**可刪除,需輸入 HR 管理員密碼
  • 刪除會連帶清除:ExamAnswers → ExamRecords → GradeAdjustments → ExamAttachments → DepartmentConfirmations → InterviewRecords → GradeDistributions → GradeOverrideLogs → DisciplinaryRecords → PeriodUserMappings → NotificationLogs → ExamParticipants → AnnualResults → ExamPeriods → ExamProject
  • 不刪除:SystemUsers(帳號)、Employees(員工)、AuditLogs(稽核)

批次送出驗證(BatchSubmitAsync)

初核/複核主管批次送出草稿時,系統按單位(Unit) 檢查「整包送」規則(對應紙本流程:整包考核表裝信封袋才能往後送):

單位 key 解析順序

Employee.UnitEmployee.DepartmentVirtualDeptName(fallback)

規則

  • 初核:單位內目前在初核(step 1)且 R1=我的人 → 全部有草稿才可送
  • 複核:單位內 R2=我 且未通過複核(currentStep ≤ R2Step)的人 → 全員已到複核 + 全員有草稿 才可送
    • 若有人仍在初核(currentStep < R2Step)→ 擋下,等初核主管把該人送上來
    • 若有人因退回回到初核 → 擋下整單位,等他回到複核 + 重新存草稿
  • 審核主管(初審/複審/三審/終審):可沿用前關分數,不需草稿;全選/批次時審核主管自動視為「可送」

為什麼按單位不按虛擬部門

  • 單位(如「製作工程部-製播-攝影組」)= 有 1 位單位主管負責該單位一般職的初核
  • 單位內考核「一起走」符合實務流程(單位主管也是批次送)
  • 不同單位獨立推進,避免初核主管彼此等待

HR 紙本流程類比

  • 整包考核表要全做完、裝信封袋 → 才能送下一關
  • 少一份或有一份沒填完 → 不能封袋
  • 退回 1 人 → 抽出那份、該份處理完再回包 → 一起再封袋送

All-or-nothing(重要)

批次送出為全有全無交易,只要任一筆驗證失敗,整批不寫入 DB。避免過去「部分送出部分留草稿」令使用者困惑的情況。

一次批次只能同一關卡(前端限制)

MyListPage 批次送出前會檢查 draftSelectedmyRole 去重,size > 1 直接擋下並顯示 toast,要求使用者分別選取各關卡後送出。 原因:同時送出初核 + 複核時,分母/分子跨關卡混算會造成超額判定失真;分開送出讓每批的配額檢查對齊到單一關卡。


考核記錄跨專案轉移(MigrateRecordsAsync)

一次性 API POST /api/hr/migrate-records,用於將舊專案的考核記錄轉移到新專案。

轉移規則

  • EmployeeId 配對來源/目標參與者
  • 只轉移 Step 1(初核)和 Step 2(複核)的記錄(含草稿)
  • Step 3+(初審/複審)的記錄不轉移(審核鏈可能不同)
  • R1/R2 必須一致才轉移,不一致則跳過
  • 目標已有非退回記錄 → 跳過(防重複轉移)
  • 轉移後自動更新 CurrentStepStatus
  • 支援 dryRun: true 預覽模式

主管交接(ReviewerTransferService)

考核進行中主管離職,保留 ExamRecord.ReviewerName 歷史、只換 ExamParticipant.Reviewer* 配置欄位;NewReviewerName 為空時 Shift 往前遞補(TotalReviewSteps -= 1)。人員管理頁批次調整主管 Dialog 切換「保留記錄 / 清空重評」兩模式。

API:POST /api/hr/participants/batch-transfer-reviewers (支援 DryRun 預覽)。

📖 完整規格:REVIEWER_TRANSFER.md


命名規範

資料庫

  • 資料表 / 欄位:PascalCase
  • 主鍵:一律 Id(INT IDENTITY)
  • 外鍵:{資料表名}Id
  • 布林:Is 開頭,時間:At 結尾
  • 分數:DECIMAL(5,2)

API

  • Base URL:/api,kebab-case
  • 統一回應:{ success, data, message }

C# 程式碼

  • 命名空間:PerformanceAppraisal.{層級}
  • 類別/方法:PascalCase,非同步加 Async
  • 變數:camelCase,常數:UPPER_SNAKE_CASE
  • 所有 DB 操作用 EF Core,禁止拼接 SQL
  • 每支 API 須加 [Authorize]
  • Controller 不得 try-catch 吞掉例外
// ❌ 禁止
var sql = "SELECT * FROM Employees WHERE Name = '" + name + "'";
try { ... } catch { return Ok(); }
 
// ✅ 正確
var employees = await _db.Employees.Where(e => e.Name == name).ToListAsync();

Git 分支

main ← 正式環境    feature/xxx ← 功能開發
develop ← 整合測試  hotfix/xxx ← 緊急修復

公告系統(Announcements)

登入頁「最新消息」與系統公告維護(HR 可增修)。含自動公告:GradeReviewService.ConfirmDepartmentAsync 確認後寫入「【部門】X 年 Y 考核已全數完成!」。

📖 完整規格(資料表/API/session chip/HR 後台):ANNOUNCEMENT.md


標語系統(Taglines)

登入頁 Hero + 今日要事 tagline 輪播的內容源(HR 在 /hr/taglines 增刪改)。Categorylogin(三段)/ today(兩段);預種 80 筆。

📖 完整規格:TAGLINE.md


今日要事(TodayPage · /review/today

考核主管 / 虛擬部門主管登入後的任務導向首頁(取代 /review/dashboard)。頁面結構:頁首(問候 + hero tagline + 最新消息卡)+ 單位進度面板 + 三分組(需要先看 / 今日可做 / 已完成)。

📖 完整規格(頁面結構、預設展開規則、分組判斷、UnitProgressPanel):TODAY_PAGE.md


UI 色彩設計規範

主色:年代紅(Era Red)

--color-primary-500: #C00018(年代集團品牌色,定義於 index.css @theme)。原 Google 藍已替換為紅色色階 50~900。

設計原則(避免紅色過載)

  1. 紅色僅作為錨點與強調色,不做大面積背景/漸層(頁面頭部 banner、儀表板卡片等要避免 from-primary-* 系列深色漸層)
  2. 中性灰打底:頁首、列表、表格底色用 bg-gray-50 / bg-white + border-gray-200
  3. 資料視覺化避開 primary
    • 進度條:blue-300blue-400blue-600(關卡遞進淡→深)
    • 分析/圖表 icon 圈:bg-indigo-50 + text-indigo-600(等第比率監控、截止日倒數、等第分配進度)
    • 完成色保留 emerald-500
  4. 連結/姓名可點:預設 text-gray-900,hover text-primary-600(避免整欄紅字)
  5. 次要操作text-gray-500 hover text-gray-700(展開/收合等)
  6. 保留語意紅:違規/錯誤 text-red-700 bg-red-50 border red-200(與 primary 紅可區分)

這些原則已套用於 ReviewDashboardPage.tsxDashboardPage.tsx 的 banner 與 icon。


尚未實作

  • 🟡 104 回寫 PDF 格式待微調 — 年中 / 年終 / 年度結算三種 PDF 版型細節(印章位置 / 簽核歷程 / 等第區塊 / 欄位對齊)待 HR 提供修改清單
  • 🟡 單元測試:xUnit + EF InMemory + Moq(ExamSystem.Tests223 筆全綠
    • GradingService 36 / ReviewValidator 58 / ReviewController 19 / HrController 32 / ParticipantImportService 14 / GradeReviewService 18 / TaglineController 13 / AnnouncementController 13 / NotificationController 16 / 其他 4
    • 尚未覆蓋:EmailService / ExcelService
  • 🟡 Service 層重構
    • ✅ 已抽出:GradingService(等第/天花板公式)、ReviewValidator(提交驗證)、SettlementService(年度結算)、ProtectionService(人員管理保護 CheckProtectedAsync + 密碼驗證)、Hrms104Repository(104 HRMS 直連 SQL)、ReviewerAccountService(主管帳號 lifecycle)、GradeReviewService(部門確認/退回 Confirm/Return Department + 主管端 SubmitDepartment)、ParticipantImportService(Sync104Employees + ImportEmployees 共用 upsert)
    • HrController 行數:9,979 → 9,520 → 11,225(後續迭代成長)→ 2026/04/25 拆 7 個 partial 檔,主檔 7,433 行
    • Partial 檔清單:HrController.{Settlement,GradeDistribution,GradeAdjustment,NarrativeCheck,Interviews,Sync,Reports}.cs(共 3,792 行移出主檔;主類別宣告加 partial 修飾元,行為零變化,163 tests 全綠)
  • 共用 PasswordModal + sudo-mode:本週期結束後與單一帳號權限設計一併處理(使用者決議延後)

參考文件(Docs/dev/)

核心參考

文件內容何時讀取
API_REFERENCE.md160+ API 端點完整清單新增/修改 API 時
DATABASE.md29 DbSet 資料表清單查詢資料表結構時
PAGES.md23 個前端頁面路由對照修改路由/新增頁面時
DEPLOY.mdIIS 部署指南與設定部署時
NOTIFICATION.md催繳通知系統、範本變數、附件寄送修改通知功能時
EXCEL_IMPORT.mdExcel 匯入規格 + 104 整合 + gen_import.py 規則修改匯入功能時
CHANGELOG.md已完成功能清單了解歷史變更時

功能模組(從 CLAUDE.md 拆出)

文件內容何時讀取
EXAM_REPORT.md考核結案報表 3 Tab + 單位×公司統計 + Excel 匯出修改結案報表時
REVIEWER_TRANSFER.md主管交接(保留記錄換配置 + Shift 遞補)修改交接邏輯時
TODAY_PAGE.md今日要事頁面結構、分組、UnitProgressPanel修改 /review/today 時
PROTECTION.md人員管理保護機制、8 端點、密碼解鎖動人員管理 API 時
OVERRIDE_LOG.md超額說明歷程 DTO + 寫入規則 + 權限修改超額歷程時
INTERVIEW.md強制面談流程 + PDF 套表 + gen_import 規則修改面談/匯入時
ANNOUNCEMENT.md公告系統(含自動公告)修改公告時
TAGLINE.md標語系統(login/today/return 三類)修改 tagline / 退回原因時
COORDINATOR.md部門行政角色(Coordinator)Schema + API + TodayPage 追蹤區塊修改行政追蹤時
104_WRITEBACK_FLOW.md104 回寫 9 階段 lifecycle(考核完成 → INSERT HRMS_EXAM + COPY PDF → 週期結案)修改 104 回寫 / 週期結案時
EXAM_FORM_PDF.md考核表單 PDF 三模式(blank/half/filled)版面、分數可見性、附件合併、職別題目、API 規格修改 gen_exam_form_pdf.py 或 ExamFormPdfService 時

最後更新:2026/04/25(A3 Service 重構 + Controller tests + ExcelService 清理 + ExcludedCandidates 候選池 + UI 統計 chip)· 年代集團資訊科技部

近期重大變更

  • 2026/04/25 晚間A3 Service 重構 + B 測試補強 + ExcelService 清理 + ExcludedCandidates 候選池
    • A3:ReviewController.SubmitDepartmentAsync 委派 GradeReviewService(~370 行 → ~22 行薄殼;天花板/配額/Confirmation/稽核全保留行為)
    • B:3 個 Controller 測試集 +60 筆(GradeReviewService 18 / TaglineController 13 / AnnouncementController 13 / NotificationController 16);測試 163 → 223 全綠
    • ExcelService 清理 -637 行:移除假勤/獎懲/虛擬部門 3 條 Excel 匯入路徑(全改用 104 同步 / 手動 CRUD),對外 API 縮減為 4 支(ParseImportFileAsync / ExportReportAsync / ExportRatingRatioReport / ExportDeptUnitRatingRatioReport)
    • ExcludedCandidates 候選池(migration AddExcludedCandidates):解決「gen_import.py 用 today() 篩 90 天,但實際考核 1-2 個月後才開始」問題
      • gen_import.py 必填 --period-start YYYY-MM-DD + 寫第 2 sheet「未列入名單」(HireDate90 / Resigned 兩類)
      • 後端 ExcelService.ParseExcludedCandidates + ParticipantImportService.ImportExcludedCandidatesAsync + 3 支 API(GET/POST restore/DELETE)
      • 前端人員管理拆 2 Tab:「在考核名單」/「未列入名單」(含 amber badge),新元件 ExcludedCandidatesPanel:篩選/列表/加入考核 Modal/清除全部;說明口語化
    • UI 微調:篩選筆數 chip 移入篩選卡片右下角(無篩選=灰底「共 N 筆」/有篩選=主色 chip + 灰字全部數);操作提醒 +2 條(period-start 必填 + 未列入 Tab)
    • 104 PDF 存檔年度部分修正:考核結案報表「年度總結」tab 部門列表改以年終受評為基準(GetSettlementStatsAsyncYearEndCount / YearEndCompletedCount 欄;前端 filter yearEndCount === 0 部門 + 應考人數改用年終值);同邏輯與「受評 230 人(已排除年中受評但年終未列入 208 人)」對齊
    • 104 回寫中心部門列表 header 加人數統計 chip:3 個 chip(部門 / 應考人數·已完成 / 已回寫進度);年度 tab 從 GetAnnualWritebackStatusAsync 新增 Depts: [{name, totalCount, completedCount}] + TotalPeople / TotalCompleted
  • 2026/04/25HrController 拆檔 + 效能健檢 + UX 微調
    • HrController.cs 拆 7 partial:主檔 11,225 → 7,433 行(移出 3,792 行);按功能切 Settlement / GradeDistribution / GradeAdjustment / NarrativeCheck / Interviews / Sync / Reports;主類別加 partial 修飾元,行為零變化,163 tests 全綠
    • 效能索引 migrationAddPerformanceIndexes):新增 6 個複合索引(ExamRecord×2 / GradeOverrideLog / DisciplinaryRecord / GradeAdjustment / AuditLog),條件式 DropIndex 避免歷史 schema 殘留
    • AsNoTracking 收斂:4 個唯讀 Controller(Announcement / BugReport / Notification / Tagline)13 處 read-only 查詢加 .AsNoTracking(),減少 EF change-tracker 開銷
    • 104 年度成績回寫改 per-dept:年度 tab 從單一按鈕改為與年中/年終 tab 一致的 DepWritebackBlock(每部門一鍵 + 進度追蹤);資料源從 AnnualResults(結算前為空)改為 ExamParticipants(年終 VirtualDeptName)
    • 104 SMB 路徑可訪問性檢查writeback-status API 新增 smbConnected + smbError;前端銀行式雙 banner(DB / SMB);成功不曝路徑、失敗提示「請 HR 聯絡資訊科技部」
    • UI 用語對齊:「專案尚未鎖定 → 至專案管理頁面鎖定專案」改「考核週期尚未鎖定 → 至專案管理頁面鎖定該考核週期」(年中/年終/面談 tab);Waiting 狀態徽章「待審核」→ 「等待中」(與分組標題「複核(等待中)」一致)
  • 2026/04/24 深夜 — 等第分配卡片分子分母對齊(4 視角,min~max 容許區間)、5 本 PAM 操作手冊按角色顯示、104 試打資料 cleanup SQL
  • 2026/04/24 晚間 — 用語對齊「年底→年終」(48 檔 379 處 + DB 補丁)、面談表 PDF 表頭加民國年份、104 回寫操作人預檢補成功 banner
  • 2026/04/24 — 考核結案報表年度總結基準改「年終受評者」、Excel Sheet 2 欄位統一、狀態欄 7 種年度進度標示
  • 更早變更見 CHANGELOG.md