12.11 · Scorecard + known bugs + magic numbers
บทสุดท้ายของ Volume 12 รวมภาพใหญ่ — scorecard ของแต่ละ domain, bug ที่พบ, magic numbers ที่ต้องระวัง, dead code, และแนวทางปรับปรุง
1. Domain scorecard
ให้คะแนนแต่ละ domain ตาม 6 ด้าน (5 = ดีมาก, 1 = แย่)
| Domain | Logic clarity | Testability | Coupling to CD | Validation | Bug density | รวม/25 |
|---|---|---|---|---|---|---|
| Leave | 3 | 2 | 5 | 4 | 2 | 16 |
| Welfare (WDHospital) | 2 | 1 | 3 | 3 | 2 | 11 |
| Payroll/Payslip | 3 | 2 | 5 | 3 | 3 | 16 |
| Provident Fund | 4 | 3 | 4 | 4 | 3 | 18 |
| Study Leave | 4 | 3 | 2 | 3 | 4 | 16 |
| Auth/Registration | 3 | 2 | 5 | 3 | 3 | 16 |
| Delegate/Role | 2 | 2 | 2 | 2 | 4 | 12 |
| Notification | 3 | 2 | 2 | 3 | 3 | 13 |
สรุปคะแนน
- ดีที่สุด: Provident Fund (18/25) — code ชัด, transaction ใช้ถูก
- แย่ที่สุด: Welfare/WDHospital (11/25) — ไฟล์ใหญ่ 3,200 บรรทัด, magic numbers เยอะ, validation cap ถูกปิด
- เสี่ยงสูง: Leave — มี bug สำคัญที่
hrmi.js:1266
2. Known bugs
Bug #1 — createLeaveDocument เงื่อนไขกลับด้าน (Leave)
ที่อยู่: hrmi.js:1266
if (typeof args.approveStatus === "undefined") {
mappingStatus = statusHrmi.filter(value => args.approveStatus === value.statusId)
...ปัญหา: เงื่อนไข === "undefined" กลับด้าน — ควรเป็น !== "undefined"
ผลกระทบ: เมื่อ client ส่ง approveStatus มา — จะตก else branch และใช้ args.status (ซึ่งอาจเป็น undefined)
ความรุนแรง: สูง — ส่งผลต่อ status ทุกเอกสารลาที่ถูกสร้าง
แนวทางแก้: เปลี่ยน === เป็น !==
Bug #2 — status mapping hrmi.js:1266 ผิดพลาด
ปัญหา: เมื่อ client ส่ง approveStatus = 'W' จะ filter จาก args.approveStatus ไม่ใช่ค่าคงที่
mappingStatus = statusHrmi.filter(value => args.approveStatus === value.statusId)ผลกระทบ: mappingStatus อาจว่าง → throw DATA_NOT_FOUND ทั้งที่ควรผ่าน
Bug #3 — PDF retention 5 วินาที (Payroll)
ที่อยู่: routes.js:82
res.sendFile(pdfPath)
setTimeout(() => deletePDF(pdfPath), 5000)ปัญหา: ถ้า client download ช้ากว่า 5 วินาที — ไฟล์ถูกลบก่อน → 404
ผลกระทบ: ผู้ใช้ในเครือข่ายช้าอาจไม่ได้รับสลิป
แนวทางแก้: ใช้ streaming หรือเพิ่ม retention เป็น 5 นาที
Bug #4 — Welfare cap ถูกปิด
ที่อยู่: hospital.js:2748
// if (args.totalCurrentWithdraw > 8000) {
// throw responError('AMOUNT_OVER_LIMIT', ...)
// }ปัญหา: validation cap ยอดเบิก 8,000 บาทถูก comment ปิด — ปัจจุบัน client ส่งยอดมาเท่าไรระบบรับหมด
ความรุนแรง: สูง — พนักงานอาจเบิกเกินสิทธิ์
Bug #5 — Cron ไม่มี error handling
ที่อยู่: crons.js
schedule.scheduleJob('30 3 * * *', async () => {
updateDelegatesExpire();
})ปัญหา: ไม่มี try/catch — ถ้า throw จะเป็น unhandledRejection
ผลกระทบ: cron ล้มเหลวโดยไม่รู้ตัว
Bug #6 — transferChangeBenefitPersonCD ชื่อผิด
ปัญหา: ชื่อฟังก์ชันบอกว่า "BenefitPerson" แต่จริง ๆ ใช้สำหรับทั้ง benefit และ rate — ทำให้สับสน
Bug #7 — Race condition DocNo
ที่อยู่: hrmi.js:1424, hospital.js:2858
ปัญหา: การ generate docNo ไม่ได้ใช้ DB sequence หรือ lock — ถ้า 2 request เข้ามาพร้อมกันอาจได้เลขซ้ำ
3. Magic numbers ทั้งหมด
รวม magic numbers ที่ฝังใน controller ทั้งหมด — ควรย้ายไป config
Org codes
| Value | ความหมาย | ที่มา |
|---|---|---|
'02' | โรงพยาบาลจุฬา | hospital.js:107-120 |
'03' | สภากาชาด | hospital.js:107-120 |
'88' | บำนาญ | hospital.js:1266, 1388 |
'0734' | Plasma (skip validation) | hrmi.js:1302 |
'07' | Plasma branch prefix | hrmi.js:210 |
Leave validation
| Value | ความหมาย | ที่มา |
|---|---|---|
earlyLeaveDay | ล่วงหน้ากี่วัน | hrmi.js:1302 (from CD) |
backLeaveDay | ย้อนหลังได้กี่วัน | hrmi.js:1302 (from CD) |
maxLeaveDay | สูงสุดกี่วัน | hrmi.js:1283 (from CD) |
minLeaveDay | อย่างน้อยกี่วัน | hrmi.js:1283 (from CD) |
reqAttachDay | เกิน N วันต้องแนบใบรับรองแพทย์ | hrmi.js:1164 (from CD) |
Welfare
| Value | ความหมาย | ที่มา |
|---|---|---|
1 ปี (slip) | อายุใบเสร็จสูงสุด | hospital.js:2784 |
30 วัน (resignation) | ระยะเวลาเบิกหลังพ้นสภาพ | hospital.js:3158 |
20 ปี (child) | อายุบุตรสูงสุด | hospital.js:3126 |
8000 บาท | cap ยอดเบิก (ปิด) | hospital.js:2748 |
> 179, > 83 | threshold skip export | routes.js:803, 807 |
3.15576e+10 | ms/year คำนวณอายุ | hospital.js:3144 |
Payroll
| Value | ความหมาย | ที่มา |
|---|---|---|
3600 * 1000 ms | signed URL timeout 1 ชม. | routes.js:55 |
5000 ms | PDF retention | routes.js:82 |
+543 | แปลง พศ. | formatdate.js:37,42 |
qpdf ... 40 | RC4-40 encryption | routes.js:73 |
8 หลัก | password DDMMYYYY | controllers/slip/slip.js:41 |
Auth/Notification
| Value | ความหมาย | ที่มา |
|---|---|---|
| 5 นาที | captcha expire | auth.js:307 |
| 15 นาที | JWT vtrc-api | env |
| 30 วัน | public device token | notification.js:113 |
4. Dead code / disabled code
| รายการ | ที่อยู่ | สถานะ |
|---|---|---|
| Auto-cancel leave cron | crons.js:29-70 | comment ปิด |
| Welfare cap 8,000 บาท | hospital.js:2748 | comment ปิด |
hospital.js:1690-1738 | block ที่ comment ปิด | ควรลบ |
countApprover unused var | hrmi.js:1794 | ประกาศแล้วไม่ใช้ |
5. Dead files / ไฟล์ที่น่าสงสัย
| ไฟล์ | หมายเหตุ |
|---|---|
lib/createslip/index.js vs lib/genSlip/index.js | สองไฟล์ทำงานคล้ายกัน — อาจซ้ำซ้อน ต้องตรวจว่าอันไหนใช้จริง |
_bk*.js files | backup ไฟล์เก่า — ไม่ควรแก้ แต่ควรลบในอนาคต |
*_copy* files | copy ของไฟล์ — ลบได้ |
6. Coupling to cu-central-api — ตารางรวม
ทุก domain พึ่งพา cu-central-api ในระดับสูง — ถ้า CD ล่ม vtrc-api ใช้งานไม่ได้เกือบทั้งหมด
| Domain | จำนวน cross-service call | ฟังก์ชันสำคัญที่พึ่ง CD |
|---|---|---|
| Leave | 12+ | getLeaveDay, getWorkingDay, getApprover |
| Welfare | 2 | getMedical, employeeProfile |
| Payroll | 4+ | payrollSlipDetail, salaryCertDetail, pwdVerification |
| Fund | 4 | fetchEmployeeProvidentFund, hrBenefitPerson |
| Auth | 10+ | signin, signinbypass, token, signout, setupPasswordUser |
| Study | 2 | getEmpStudyLeaveRequest, personal |
| User | 2 | searchUser, employeeProfile |
| Notification | 0 | (vtrc-api ทำเอง) |
ยกเว้น notification และ publication — domain อื่นทั้งหมดขึ้นกับ CD โดยตรง
7. Code quality issues
1. ไฟล์ใหญ่เกินไป
| ไฟล์ | ขนาด | ปัญหา |
|---|---|---|
hrmi.js | ~2,600 บรรทัด | ยากอ่าน |
hospital.js | ~3,200 บรรทัด | ยากอ่าน + มี delegate logic ปน |
routes.js | ~68 KB | รวมทุก REST endpoint |
แนวทางแก้: แยกตาม concern (validation, payment, notification, delegate)
2. Inline templates
ไม่มี template engine — title/body inline ใน controller
แนวทางแก้: สร้าง notifications/templates.js รวมไว้ที่เดียว
3. ไม่มี constant file
Magic numbers กระจาย — ควรรวมเป็น constants.js
4. ไม่มี test
ไม่พบ unit test สำหรับ business logic — ทุก domain ตรวจด้วย manual test
8. Long-term improvement recommendations
ระดับสั้น (1-2 สัปดาห์)
- แก้ bug
hrmi.js:1266— เปลี่ยนเงื่อนไข===เป็น!== - เพิ่ม PDF retention เป็น 5 นาที (แทน 5 วินาที)
- เปิด welfare cap กลับมา (หลังยืนยันกับ HR)
- catch error ใน cron ทุก job
ระดับกลาง (1-2 เดือน)
- แยก
hospital.jsเป็น validation.js, payment.js, notification.js, delegate.js - ย้าย delegate ไป
delegate/delegate.js - สร้าง
constants.jsรวม magic numbers - เพิ่ม rate limit สำหรับ PDF generation
- เปลี่ยน PhantomJS เป็น Puppeteer
ระดับยาว (3-6 เดือน)
- เพิ่ม unit test สำหรับ workflow logic
- เพิ่ม transaction retry สำหรับ cross-service sync
- เพิ่ม cache สำหรับ lookup ที่ยิง CD บ่อย (getApprover, getLeaveType)
- สร้าง workflow abstraction — class/instance แทน procedural code
- เพิ่ม observability — log ทุก status transition พร้อม trace ID
9. ข้อแนะนำสำหรับวิศวกรใหม่
ถ้าเข้ามาแก้ business logic ของ domain ใด:
- อ่าน บท 11.1 ก่อน — เข้าใจแพทเทิร์นร่วม
- อ่านบทของ domain นั้น — เช่น บท 11.3 สำหรับ welfare
- เช็ค magic numbers ใน หัวข้อ 3 ของบทนี้
- เช็ค known bugs — อย่าซ้ำหรือแก้ผิดแบบเดิม
- ตรวจ cu-central-api dependency — logic สำคัญอยู่ที่ CD ไม่ใช่ vtrc-api
- ทดสอบใน UAT ก่อน deploy — เพราะไม่มี unit test คุ้มกัน
สรุป Volume 12
Volume นี้เจาะลึก business logic ของระบบ VTRC — state machine, workflow, calculation, validation ของทุก domain
ประเด็นสำคัญที่ควรจำ
- logic กระจาย 2 ระบบ — vtrc-api เป็น workflow engine, cu-central-api เป็น calculation + HR data
- ทุก domain ใช้แพทเทิร์นร่วม — parent + history, status enum, notification trigger
- ไม่มีการคำนวณใน vtrc-api — วันลา, สิทธิ์เบิก, ภาษี, เงินสมทบ ทั้งหมดอยู่ใน CD
- มี bug สำคัญที่
hrmi.js:1266— ควรแก้โดยด่วน - welfare cap ถูกปิด — พนักงานอาจเบิกเกินสิทธิ์
- ไฟล์ใหญ่ (
hrmi.js,hospital.js) — ยากต่อการ maintain - ไม่มี unit test — ต้องทดสอบ manual
Volume ถัดไป: Volume 13 · Infrastructure & Deployment (ยังไม่ได้เขียน)