Skip to content

12.8 · Delegate & Role

บทนี้เจาะลึก business logic ของการมอบอำนาจ (delegate) และการจัดการบทบาท (role) — รวมถึงการสลับ profile และ magic org codes ที่เกี่ยวข้อง


ภาพรวม

ด้านค่า
Delegate controllerlib/controllers/withdraw/hospital.js:2413+ (historical location)
Role controllerlib/controllers/role/role.js, roleAccess.js
User controllerlib/controllers/user/user.js, userDivision.js
Profile controllerlib/controllers/profile/profile.js
Repositorylib/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, updatedAt

2. Delegate operations

createDelegateApprover

ที่อยู่: hospital.js:2413

js
// 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

js
// 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:

js
// concept — กรองด้วย admin.orgUnitCode.substring(0, 4)
const delegators = await User.findAll({
  where: {
    orgUnitCode: { [Op.startsWith]: adminOrgUnitPrefix }
  }
})

3. Delegate lookup — ใช้ใน workflow

เวลามีเอกสารเข้ามา ระบบจะตรวจว่า approver ได้มอบอำนาจให้ใคร:

getDelegateToEmpCode

js
// concept — map approver → delegate
const delegates = await Delegate.getDelegateToEmpCode({
  empCode: [approverEmpCode1, approverEmpCode2],
  docType: 'LEAVE',
  current: new Date()
})
// return [{ fromEmpCode, toEmpCode }]

การใช้ใน leave workflow

js
// 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

22:25:vtrc-api/api/src/crons.js
// concept — cron รันทุกวัน 03:30
schedule.scheduleJob('30 3 * * *', async () => {
    await updateDelegatesExpire()
})

ทุกวันที่ 03:30 — ปิด delegate ที่เลย endDate:

js
// concept
await Delegate.update(
  { isActive: 0 },
  { 
    where: { 
      endDate: { [Op.lt]: new Date() },
      isActive: 1
    } 
  }
)

5. Role assignment

changeRole

ที่อยู่: user.js:88

js
// 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:

graphql
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

js
// 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

js
// 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:

js
// 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 prefixdropdown พิเศษ
'88'บำนาญempType = "บำนาญ"

10. searchUser — ค้นผ่าน cu-central-api

ที่อยู่: user.js:197

js
// 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

js
// concept — ใช้ทั่วระบบ (leave, hospital, fund, study)
export const findEmpDetailByEmpCode = async (empCode) => {
  return await employeeDetailCD({ empCode })
}

12. Cron — updateDelegatesExpire

30:33:vtrc-api/api/src/crons.js
jobs.push(schedule.scheduleJob('30 3 * * *', async () => {
    updateDelegatesExpire();
}))
  • รันทุกวันเวลา 03:30
  • ปิด delegate ที่เลย endDate

13. Magic numbers / hardcoded values

Valueความหมายที่มา
"LEAVE", "MEDICAL"docTypehospital.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 functioncu-central-apiวัตถุประสงค์
searchUserCDsearchUserค้น user
employeeDetailCDemployeeProfileดึง 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ใช่

ขั้นตอนถัดไป

ไป บท 11.9 Notification system