104 回寫作業流程

部門考核完成提交 → 寫入 104 HRMS_EXAM 的完整 lifecycle

最後更新:2026/04/23


🔧 2026/04/23 規格修正(HR 確認 + DB 驗證)

對照 vwCUSTOM2_REPORT_DETAIL_EXAM 歷史 + DB schema 直查確認:

修正 1:START_PLAN_NAME 三種週期字串

週期舊(錯)❌新(正確)✅
年中XXX年年度績效考核XXX年年中績效考核
年終XXX年年度績效考核XXX年年終績效考核
年度XXX年總考核XXX年總等第

PLAN_TIMES_NAME(上)/(下)/年)不變。對照 113/114 年歷史資料完全一致。

修正 2:TESTIONG_RESULT 規則

從固定 '0' 改為依分數判定:

  • FinalScore < 70'1'(不合格)
  • FinalScore >= 70'0'(合格)

修正 3:INSERT 時保留舊 EXAM_SELF_REPORT

DELETE 前先讀 EXAM_SELF_REPORT,INSERT 時帶回去。
理由:若先寫面談、後重跑考核,避免把面談檔名洗掉(保險機制;正常流程是「考核 → 面談」順序,不會發生,但保留以防萬一)。

修正 4:SMB 預設路徑

\\10.150.111.161\d$\Program Files (x86)\EHRMS\Upload\05091991\Exam

平鋪不分子資料夾,覆寫 Services/Writeback104Service.cs 預設值。

公司對照(DB 驗證 2026-04-23)

COMPANY_ID公司在職人數等第代碼(特優~丙等)
1ERA 年代網際90314, 15, 16, 17, 18
2年代健康生活019, 20, 21, 22, 23
13翃聚傳播0(舊資料無對應)
14艾比茲科技227, 28, 29, 30, 31

沒有「壹電視 / MUCH」公司(先前 reference memory 標錯)。本地 ExamSystem 顯示「NTV」的員工,在 104 實際 COMPANY_ID = 1 (ERA),回寫時用 Hrms104Repository.GetEmployeeIdAndCompanyAsync 即時查 104 真實值,所以等第代碼會用 ERA 系列(正確)。

待 HR 確認

  • CREATE_EMPLOYEE_ID / UPDATE_EMPLOYEE_ID:目前寫死 -1,HR 需提供確切「系統寫入者」員工 ID,建議搬到 SystemSettings:Sync104:WriterEmployeeId

🧪 測試模式(待實作)

設計

  • API 加 ?testMode=true 參數
    • 跳過 Project.Status == "Locked" 檢查
    • HR 密碼仍要
  • SMB File.Copy 改「目標已存在則跳過」(HR 可手動先丟檔,DB 仍正常 INSERT 檔名)

測試 SOP(新媒體部 + 版權購銷部 範例)

  1. HR 手動把考核 PDF 丟到 \\10.150.111.161\d$\Program Files (x86)\EHRMS\Upload\05091991\Exam\
  2. 呼叫 POST /api/hr/periods/{periodId}/writeback-104?testMode=true,body 同正式版
  3. HR 在 104 前台檢查資料正確性
  4. 確認後執行 cleanup:
    -- 1) 刪 104 HRMS_EXAM 測試記錄
    DELETE FROM HRMS_EXAM
    WHERE EXAM_PERIOD = '2026-06-01'
      AND START_PLAN_NAME = '115年年中績效考核'
      AND EMPLOYEE_ID IN ( /* 兩部門員工 ID 清單 */ );
     
    -- 2) 刪本地 ExamSystem DeptWritebackStatuses
    DELETE FROM DeptWritebackStatuses
    WHERE PeriodId = {115年中periodId}
      AND VirtualDeptName IN ('新媒體部','版權購銷部')
      AND WritebackType = 'MidYear';

完整 lifecycle(考核成績)

🔹 階段 1:考核作業

初核 → 複核 → 初審 → 複審 → 三審 → 終審
     ↓
全部員工 Status = Completed
(該部門所有人的 FinalScore / FinalGrade 已定)

🔹 階段 2:等第審核 ✓(既有)

  1. HR 打開「等第調整」頁(/review/grade-distribution
  2. 虛擬部門主管(或 HR 代為操作)對該部門做等第調整
  3. 點「提交部門」→ DepartmentConfirmation.Status = Submitted
  4. HR 確認 → Status = Confirmed

Gate 1:該部門等第確認 → 可進入下一階段

🔹 階段 3:發信通知 ✓(既有)

  1. 「結案報表」頁 → 該部門卡片 → 點「發送報告」
  2. NotificationTemplate(Code=DeptReport
  3. 附結案報告 Excel(同匯出 Excel 的檔)
  4. 收件人勾選 → 輸入 HR 密碼 → 寄出

🔹 階段 4:HR 自管(系統不追蹤)

  • 紙本簽回(部門主管簽名後回 HR)
  • HR 確認全部門繳回 + 對考核無異議

🔹 階段 5:公司週期報告書 ✓(既有 Excel)

  1. HR 匯出「公司週期報告書」Excel(全部門合併)
  2. 紙本給董事長審閱

🔹 階段 6:董事長簽名 → 鎖定專案

  1. HR 收到董事長簽名的紙本
  2. 到「專案管理」頁點「鎖定專案」→ ExamProject.Status = Locked
  3. 此後不允許改人員 / 分數 / 等第

Gate 2:專案 Locked → 可執行 104 回寫

🔹 階段 7:104 結案 PDF 產生 ✓(既有)

在「考核結案報表 → 104 回寫 PDF 存檔」tab:

  1. 每個部門點「產生」按鈕
  2. 背景任務產 PDF 到 D:\ExamSystem\{年}\{年中|年終}\104回寫PDF\{部門}\
  3. 檔名:{EmpNo}_{Name}_{yyyyMMdd_HHmm}.pdf
  4. 寫入 GeneratedDocuments 表(TaskType=Writeback104,Status=Success

Gate 3:該部門全部 PDF 已產生

🔹 階段 8:分部門 104 回寫 ⭐(2026/04/22 新增)

「104 回寫中心」頁 → 選「年中考核成績」/「年終考核成績」頁簽:

每部門一列:

[民調中心]    1/1 已回寫 ✓     [重新回寫]
[艾比茲]      2/2 已回寫 ✓     [重新回寫]
[行銷公關部]  0/1              [執行回寫]
...

點「執行回寫」→ 輸入 HR 密碼 →

後端 Writeback104Service.WritebackDeptAsync 每位員工:

  1. 反查 104 EMPLOYEE_ID + COMPANY_ID(by EMPLOYEE_NO
  2. 等第 → PROBATION_GRADE_ID(依 COMPANY_ID 對照表)
  3. HRMS_EXAM 是否已有同筆(by EMPLOYEE_ID + EXAM_PERIOD + START_PLAN_NAME + PLAN_TIMES_NAME
  4. 若存在 → 刪 SMB 上舊 PDF + DELETE HRMS_EXAM
  5. INSERT HRMS_EXAM(23 欄位,EXAM_TEST_REPORT = PDF 檔名)
  6. COPY 本地 PDF → \\10.150.111.161\d$\...\Exam\{檔名}
  7. 成功 counter++;失敗記 FailureLog JSON

狀態寫入 DeptWritebackStatuses

  • Success / PartialFail / Failed
  • Modal 跳出結果摘要 + 失敗清單(可重試)

🔹 階段 9:週期結案 ⭐(2026/04/22 新增)

當該週期所有部門 Status=Success

  1. 「週期結案」按鈕啟用(綠底)
  2. HR 點擊 → 輸入 HR 密碼 + 二次確認
  3. 後端 Writeback104Service.ClosePeriodAsync 驗證
  4. ExamPeriod.IsClosed = true, ClosedAt + ClosedBy 填入
  5. 週期狀態顯示「已結案 + 日期 + 結案人」

完成!


⚠️ 失敗重試情境

情境處理
某員工 104 查無 EMPLOYEE_ID失敗清單顯示 → HR 補建 104 帳號 → 點「重新回寫」
PDF 本地遺失回「104 回寫 PDF 存檔」頁重產 → 重新回寫
SMB 寫入失敗(網路/磁碟)檢查網路/磁碟 → 重新回寫
HR 要重寫整部門點「重新回寫」→ 後端自動先 DELETE 舊筆 + SMB 檔,再 INSERT 新

系統 Gating 條件(後端 Service 驗證)

單部門 writeback-104 必須:

  1. Project.Status == “Locked”(董事長已簽,HR 手動鎖定)
  2. 該部門 DepartmentConfirmation.Status == “Confirmed”
  3. 該部門所有員工的 104 回寫 PDF 已產生
  4. HR 密碼正確(ValidateSync104PasswordAsync
  5. 該部門 DeptWritebackStatus.Status != “Success”(或 HR 強制重跑)

週期結案必須:

  1. 所有部門 DeptWritebackStatus.Status == “Success”
  2. HR 密碼正確

🟡 待補(下一批)

面談結果完整流程(紙本簽核 → 上傳 → 回寫 104)

前段已完成 ✓:

  1. 觸發候選(年中底/年度 乙丙等 + 年終+年度 case 合併清單)
  2. gen_interview_pdf.py 套表產出面談表 + 追蹤表 PDF → 存 InterviewRecord.PdfPath
  3. 發信附 PDF 給初核主管
  4. 初核主管手寫評語 + 簽名紙本

後段待補 ❌: 5. 紙本回交 HR 6. HR 掃描整理後上傳到 PAM → 存 InterviewRecord.ScannedPath 7. 上傳後先存放本地,不立即寫 104 8. 等 104 回寫面談資料的觸發時點 → 才:

  • UPDATE HRMS_EXAM.EXAM_SELF_REPORT = ScannedPath 的檔名
  • COPY ScannedPath 檔案 → 104 SMB share
  • 標記 InterviewRecord.WrittenBack104 = true

寫回分派規則

  • 年中面談 → UPDATE 年中週期的 HRMS_EXAM row(該員年中 INSERT 過的那筆)
  • 年終 + 年度面談(UI 合併清單)→ 依 InterviewRecord.TriggerPeriodType 分派:
    • YearEnd → 寫年終週期的 row
    • Annual → 寫年度週期的 row

Gating:該員考核結果已 INSERT 過(EXAM_ID 存在)+ 掃描 PDF 已上傳(ScannedPath 非空)

年度總成績 per-dept

目前 UI 保留舊版整批 button,需改為 per-dept(同考核成績架構)。


相關檔案

項目檔案
規格 + 對照表Memory reference_104_writeback.md
ServiceServices/Writeback104Service.cs
RepositoryServices/Hrms104Repository.csGetEmployeeIdAndCompanyAsync / CheckExistingExamAsync / DeleteExamAsync / InsertExamAsync
APIControllers/HrController.cswriteback-104 / close / writeback-status
UIclient-app/src/pages/hr/Sync104WriteBackPage.tsx
狀態表DeptWritebackStatuses(Migration 20260422055659_AddDeptWritebackStatusAndPeriodClose
週期結案ExamPeriod.IsClosed / ClosedAt / ClosedBy

API 一覽

POST /api/hr/periods/{periodId}/writeback-104

Body: { virtualDeptName: string, hrPassword: string } 回傳:{ totalCount, successCount, failCount, status, failures[] }

POST /api/hr/periods/{periodId}/close

Body: { hrPassword: string } 驗證全部門 Success → 設定 IsClosed=true

GET /api/hr/periods/{periodId}/writeback-status

回傳:{ periodId, writebackType, isClosed, closedAt, closedBy, statuses[] }

GET /api/hr/projects/{projectId}/writeback-status(專案彙總版)

回傳各週期的寫回 + 面談狀態:

{
  "periodStatuses": [
    {
      "periodId": 1,
      "periodTitle": "2026 年中",
      "periodType": "MidYear",
      "totalParticipants": 35,
      "completedCount": 35,
      "writtenBackCount": 0,
      "interviewTotal": 2,              // 面談候選總數
      "interviewCompletedCount": 1,     // Status = Submitted/Completed
      "interviewWrittenCount": 0,       // 已 104 回寫
      "interviewCandidates": [          // 展開清單用
        {
          "participantId": 123,
          "interviewId": 45,
          "employeeNo": "250413",
          "name": "陳柏克",
          "virtualDeptName": "新媒體部",
          "finalGrade": "乙等",
          "finalScore": 65.5,
          "status": "Submitted",
          "writtenBack104": false,
          "hasScanned": true
        }
      ]
    }
  ]
}

前端「104 回寫中心」用 interviewCandidates[] 渲染展開列表,含「檢視檔案」按鈕(previewBlobWithAuth('/hr/interviews/{id}/scanned'))。