考核表單 PDF 匯出規格(gen_exam_form_pdf.py)
本文件描述考核表單 PDF 三種模式的版面、資料可見性、簽核欄位規則。 Python 腳本:
gen_exam_form_pdf.py(A4 單頁 595.28 × 841.89 pt) 後端服務:ExamResultPdfService.BuildFormPdfAsync(participantId, mode)API 端點:GET /api/hr/participants/{id}/exam-form-pdf?mode=blank|half|filled
三種模式定義
| 模式 | 名稱 | 適用情境 | 說明 |
|---|---|---|---|
blank | 空白表單 | 考核前,供董事長/主管手寫 | A/B/C/D 全空,僅顯示出勤獎懲參考資訊 |
half | 半填版 | 初核完成後,送複核/董事長手寫 B 分 | 只顯示 A 分、初核評語,B/C/D 留空 |
filled | 結果通知書 | 考核完成,含調分/等第 | 全部數值 + 簽核歷程(7 列 table) |
分數欄位可見性
| 欄位 | blank | half | filled |
|---|---|---|---|
| 個別題目 Score A | ✗ 空白 | ✓ 顯示 | ✓ 顯示 |
| 個別題目 Score B | ✗ 空白 | ✗ 空白 | ✓ 顯示 |
| 合計欄 (A) | ✗ 空白 | ✓ 顯示 | ✓ 顯示 |
| 合計欄 (B) | ✗ 空白 | ✗ 空白 | ✓ 顯示 |
| C(平均分) | ✗ 空白 | ✗ 空白 | ✓ 顯示,藍字 |
| 出勤扣分 | ✓ 顯示(紅字) | ✓ 顯示(紅字) | ✓ 顯示(紅字) |
| D(當次成績) | ✗ 空白 | ✗ 空白 | ✓ 顯示,藍字 |
| 成績(D) / 成績(D→調分) | ✗ 空白 | ✗ 空白 | ✓ 顯示,深藍底藍字 |
| 等第 | ✗ 空白 | ✗ 空白 | ✓ 顯示 |
合計行「(A)」和「(B)」之間沒有分隔線,保留手寫空間。(A) 置於右側 -80pt,(B) 置於 -38pt。
背景色規則(三種模式一致)
| 列 | 背景色 |
|---|---|
| 合計行 | 無(僅 GRAY 框線) |
| C(平均分) | 淡藍 (0.92, 0.96, 1.0) |
| D(當次成績) | 淡藍 (0.92, 0.96, 1.0) |
| 成績(D) | 較深藍 (0.85, 0.9, 1.0) |
| 獎懲標題行 | LIGHT_GRAY |
| 獎懲數值行 | 無底色(透明) |
分數列高度(5 列均分)
n_score = 5(合計A+B / C / 出勤 / D / 成績D,三種模式均為 5 列)ROW_H = panel_h / (n_score + 2)(+2 = 獎懲標題 + 獎懲數值)panel_h由簽核欄佔高反推,三種模式SIGN_TOTAL_H = 126pt,確保左右面板等高
獎懲區塊
- 永遠顯示真實資料(大功/小功/嘉獎/大過/小過/申誡各格次數與加減分)
- 正加分:藍字;負扣分:紅字;零:空白
- 高度填滿至
panel_h底部(disc_bottom = y + panel_h),解決左右面板不齊問題
左側主管評語面板
| 模式 | 顯示內容 |
|---|---|
blank | 空灰框(不顯示任何文字) |
half | 初核主管評語(Comment)+ 具體事蹟(Narrative) |
filled | 初核 + 複核主管評語,有兩段即 50/50 分割顯示 |
簽核欄
filled 模式 — 7 列簽核歷程 table
欄位:關卡 / 簽核人 / 簽核時間 / 備註
- 各關卡最後一筆非退回非草稿的
ExamRecord.SubmittedAt - 關卡對應:初核(Step1) / 複核(Step2) / 初審(Step3) / 複審(Step4) / 三審(Step5) / 四審(Step6) / 終審(Step7)
- 超過
TotalReviewSteps的關卡不顯示 - 若
GradeDistribution.IsConfirmed,額外顯示 HR 確認列
blank / half 模式 — 6 格實體簽名框
角色由右至左:初核主管 / 複核主管 / 部門主管 / 執行副總經理 / 總經理 / 董事長
half模式:初核主管格自動填入「主管姓名 + 簽核時間戳記」(取 signing_history Step1 的時間)blank模式:全部空白
附件合併規則
| 模式 | 附件合併至 PDF |
|---|---|
blank | 不合併(附件列表傳空陣列) |
half | 合併(PDF 後附所有上傳附件) |
filled | 合併(PDF 後附所有上傳附件) |
附件合併:Python 端 fitz.open(att_path) → doc.insert_pdf(att_doc),檔案不可開啟時略過(不報錯)。
允許的附件格式(前後端白名單一致,10MB 上限):
- PDF:
.pdf - 圖檔:
.jpg、.jpeg、.png、.gif、.webp、.bmp、.tiff、.tif(PyMuPDF 可直接開啟並以 1 頁文件併入)
Word / Excel(.doc / .docx / .xls / .xlsx)不接受。PyMuPDF 無法合併 Office 檔,請使用者先另存為 PDF 再上傳(前後端都會擋,toast 提示改存 PDF)。
附件路徑解析(後端 C#):
- 舊格式
/uploads/...→{ContentRootPath}/wwwroot/uploads/... - 新格式(絕對路徑)→ 直接使用
職別對應題目(formType)
題目依 ExamParticipant.FormType 過濾,四種類型:
| FormType | 部門 | 職層 | 題組 |
|---|---|---|---|
GenGeneral | 非業務部 | 一般職 | 一般職一般組 |
GenManager | 非業務部 | 管理職 | 一般職管理組 |
BizGeneral | 業務部 | 一般職 | 業務職一般組 |
BizManager | 業務部 | 管理職 | 業務職管理組 |
過濾邏輯(FilterQuestionsByFormType):
Question.FormType == null→ 適用所有"Manager"對應 FormType 含 Manager 的題目"General"對應非 Manager"Sales"對應 Biz 開頭"NonSales"對應非 Biz 開頭
後端 JSON 資料結構(傳給 Python)
{
"mode": "blank|half|filled",
"year": "114",
"period_type": "MidYear|YearEnd",
"print_date": "2026-04-22 14:30",
"employee": {
"name": "", "employee_no": "", "hire_date": "yyyy/MM/dd",
"tenure": "X年Y個月", "company": "", "department": "",
"unit": "", "job_title": "", "job_level": "管理職|一般職"
},
"reviewers": { "r1": "", "r2": "", "a1": null, "a2": null, "a3": null, "a4": null, "a5": null },
"dimensions": [
{
"name": "個人條件", "prefix": "(一)", "weight": "25%",
"questions": [{ "name": "專業技能", "score_a": 4.0, "score_b": 4.0 }]
}
],
"summary": {
"score_a": 80, "score_b": 78, "score_c": 79,
"leave_hours_personal": 8, "leave_hours_sick": 0,
"attendance_deduct": -1.0, "score_d": 78.0,
"grade": "甲等", "adjusted_score": null,
"r1_comment": "評語文字", "r2_comment": "",
"r1_narrative": "具體事蹟", "r2_narrative": "",
"comment": "(fallback)", "narrative": "(fallback)"
},
"disciplinary": { "大功": 0, "小功": 0, "嘉獎": 2, "大過": 0, "小過": 0, "申誡": 1 },
"signing_history": [
{ "role": "初核", "name": "王小明", "time": "2026-03-15 10:30:00" }
],
"attachments": [
{ "path": "C:/ExamSystem/wwwroot/uploads/exam/..." }
]
}API 規格
GET /api/hr/participants/{id}/exam-form-pdf?mode=blank|half|filled
Authorization: Bearer {token}
Response: application/pdf(直接下載)
Content-Disposition: attachment; filename="{employeeNo}_{name}_{mode}.pdf"
- HR/Admin 限定
- mode 預設
filled - 產檔後直接回傳 bytes,不寫
GeneratedDocuments(ad-hoc 下載,不走 104 回寫流程) - Python 腳本呼叫:
python gen_exam_form_pdf.py --input {tempJson},結果取output_path讀檔
前端按鈕(ExportPdfButton)
位置:ReviewFormPage.tsx — 頁首(僅 HR 顯示,含已完成仍可匯出)
- 下拉選項:空白表單 / 半填版 / 結果通知書
- 自動建議模式:
isCompleted → filled;有 Step1 非退回記錄 →half;否則 →blank - 點選後呼叫
downloadBlobWithAuth('/api/hr/participants/{id}/exam-form-pdf?mode=xxx', fileName) - Loading 狀態:按鈕 disabled + spinner,避免重複點擊
- 錯誤:toast error 顯示後端錯誤訊息
最後更新:2026/04/22 · 年代集團資訊科技部