Skip to content

12.9 · Notification system

บทนี้เจาะลึก business logic ของระบบการแจ้งเตือน — event ที่ trigger, channel ที่ใช้, audience selection, และ template


ภาพรวม

ด้านค่า
ไฟล์ controllerlib/controllers/notification/notification.js (~700 บรรทัด)
Queuelib/controllers/notification/queneNotification.js
Requestlib/controllers/notification/notificationRequest.js
Creatorlib/controllers/createNotification/createNotification.js
Firebaselib/firebase/firebasePushNotification.js
Channels3 (in-app, push FCM, public device)
Public device token expire30 วัน
Notification types3 (notification, news, course)

1. 2 โหมดการส่ง

sendNotification — broadcast

ที่อยู่: notification.js:146

ใช้สำหรับ broadcast ไป post/document/course — target เป็นผู้ใช้ทั้งหมดหรือกลุ่มใหญ่

sendNotification({
  title, body,
  postId / documentId / courseId,
  statusContent  // PUBLIC, INTERNAL, ALLINTERNAL
})

เลือก audience ตาม statusContent

ส่ง in-app + push และ push FCM

sendNotificationByThirdParty — individual

ที่อยู่: notification.js:561

ใช้สำหรับ workflow notification — target เป็นบุคคลเฉพาะ (เช่น approver, เจ้าของเอกสาร)

sendNotificationByThirdParty({
  title, body,
  redirect_path,
  notificationGroup,
  users: [{ empCode }]   // ผู้รับเฉพาะเจาะจง
}, context, "notification")

วนลูปส่งให้แต่ละ empCode

2. Event → Notify mapping

แต่ละ workflow domain trigger notification ในจังหวะที่กำหนด:

DomainActionNotify ใครTitle (ตัวอย่าง)
Leavesubmit → PENDING lv.1approvers lv.1"รออนุมัติการลา"
Leaveapprove (next level)approvers ระดับถัดไป"รออนุมัติการลา"
Leaveapprove finalเจ้าของเอกสาร"อนุมัติการลาแล้ว"
Leaverejectเจ้าของเอกสาร"ไม่อนุมัติการลา"
Welfaresubmit → PENDINGapprovers"รออนุมัติการเบิก"
Welfareneed more infoเจ้าของ"เอกสารไม่ครบ"
Welfarerejectเจ้าของ"ไม่อนุมัติการเบิก"
Welfarepaidเจ้าของ"รอการโอนเงิน"
Welfarepaid cashเจ้าของ"รอรับเงินสด"
Welfaresuccessเจ้าของ"โอนเงินสำเร็จ"
Welfarefailedเจ้าของ"โอนเงินไม่สำเร็จ"
Delegatecreateผู้รับมอบ"คุณได้รับมอบหมาย..."
Newspublishผู้ใช้ที่เกี่ยวข้อง(ตามหัวขข่าว)

ที่มา: hrmi.js:1628-1638, 2027-2052, hospital.js:1566-1649


3. Audience selection — statusContent

สำหรับ broadcast (sendNotification):

317:361:vtrc-api/api/src/lib/controllers/notification/notification.js
// concept
if (statusContent === 'PUBLIC') {
  // ส่งทุก public device + active session user
  recipients = await getAllPublicDevices() + getAllActiveSessions()
} else if (statusContent === 'INTERNAL') {
  // ส่งเฉพาะ division members
  recipients = await getDivisionMembers(divisionId)
} else if (statusContent === 'ALLINTERNAL') {
  // ส่งทุก active session user
  recipients = await getAllActiveSessions()
}

4. Channels — 3 ช่องทาง

In-app — createNotification

js
// notification.js:146 (concept)
await createNotification({
  notificationId: uuid,
  title: args.title,
  body: args.body,
  redirect_path: args.redirect_path,
  notificationGroup: args.notificationGroup,
  userId: targetUserId,
  empCode: targetEmpCode,
  isRead: 0,
  createdAt: new Date()
})

เก็บใน Notifications table — frontend ดึงไปแสดงในหน้าแจ้งเตือน

Push (FCM) — firebasePushNotification

js
// notification.js:217 (concept)
await firebasePushNotification({
  title: args.title,
  body: args.body,
  data: { redirect_path: args.redirect_path }
}, userDeviceTokens)

ใช้ Firebase Cloud Messaging (FCM) — ส่งไป device token ที่ลงทะเบียนไว้

Public device — NotificationPublicDevices

js
// notification.js:113 (concept)
await NotificationPublicDevices.create({
  deviceToken: args.deviceToken,
  expireDate: moment().add(30, 'days').toDate()   // expire 30 วัน
})

สำหรับ user ที่ยังไม่ login — อุปกรณ์ที่ลงทะเบียนรับ notification แบบ anonymous


5. Notification types

js
// concept
type: "notification"   // default
type: "news"           // สำหรับ postId
type: "course"         // สำหรับ courseId

6. Template — inline ใน controller

ไม่มี template engine — title/body inline ใน controller แต่ละจุด:

js
// ตัวอย่างจาก leave (concept)
await sendNotificationByThirdParty({
  title: "รออนุมัติการลา",
  body: `เอกสารการลาเลขที่ ${docNo} รอการอนุมัติ...`,
  redirect_path: `Leave/Confirm/${docId}`,
  notificationGroup: "LEAVE",
  users: approvers.map(empCode => ({ empCode }))
}, context, "notification")

Notification group

กลุ่มหมวดหมู่ใหญ่:

Groupใช้กับ
LEAVEการลา
WELFARE / MEDICAL_DETAIL, MEDICAL_APPROVINGสวัสดิการ
FUNDกองทุน
STUDYการลาศึกษา
NEWSข่าวสาร

7. MEDICAL_NOTI_TYPE

31:34:vtrc-api/api/src/lib/controllers/withdraw/hospital.js
const MEDICAL_NOTI_TYPE = { 
  detail: "MEDICAL_DETAIL", 
  approving: "MEDICAL_APPROVING" 
}

ใช้แยก notification สวัสดิการเป็น 2 กลุ่มย่อย


8. ไม่ throw error ถ้า user ไม่พบ

617:624:vtrc-api/api/src/lib/controllers/notification/notification.js
// concept
try {
  await sendNotificationByThirdParty(...)
} catch (err) {
  // log only — ไม่ throw
  console.error('Notification failed:', err)
}

workflow ไม่ควรล้มเหลวเพราะ notification ไม่สำเร็จ — ดังนั้นจึง catch และ log เท่านั้น


9. Read tracking

สำหรับ News/Post:

js
// postRead.js (concept)
await PostRead.create({
  userId,
  postId,
  readAt: new Date()
})

ใช้ track ว่า user อ่านโพสต์ไหนแล้ว

สำหรับ Notification — ใช้ isRead flag ใน Notifications table


10. Notification settings

ผู้ใช้สามารถเปิด/ปิด notification ได้:

js
// concept
NotificationUserSettings table:
├── userId
├── requireNotification (1 = เปิด, 0 = ปิด)
└── notificationGroup (เช่น LEAVE, WELFARE)

11. SMS / Email — ไม่พบใน vtrc-api

SMS และ email ส่งผ่าน cu-central-api เท่านั้น (เช่น sendVerifyCodeByCD, requestOtpCD)

vtrc-api ไม่ได้ส่ง SMS/email โดยตรง


12. Magic numbers / hardcoded values

Valueความหมายที่มา
30 วันpublic device token expirenotification.js:113
requireNotification: 1toggle default
MEDICAL_DETAIL, MEDICAL_APPROVINGnotification subtypehospital.js:31-34
'notification', 'news', 'course'type

13. ข้อระวังทั้งหมด

1. ไม่มี template engine

title/body กระจายใน controller — ถ้าต้องเปลี่ยนข้อความ ต้องแก้ code ทุกจุด

2. ไม่มี retry

ถ้า FCM ส่งไม่สำเร็จ — ไม่มี retry mechanism (นอกจาก log)

3. Public device expire 30 วัน

ถ้า user ไม่ได้ login 30 วัน — device token หมดอายุ ต้องขอใหม่

4. Notification group string hard-coded

'LEAVE', 'WELFARE', 'FUND' ฯลฯ — ถ้าเพิ่ม domain ต้อง sync กับ frontend

5. FCM key ใน config

Firebase server key อยู่ใน config.js — เป็น secret ที่ควรย้ายไป env

6. ไม่มี rate limit

ถ้ามี notification พร้อมกันเยอะ — FCM อาจ throttle หรือ fail


14. Summary — notification domain

ด้านทำใน vtrc-apiส่งต่อ cu-central-api
In-app notificationใช่ (DB row)
Push notification (FCM)ใช่
Public deviceใช่
Audience selectionใช่
Templateinline (ไม่มี engine)
SMS/Emailใช่ (CD)
Notification trigger (workflow)ใช่
Read trackingใช่

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

ไป บท 11.10 Publication + Conicle + Cron