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 | 公司 | 在職人數 | 等第代碼(特優~丙等) |
|---|---|---|---|
| 1 | ERA 年代網際 | 903 | 14, 15, 16, 17, 18 |
| 2 | 年代健康生活 | 0 | 19, 20, 21, 22, 23 |
| 13 | 翃聚傳播 | 0 | (舊資料無對應) |
| 14 | 艾比茲科技 | 2 | 27, 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(新媒體部 + 版權購銷部 範例)
- HR 手動把考核 PDF 丟到
\\10.150.111.161\d$\Program Files (x86)\EHRMS\Upload\05091991\Exam\ - 呼叫
POST /api/hr/periods/{periodId}/writeback-104?testMode=true,body 同正式版 - HR 在 104 前台檢查資料正確性
- 確認後執行 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:等第審核 ✓(既有)
- HR 打開「等第調整」頁(
/review/grade-distribution) - 虛擬部門主管(或 HR 代為操作)對該部門做等第調整
- 點「提交部門」→
DepartmentConfirmation.Status = Submitted - HR 確認 →
Status = Confirmed
Gate 1:該部門等第確認 → 可進入下一階段
🔹 階段 3:發信通知 ✓(既有)
- 「結案報表」頁 → 該部門卡片 → 點「發送報告」
- 套
NotificationTemplate(Code=DeptReport) - 附結案報告 Excel(同匯出 Excel 的檔)
- 收件人勾選 → 輸入 HR 密碼 → 寄出
🔹 階段 4:HR 自管(系統不追蹤)
- 紙本簽回(部門主管簽名後回 HR)
- HR 確認全部門繳回 + 對考核無異議
🔹 階段 5:公司週期報告書 ✓(既有 Excel)
- HR 匯出「公司週期報告書」Excel(全部門合併)
- 紙本給董事長審閱
🔹 階段 6:董事長簽名 → 鎖定專案
- HR 收到董事長簽名的紙本
- 到「專案管理」頁點「鎖定專案」→
ExamProject.Status = Locked - 此後不允許改人員 / 分數 / 等第
Gate 2:專案 Locked → 可執行 104 回寫
🔹 階段 7:104 結案 PDF 產生 ✓(既有)
在「考核結案報表 → 104 回寫 PDF 存檔」tab:
- 每個部門點「產生」按鈕
- 背景任務產 PDF 到
D:\ExamSystem\{年}\{年中|年終}\104回寫PDF\{部門}\ - 檔名:
{EmpNo}_{Name}_{yyyyMMdd_HHmm}.pdf - 寫入
GeneratedDocuments表(TaskType=Writeback104,Status=Success)
Gate 3:該部門全部 PDF 已產生
🔹 階段 8:分部門 104 回寫 ⭐(2026/04/22 新增)
「104 回寫中心」頁 → 選「年中考核成績」/「年終考核成績」頁簽:
每部門一列:
[民調中心] 1/1 已回寫 ✓ [重新回寫]
[艾比茲] 2/2 已回寫 ✓ [重新回寫]
[行銷公關部] 0/1 [執行回寫]
...
點「執行回寫」→ 輸入 HR 密碼 →
後端 Writeback104Service.WritebackDeptAsync 每位員工:
- 反查 104
EMPLOYEE_ID+COMPANY_ID(byEMPLOYEE_NO) - 等第 →
PROBATION_GRADE_ID(依 COMPANY_ID 對照表) - 查
HRMS_EXAM是否已有同筆(byEMPLOYEE_ID + EXAM_PERIOD + START_PLAN_NAME + PLAN_TIMES_NAME) - 若存在 → 刪 SMB 上舊 PDF +
DELETE HRMS_EXAM INSERT HRMS_EXAM(23 欄位,EXAM_TEST_REPORT= PDF 檔名)COPY本地 PDF →\\10.150.111.161\d$\...\Exam\{檔名}- 成功 counter++;失敗記
FailureLogJSON
狀態寫入 DeptWritebackStatuses:
Success/PartialFail/Failed- Modal 跳出結果摘要 + 失敗清單(可重試)
🔹 階段 9:週期結案 ⭐(2026/04/22 新增)
當該週期所有部門 Status=Success:
- 「週期結案」按鈕啟用(綠底)
- HR 點擊 → 輸入 HR 密碼 + 二次確認
- 後端
Writeback104Service.ClosePeriodAsync驗證 ExamPeriod.IsClosed = true,ClosedAt+ClosedBy填入- 週期狀態顯示「已結案 + 日期 + 結案人」
完成!
⚠️ 失敗重試情境
| 情境 | 處理 |
|---|---|
| 某員工 104 查無 EMPLOYEE_ID | 失敗清單顯示 → HR 補建 104 帳號 → 點「重新回寫」 |
| PDF 本地遺失 | 回「104 回寫 PDF 存檔」頁重產 → 重新回寫 |
| SMB 寫入失敗(網路/磁碟) | 檢查網路/磁碟 → 重新回寫 |
| HR 要重寫整部門 | 點「重新回寫」→ 後端自動先 DELETE 舊筆 + SMB 檔,再 INSERT 新 |
系統 Gating 條件(後端 Service 驗證)
單部門 writeback-104 必須:
- Project.Status == “Locked”(董事長已簽,HR 手動鎖定)
- 該部門 DepartmentConfirmation.Status == “Confirmed”
- 該部門所有員工的 104 回寫 PDF 已產生
- HR 密碼正確(
ValidateSync104PasswordAsync) - 該部門 DeptWritebackStatus.Status != “Success”(或 HR 強制重跑)
週期結案必須:
- 所有部門 DeptWritebackStatus.Status == “Success”
- HR 密碼正確
🟡 待補(下一批)
面談結果完整流程(紙本簽核 → 上傳 → 回寫 104)
前段已完成 ✓:
- 觸發候選(年中底/年度 乙丙等 + 年終+年度 case 合併清單)
gen_interview_pdf.py套表產出面談表 + 追蹤表 PDF → 存InterviewRecord.PdfPath- 發信附 PDF 給初核主管
- 初核主管手寫評語 + 簽名紙本
後段待補 ❌:
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→ 寫年終週期的 rowAnnual→ 寫年度週期的 row
Gating:該員考核結果已 INSERT 過(EXAM_ID 存在)+ 掃描 PDF 已上傳(ScannedPath 非空)
年度總成績 per-dept
目前 UI 保留舊版整批 button,需改為 per-dept(同考核成績架構)。
相關檔案
| 項目 | 檔案 |
|---|---|
| 規格 + 對照表 | Memory reference_104_writeback.md |
| Service | Services/Writeback104Service.cs |
| Repository | Services/Hrms104Repository.cs(GetEmployeeIdAndCompanyAsync / CheckExistingExamAsync / DeleteExamAsync / InsertExamAsync) |
| API | Controllers/HrController.cs(writeback-104 / close / writeback-status) |
| UI | client-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'))。