12.9 · Notification system
บทนี้เจาะลึก business logic ของระบบการแจ้งเตือน — event ที่ trigger, channel ที่ใช้, audience selection, และ template
ภาพรวม
| ด้าน | ค่า |
|---|---|
| ไฟล์ controller | lib/controllers/notification/notification.js (~700 บรรทัด) |
| Queue | lib/controllers/notification/queneNotification.js |
| Request | lib/controllers/notification/notificationRequest.js |
| Creator | lib/controllers/createNotification/createNotification.js |
| Firebase | lib/firebase/firebasePushNotification.js |
| Channels | 3 (in-app, push FCM, public device) |
| Public device token expire | 30 วัน |
| Notification types | 3 (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 FCMsendNotificationByThirdParty — individual
ที่อยู่: notification.js:561
ใช้สำหรับ workflow notification — target เป็นบุคคลเฉพาะ (เช่น approver, เจ้าของเอกสาร)
sendNotificationByThirdParty({
title, body,
redirect_path,
notificationGroup,
users: [{ empCode }] // ผู้รับเฉพาะเจาะจง
}, context, "notification")
↓
วนลูปส่งให้แต่ละ empCode2. Event → Notify mapping
แต่ละ workflow domain trigger notification ในจังหวะที่กำหนด:
| Domain | Action | Notify ใคร | Title (ตัวอย่าง) |
|---|---|---|---|
| Leave | submit → PENDING lv.1 | approvers lv.1 | "รออนุมัติการลา" |
| Leave | approve (next level) | approvers ระดับถัดไป | "รออนุมัติการลา" |
| Leave | approve final | เจ้าของเอกสาร | "อนุมัติการลาแล้ว" |
| Leave | reject | เจ้าของเอกสาร | "ไม่อนุมัติการลา" |
| Welfare | submit → PENDING | approvers | "รออนุมัติการเบิก" |
| Welfare | need more info | เจ้าของ | "เอกสารไม่ครบ" |
| Welfare | reject | เจ้าของ | "ไม่อนุมัติการเบิก" |
| Welfare | paid | เจ้าของ | "รอการโอนเงิน" |
| Welfare | paid cash | เจ้าของ | "รอรับเงินสด" |
| Welfare | success | เจ้าของ | "โอนเงินสำเร็จ" |
| Welfare | failed | เจ้าของ | "โอนเงินไม่สำเร็จ" |
| Delegate | create | ผู้รับมอบ | "คุณได้รับมอบหมาย..." |
| News | publish | ผู้ใช้ที่เกี่ยวข้อง | (ตามหัวขข่าว) |
ที่มา: hrmi.js:1628-1638, 2027-2052, hospital.js:1566-1649
3. Audience selection — statusContent
สำหรับ broadcast (sendNotification):
// 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
// 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
// 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
// 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
// concept
type: "notification" // default
type: "news" // สำหรับ postId
type: "course" // สำหรับ courseId6. Template — inline ใน controller
ไม่มี template engine — title/body inline ใน controller แต่ละจุด:
// ตัวอย่างจาก 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
const MEDICAL_NOTI_TYPE = {
detail: "MEDICAL_DETAIL",
approving: "MEDICAL_APPROVING"
}ใช้แยก notification สวัสดิการเป็น 2 กลุ่มย่อย
8. ไม่ throw error ถ้า user ไม่พบ
// concept
try {
await sendNotificationByThirdParty(...)
} catch (err) {
// log only — ไม่ throw
console.error('Notification failed:', err)
}workflow ไม่ควรล้มเหลวเพราะ notification ไม่สำเร็จ — ดังนั้นจึง catch และ log เท่านั้น
9. Read tracking
สำหรับ News/Post:
// postRead.js (concept)
await PostRead.create({
userId,
postId,
readAt: new Date()
})ใช้ track ว่า user อ่านโพสต์ไหนแล้ว
สำหรับ Notification — ใช้ isRead flag ใน Notifications table
10. Notification settings
ผู้ใช้สามารถเปิด/ปิด notification ได้:
// 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 expire | notification.js:113 |
requireNotification: 1 | toggle default | — |
MEDICAL_DETAIL, MEDICAL_APPROVING | notification subtype | hospital.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 | ใช่ | — |
| Template | inline (ไม่มี engine) | — |
| SMS/Email | — | ใช่ (CD) |
| Notification trigger (workflow) | ใช่ | — |
| Read tracking | ใช่ | — |