1.6 · การลา (Leave)
บทนี้อธิบาย flow การลาในเชิง business — ตั้งแต่ประเภทการลา, validation ก่อนยื่น, การอนุมัติ multi-level, จนถึง cancel
flow นี้เป็น flow ที่ซับซ้อนที่สุดใน VTRC เพราะ validation หนักและ approval chain มาจาก HRMI ไม่ใช่ VTRC
ประเภทการลา — ไม่ได้ hardcode
จำนวนประเภทการลาและกฎของแต่ละประเภท ไม่ได้เก็บใน VTRC แต่ดึง live จาก HRMI
Query — fetchLeaveType / dropdownLeaveType (typeDefs/hrmi.js:4-15)
แต่ละประเภทมี field ที่กำหนดเงื่อนไขการลา (hrmi.js:436-451)
| Field | หมายถึง |
|---|---|
leaveTypeCode | รหัสประเภท (string) |
leaveTypeName | ชื่อไทย เช่น "ลาป่วย" |
isFullDayLeave | ลาเต็มวันเท่านั้น |
isCanLeaveOver | ลาข้ามปีได้ไหม |
maxLeaveDay | จำนวนวันสูงสุดต่อครั้ง |
minLeaveDay | จำนวนวันขั้นต่ำต่อครั้ง |
earlyLeaveDay | จองล่วงหน้ากี่วันขึ้นไป |
backLeaveDay | ลาย้อนหลังได้กี่วัน |
reqAttachDay | ลาต่อเนื่องกี่วันขึ้นไปต้องแนบใบรับรองแพทย์ |
isAutoApprove | อนุมัติอัตโนมัติ |
isCancelByReq | ยกเลิกได้ผ่าน request |
ประเภทที่ใช้กันจริงมี ~12 ประเภท — ลาป่วย, ลากิจ, ลาพักผ่อน, ลาคลอด, ลาทหาร, ลาไปศึกษา, ลาอุปสมบท, ลาช่วยภริยาคลอด, ลาดูแลบุตร, ลาประจำปี, ลาอื่นๆ
ในโค้ด VTRC มี code เดียวที่ถูก hard-reference — "1" คือลาป่วย (hrmi.js:1169 ใน continuous-leave check)
Validation ก่อนยื่นคำขอ
ก่อน createLeaveDocument ผ่าน — VTRC ตรวจ 5 อย่าง (lib/controllers/hrmi/hrmi.js:1277-1333)
| ลำดับ | ตรวจอะไร | Error message ถ้าไม่ผ่าน |
|---|---|---|
| 1 | maxLeaveDay | ${leaveTypeLabel} ไม่เกิน ${maxLeaveDay} วัน ต่อครั้ง |
| 2 | minLeaveDay | ต้องลาอย่างน้อย ${minLeaveDay} วัน |
| 3 | backLeaveDay (ย้อนหลัง) | ไม่อยู่ในเงื่อนไข ย้อนหลัง ${backLeaveDay} วัน |
| 4 | earlyLeaveDay (ล่วงหน้า) | ไม่อยู่ในเงื่อนไข ล่วงหน้า ${earlyLeaveDay} วัน |
| 5 | วันเริ่ม/สิ้นสุด sanity | START_DATE_LEAVE_TYPE_MISMATCH ฯลฯ |
Bypass rules — 2 กรณี
- HR HQ (
0734) —hrmi.js:1302skip ทั้ง 4 validation ถ้า admin สังกัดorgUnitCodeขึ้นต้นด้วย0734 adminCanPass === true— flag นี้ส่งจาก HR admin UI เพื่อบังคับผ่าน
ทั้งสองเป็น policy ของ HR HQ — ไม่ใช่ bug
การลา Custom (รายชั่วโมง)
durationType: 'Custom' — ลาเป็นช่วงเวลาในวัน ไม่ใช่ทั้งวัน
hrmi.js:1335-1385 — ตรวจเพิ่ม
- ดึง shift ของพนักงานผ่าน
fetchCDLeaveShiftTime - จัดการ overnight shift (ที่
shiftTimeIn > shiftTimeOutเช่น 22:00–06:00) - ตรวจว่า
leaveStartTimeและleaveEndTimeอยู่ใน[shiftTimeIn, shiftTimeOut] - ถ้า
leaveStartTime > leaveEndTime→ reject "เวลาเริ่มต้นต้องไม่มากกว่าเวลาสิ้นสุด"
durationType enum (hrmi.js:584-589)
| Code | ความหมาย |
|---|---|
H1 | ครึ่งวันเช้า |
H2 | ครึ่งวันบ่าย |
Full | เต็มวัน |
Custom | รายชั่วโมง |
กฎ "ลาต่อเนื่องต้องมีใบแพทย์"
hrmi.js:1164-1216 — findLeaveContinuos(args, empCode, reqAttachDay)
กฎ — ถ้าลาป่วย (type "1") ต่อเนื่องกัน >= reqAttachDay วัน ต้องแนบใบรับรองแพทย์
algorithm
- เริ่มจาก
leaveStartDate - เดินหลังไปวันก่อนหน้า เช็คว่ามีลาป่วยที่
PENDINGหรือAPPROVEไหม - เดินหน้าไปวันถัดไป เช็คแบบเดียวกัน
- รวมวันต่อเนื่อง — ถ้า >=
reqAttachDayและไม่มี attach → reject
Error — พบการลาต่อเนื่อง กรุณาแนบเอกสารใบรับรองแพทย์
ตรวจที่ hrmi.js:1473-1480
ตรวจวันทับซ้อน (Duplicate Leave)
| durationType | function | Error |
|---|---|---|
Full | validateDuplicateDate | วันลาทับซ้อนกับเอกสารลาอื่น |
Custom | validateDuplicateTime | เวลาทับซ้อนกับเอกสารลาอื่น |
ตรวจที่ hrmi.js:1458-1471
Document Number Format
hrmi.js:1424-1452 — generate docNo ใน format
LEV{YYMM}-NNNNNN
ตัวอย่าง:
LEV2501-000123 (ม.ค. 2025, เลขที่ 123 ของเดือน)
LEV2512-000004 (ธ.ค. 2025, เลขที่ 4 ของเดือน)- prefix เปลี่ยนตามเดือน (
2501,2502, ...) - running number zero-pad 6 หลัก
- reset เป็น 1 เมื่อเดือนเปลี่ยน
LeaveDocument Status
models/core/leave/leave.js:58-61 — status enum ทั้งหมด 6 ค่า
| Status | หมายถึง | เมื่อไร |
|---|---|---|
PENDING | รออนุมัติ | หลัง create ใหม่ |
MOREINFO | ขอข้อมูลเพิ่ม | approver ตั้ง |
APPROVE | อนุมัติแล้ว | level สุดท้าย |
REJECT | ไม่อนุมัติ | approver ตั้ง |
CANCEL | ยกเลิก | หลังอนุมัติแล้ว ขอยกเลิก |
PEDDING_CANCLE | รอยกเลิก | พนักงานขอ cancel หลังอนุมัติ |
จุดสำคัญ — มี typo PEDDING_CANCLE (ควรเป็น PENDING_CANCEL) ที่ติดมาตั้งแต่ต้นโปรเจกต์และอยู่ใน production database ทุก consumer ต้องสะกดผิดเหมือนกัน — อย่า "แก้ให้ถูก" ในไฟล์เดียวเพราะจะพัง
Approval Chain — Multi-Level
ผู้อนุมัติการลา ไม่ได้เก็บใน VTRC — ดึง live จาก HRMI
fetchApproverLeave(empCode, leaveTypeId, leaveDay) (hrmi.js:9-15) เรียก hrmiGetLeaveApproverCD (centralize/controllers/hrmi.js:44-64) → ส่ง GraphQL query getLeaveApprover ไป HRMI
ผลลัพธ์ — list ของ [ApproverMedical] (ชื่อ type ซ้ำกับ hospital แต่คนละ context)
type ApproverMedical {
users: [User] # ผู้อนุมัติใน level นี้
level: Int # ลำดับขั้น (1, 2, 3...)
canEdit: Boolean # แก้คำขอแทนได้ไหม
approveType: String # ประเภทการอนุมัติ
approveLabel: String # label ไทย เช่น "ผู้บังคับบัญชาระดับ..."
}chain อาจมีหลาย level — แต่ละ level ต้อง approve ก่อนส่ง level ถัดไป
ถ้า HRMI ล่ม — VTRC ไม่รู้ว่าใครเป็นผู้อนุมัติ → การลาทั้งระบบไม่สามารถอนุมัติได้
เส้นทางของ Leave Document
พนักงาน create
│
▼
status = PENDING (level 1)
│
├── ผู้อนุมัติ level 1 → approve → ส่งต่อ level 2
│ │
│ ▼
│ ผู้อนุมัติ level 2 → approve
│ │
│ ▼ (ถ้าเป็น level สุดท้าย)
│ status = APPROVE
│
├── ผู้อนุมัติ → reject
│ │
│ ▼
│ status = REJECT
│
└── ผู้อนุมัติ → moreinfo
│
▼
status = MOREINFO → พนักงานแก้ → resubmit → PENDING
หลัง APPROVE:
- พนักงานขอ cancel → status = PEDDING_CANCLE
- ผู้อนุมัติ approve cancel → status = CANCELCancel flow — แยกจาก reject
checkRemoveLeaveDocumentAsFlow (hrmi.js:140) — ฟังก์ชันที่แยก "ลบ draft" กับ "ขอ cancel หลังอนุมัติ"
- ถ้าเอกสารยัง
PENDING→ ลบได้ทันที (removeLeaveDocumentDraf) - ถ้า
APPROVEแล้ว → ต้องผ่าน cancel request (PEDDING_CANCLE) แล้วรอ approver approve cancel
Field สำคัญใน LeaveDocument
models/core/leave/leave.js:1-164 — field ที่ใช้บ่อย
| Field | หน้าที่ |
|---|---|
wdLeaveId (UUID) | PK |
leaveType / leaveTypeLabel | รหัสและชื่อประเภท |
leaveStartDate / leaveEndDate | วันที่เริ่ม/สิ้นสุด |
leaveDay | จำนวนวันรวม |
durationType | Full / H1 / H2 / Custom |
empCode | รหัสพนักงาน |
createLeaveBy | EmpCode ของคนสร้าง (อาจเป็น admin แทน) |
docId | เลขที่เอกสาร (LEV{YYMM}-NNNNNN) |
status | enum ด้านบน |
level | level ปัจจุบันใน approval chain |
noteApprove | หมายเหตุของผู้อนุมัติ |
clientType | WEB_CLIENT (พนักงาน) / WEB_ADMIN (HR แทน) |
orgCode / orgName | หน่วยงานตอนสร้างเอกสาร |
sourceDB | HRMI replica ที่ดึงข้อมูล |
leaveTotalUse / leaveUsed / leaveDayAll | วันลาคงเหลือ/ใช้ไป/ทั้งหมด |
clientType สำคัญ — ใช้แยกว่าเอกสารนี้ "พนักงานยื่นเอง" หรือ "HR ยื่นแทน" เพราะ validation ต่างกัน (HR สามารถ bypass ได้)
Attachment — ไฟล์แนบ
hrmi.js:138-237 — mutations สำหรับ attach file
| Mutation | หน้าที่ |
|---|---|
createAttachmentFile | อัปโหลดไฟล์แนบ |
updateAttachmentFile | แก้ไข |
deleteAttachmentFile | ลบ |
fetchAttachmentListFile และ fetchAttachmentDetailFile ใช้อ่าน
ใช้สำหรับ
- ใบรับรองแพทย์ (เมื่อลาป่วยต่อเนื่อง)
- เอกสารประกอบลาคลอด (ใบรับรองแพทย์เกี่ยวกับการตั้งครรภ์)
- หนังสือลางาน (ในกรณีเฉพาะ)
History — ประวัติการเปลี่ยนแปลง
fetchLeaveDocumentHistory (hrmi.js) — คืน action trail ของแต่ละเอกสาร
แต่ละ row บอก
- ใคร (empCode)
- ทำอะไร (approve / reject / moreinfo / edit)
- เมื่อไร (timestamp)
- note อะไร
ใช้สำหรับ audit + debug "ทำไมสถานะเป็นแบบนี้"
สรุป — checklist ตอน debug
| อาการ | สาเหตุที่เป็นไปได้ | ที่ตรวจ |
|---|---|---|
| "ลาไม่ผ่าน" + error max day | เกิน maxLeaveDay (เว้นแต่ 0734 / adminCanPass) | hrmi.js:1304 |
| "ลาไม่ผ่าน" + error ล่วงหน้า | ไม่จองล่วงหน้าพอ | hrmi.js:1319, 1323 |
| "ลาไม่ผ่าน" + error ย้อนหลัง | เลย backLeaveDay | hrmi.js:1317, 1321 |
| "ลาไม่ผ่าน" + error ทับซ้อน | มีเอกสารเดิมวันทับซ้อน | hrmi.js:1458-1471 |
| "ลาไม่ผ่าน" + error ใบแพทย์ | ลาป่วยต่อเนื่อง >= reqAttachDay วัน | hrmi.js:1473-1480 |
| อนุมัติไม่ได้ | HRMI ล่ม → ดึง approver chain ไม่ได้ | centralize/controllers/hrmi.js:44-64 |
| Cancel ไม่ผ่าน | สถานะไม่ใช่ APPROVE | hrmi.js:140 |
| status ภาษาไทยดูแปลก | typo PEDDING_CANCLE | models/core/leave/leave.js:59 |