12.8 · Delegate & Role
บทนี้เจาะลึก business logic ของการมอบอำนาจ (delegate) และการจัดการบทบาท (role) — รวมถึงการสลับ profile และ magic org codes ที่เกี่ยวข้อง
ภาพรวม
| ด้าน | ค่า |
|---|---|
| Delegate controller | lib/controllers/withdraw/hospital.js:2413+ (historical location) |
| Role controller | lib/controllers/role/role.js, roleAccess.js |
| User controller | lib/controllers/user/user.js, userDivision.js |
| Profile controller | lib/controllers/profile/profile.js |
| Repository | lib/repositories/{user,withdraw/delegate}.js |
| docType values | "LEAVE", "MEDICAL" |
| Magic org codes | '02', '03', '0734', '88' |
1. Delegate — มอบอำนาจอนุมัติแทน
ที่อยู่ (ผิดปกติ)
delegate logic อยู่ใน withdraw/hospital.js ไม่ใช่ delegate/ — เป็น historical accident
lib/controllers/withdraw/hospital.js:2413+ — ฟังก์ชัน delegate ทั้งหมดDelegate record
Delegate table:
├── delegateId (PK)
├── fromEmpCode (ผู้มอบ)
├── toEmpCode (ผู้รับมอบ)
├── docType ('LEAVE' หรือ 'MEDICAL')
├── startDate
├── endDate
├── isActive (1 = active, 0 = revoked)
└── createdBy, updatedBy, createdAt, updatedAt2. Delegate operations
createDelegateApprover
ที่อยู่: hospital.js:2413
// concept
const createDelegateApprover = async (args, context, empCode) => {
return await Delegate.create({
fromEmpCode: empCode,
toEmpCode: args.toEmpCode,
docType: args.docType, // "LEAVE" or "MEDICAL"
startDate: args.startDate,
endDate: args.endDate,
isActive: 1
})
// notify ผู้รับมอบ
}revokeDelegateApprover
ที่อยู่: hospital.js:2514
// concept — soft delete (set isActive = 0)
await Delegate.update({ isActive: 0 }, {
where: { delegateId: args.delegateId }
})fetchListDelegateApprover
ที่อยู่: hospital.js:2547
ดึงรายการ delegate ทั้งหมดที่ user นี้เป็นเจ้าของ (fromEmpCode)
fetchListDelegator
ที่อยู่: hospital.js:2614
ดึงรายชื่อคนที่สามารถมอบให้ได้ — กรองด้วย org:
// concept — กรองด้วย admin.orgUnitCode.substring(0, 4)
const delegators = await User.findAll({
where: {
orgUnitCode: { [Op.startsWith]: adminOrgUnitPrefix }
}
})3. Delegate lookup — ใช้ใน workflow
เวลามีเอกสารเข้ามา ระบบจะตรวจว่า approver ได้มอบอำนาจให้ใคร:
getDelegateToEmpCode
// concept — map approver → delegate
const delegates = await Delegate.getDelegateToEmpCode({
empCode: [approverEmpCode1, approverEmpCode2],
docType: 'LEAVE',
current: new Date()
})
// return [{ fromEmpCode, toEmpCode }]การใช้ใน leave workflow
// hrmi.js:1625, 1944, 2011 (concept)
// ดึง delegate ของ approver ทั้งหมด
const delegates = await Delegate.getDelegateToEmpCode({
empCode: approvers,
docType: 'LEAVE',
current: new Date()
})
// รวม delegate เข้ากับ approver
const effectiveApprovers = [...approvers, ...delegates.map(d => d.toEmpCode)]
// notify ทั้งหมด4. Delegate expire — cron
// concept — cron รันทุกวัน 03:30
schedule.scheduleJob('30 3 * * *', async () => {
await updateDelegatesExpire()
})ทุกวันที่ 03:30 — ปิด delegate ที่เลย endDate:
// concept
await Delegate.update(
{ isActive: 0 },
{
where: {
endDate: { [Op.lt]: new Date() },
isActive: 1
}
}
)5. Role assignment
changeRole
ที่อยู่: user.js:88
// concept
export const changeRole = async (token, refId, roleId) => {
return await changeRoleUserByToken(userId, refId, roleId)
}revokeRole
ที่อยู่: user.js:93
getListUserRole
ที่อยู่: user.js:267
ดึงรายการ user + role สำหรับ admin จัดการ
6. Role guard — @auth directive
Role check เกิดใน GraphQL directive ไม่ใช่ใน controller:
fetchProfile: Profile @auth(accessRole: ["USER"])
createWDHospital(...): ResposeStatus
@auth(accessRole: ["SUPER_ADMIN","ADMIN","STAFF"], accessMenu: ["hospitalHr"])ดูรายละเอียดใน Volume 10 บท 9.4
7. Profile switching — changeCurrentProfile
ที่อยู่: user.js:98
// concept
export const changeCurrentProfile = async (context, currentProfile) => {
// currentProfile = profileKey = "[empId]:[sourceDb]"
return await Session.update(
{ currentProfile },
{ where: { userId: context.userId, device: context.device } }
)
}Profile key format
[empId]:[sourceDb]
ตัวอย่าง:
12345:dbHRMI_RC_B_rep
67890:dbHRMI_CU_H_repการสลับ profile เปลี่ยน sourceDb — ทำให้เห็นข้อมูลของหน่วยงานอื่น (ถ้ามีสิทธิ์)
8. accordingProfile — ดึง profile ทั้งหมด
ที่อยู่: user.js:116
// concept — list profile ที่ user เข้าถึงได้
export const accordingProfile = async (token, device) => {
return await UserDivision.findAll({
where: { userId }
})
}9. Permission scope — magic org codes
หลาย workflow กรองข้อมูลตาม org code ของ user:
// hospital.js:107-120 (concept)
if (userOrgCode === '02' || userOrgCode === '03') {
// เห็นเฉพาะของ org ตัวเอง
condition.where.orgCode = userOrgCode
} else {
// เห็นทุกอย่างยกเว้น 02/03
condition.where.orgCode = { [Op.notIn]: ['02', '03'] }
}ตาราง org codes พิเศษ
| Org code | ความหมาย | ใช้ใน |
|---|---|---|
'02' | โรงพยาบาลจุฬา (H) | permission scope, slip |
'03' | สภากาชาด (C) | permission scope, slip |
'0734' | สังกัด plasma พิเศษ | skip leave validation |
'07' | Plasma branch prefix | dropdown พิเศษ |
'88' | บำนาญ | empType = "บำนาญ" |
10. searchUser — ค้นผ่าน cu-central-api
ที่อยู่: user.js:197
// concept
export const searchUser = async (args, context) => {
// 1. ค้นใน cu-central-api
const cdResults = await searchUserCD({
empCode, firstName, lastName, identityCard
})
// 2. match role ใน vtrc
const empCodes = cdResults.map(r => r.empCode)
const users = await User.findAll({ where: { empCode: empCodes } })
// 3. merge ข้อมูล
return cdResults.map(cd => ({
...cd,
role: users.find(u => u.empCode === cd.empCode)?.role
}))
}11. findEmpDetailByEmpCode — helper
ที่อยู่: user.js:305
// concept — ใช้ทั่วระบบ (leave, hospital, fund, study)
export const findEmpDetailByEmpCode = async (empCode) => {
return await employeeDetailCD({ empCode })
}12. Cron — updateDelegatesExpire
jobs.push(schedule.scheduleJob('30 3 * * *', async () => {
updateDelegatesExpire();
}))- รันทุกวันเวลา 03:30
- ปิด delegate ที่เลย endDate
13. Magic numbers / hardcoded values
| Value | ความหมาย | ที่มา |
|---|---|---|
"LEAVE", "MEDICAL" | docType | hospital.js |
'02', '03', '88', '0734' | org codes | หลายจุด |
orgUnitCode.substring(0, 4) | prefix 4 ตัว | hospital.js:2614 |
orgUnitCode.substring(0, 2) | prefix 2 ตัว (plasma) | hrmi.js:210 |
14. Cross-service calls
| vtrc-api function | cu-central-api | วัตถุประสงค์ |
|---|---|---|
searchUserCD | searchUser | ค้น user |
employeeDetailCD | employeeProfile | ดึง profile |
15. ข้อระวังทั้งหมด
1. Delegate logic อยู่ผิดที่
อยู่ใน withdraw/hospital.js ทำให้คน maintain ไม่รู้ว่ามี — ควรย้ายไป delegate/delegate.js
2. Magic org codes กระจาย
'02', '03', '88', '0734' ฝังในหลายไฟล์ — ถ้า org code เปลี่ยน ต้องแก้ทุกที่
3. Profile switching เปลี่ยน sourceDb
ผู้ใช้ที่มีหลาย profile (หลายหน่วยงาน) สามารถสลับ — ทำให้เห็นข้อมูลของอีกหน่วยงาน
4. Delegate overlap
ถ้ามี delegate ทับซ้อน (วันที่ทับกัน) — lookup อาจ return หลายรายการ
5. ไม่มี audit log สำหรับ delegate
ไม่ได้เก็บประวัติว่าใครมอบให้ใครเมื่อไร (นอกจาก createdBy)
16. Summary — delegate & role domain
| ด้าน | ทำใน vtrc-api | ส่งต่อ cu-central-api |
|---|---|---|
| Delegate CRUD | ใช่ | — |
| Delegate lookup | ใช่ | — |
| Role assignment | ใช่ | — |
| Role guard | ใช่ (directive) | — |
| Profile switching | ใช่ | — |
| Permission scope (org) | ใช่ | — |
| Search user | — | ใช่ |
| Employee detail | — | ใช่ |