考核表單 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)

分數欄位可見性

欄位blankhalffilled
個別題目 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 · 年代集團資訊科技部