12.5 · Provident Fund (กองทุนสงเคราะห์)
บทนี้เจาะลึก business logic ของกองทุนสงเคราะห์ — member lifecycle, workflow types (BENEFIT/RATE/APPLICATION), config window สำหรับการเปลี่ยนอัตราสมทบ และการ sync ไป cu-central-api
ภาพรวม
| ด้าน | ค่า |
|---|---|
| ไฟล์ controller | lib/controllers/fund/fund.js (~930 บรรทัด) |
| PDF report | lib/controllers/fund/fund.report.js |
| Repository | lib/repositories/fund/{fund,fundRequest,fundRequestHistory,fundRateChange,fundBenefitPerson,fundApplication,fundDocument}.js |
| Forward controller | centralize/controllers/fund.js |
| Workflow types | 3 ประเภท (BENEFIT, RATE, APPLICATION) |
| Status | DRAFT → PENDING → SUCCESS |
| Cron | updateSuccessFundChangeRateByJob (ทุกนาที ถ้า APP_NO=1) |
1. Member lifecycle
const MEMBER_STATUS = {
Member: "สมาชิกปัจจุบัน",
NotMember: "ยังไม่เป็นสมาชิก",
EndProvidentFund: "ออกจากกองทุนไม่ออกจากงาน",
EndMember: "สิ้นสุดสมาชิกภาพ"
}Transition
[NotMember] ──apply──► [Member]
│
├──changeRate──► (ยังเป็น Member, เปลี่ยนอัตรา)
│
├──endProvidentFund──► [EndProvidentFund]
│ │
│ └──reJoin──► [Member]
│
└──endMember──► [EndMember] (สิ้นสุดถาวร)ข้อมูลสมาชิกทั้งหมดมาจาก cu-central-api (getEmployeeProvidentFundDetail)
2. Workflow types — 3 ประเภท
const CONST_FUND_TYPE = {
BENEFIT: "BENEFIT", // เปลี่ยนผู้รับผลประโยชน์
RATE: "RATE", // เปลี่ยนอัตราสมทบ
APPLICATION: "APPLICATION" // สมัครเข้ากองทุน
}Workflow ร่วม — createFundRequestProcess
// concept — wrapper สร้าง row ใน FundRequests
const createFundRequestProcess = async (type, actionBy, status, ...) => {
return await FundRequest.create({
requestType: type,
status: status, // DRAFT, PENDING, SUCCESS
actionBy: actionBy,
...
})
}ทุก workflow เริ่มต้นที่ row ใน FundRequests table — เพื่อ track สถานะ
3. Rate change workflow
Flow
1. พนักงานเปิดหน้า "เปลี่ยนอัตราสมทบ"
↓
2. ตรวจ config window:
- ProvidentfundChangeDateFrom <= today <= ProvidentfundChangeDateTo?
- ถ้าไม่ → ปุ่ม "เปลี่ยน" disabled
↓
3. พนักงานกรอกอัตราใหม่ (เปอร์เซ็นต์) — ต้องอยู่ใน [ChangeMin, ChangeMax]
↓
4. createFundRateChange → INSERT FundRequest (status=DRAFT)
→ INSERT FundRateChange
↓
5. submit → UPDATE FundRequest (status=PENDING)
↓
6. รอถึง ProvidentFundEffectiveDate → cron flip PENDING → SUCCESS
↓
7. SUCCESS → sync อัตราใหม่ไป cu-central-api (transferChangeBenefitPersonCD — ผิดชื่อจริง ๆ แต่ใช้ทั้งสองกรณี)Config window
// concept — MasterConfigs table
const configs = await MasterConfigs.findAll({
where: { isActive: 1 }
})
// ProvidentfundChangeDateFrom
// ProvidentfundChangeDateTo
// ChangeMin, ChangeMax
// ProvidentFundEffectiveDatesetProvPercentBtnActive
// concept — ตรวจว่าเปิดปุ่มเปลี่ยนอัตราได้ไหม
const setProvPercentBtnActive = (changeDateFrom, changeDateTo, memberStatus) => {
const today = new Date()
return (
memberStatus === 'Member' &&
today >= changeDateFrom &&
today <= changeDateTo
)
}Cron — updateSuccessFundChangeRateByJob
// concept — cron รันทุกนาที (ถ้า APP_NO=1)
const updateSuccessFundChangeRateByJob = async () => {
const pendingRequests = await FundRequest.findAll({
where: {
requestType: 'RATE',
status: 'PENDING'
}
})
for (const req of pendingRequests) {
if (today >= ProvidentFundEffectiveDate) {
// flip PENDING → SUCCESS
await req.update({ status: 'SUCCESS' })
// sync ไป cu-central-api
await transferChangeBenefitPersonCD(...)
}
}
}ที่มา: crons.js:14-19
Force sync — updateSuccessFundChangeRateByForce
// concept — HR/admin บังคับ sync (ไม่รอ cron)
const updateSuccessFundChangeRateByForce = async (requestId) => {
await FundRequest.update({ status: 'SUCCESS' }, { where: { id: requestId } })
await transferChangeBenefitPersonCD(...)
}4. Benefit person workflow
Flow
1. พนักงานสร้างคำขอเปลี่ยนผู้รับผลประโยชน์
↓
2. createFundBenefitPerson(args):
- args.benefitPersons = array (หลายคนต่อ 1 request)
- INSERT FundRequest (status=DRAFT/PENDING)
- INSERT FundBenefitPerson สำหรับแต่ละคน
↓
3. HR approve → SUCCESS
↓
4. SUCCESS → transferChangeBenefitPersonCD:
วนลูปส่งแต่ละ benefitPerson ไป cu-central-api:
await hrBenefitPersonCD(tokenCD, row)ที่มา: fund.js:239-293
Benefit person fields
listNo — ลำดับที่
benefitsName — ชื่อผู้รับผลประโยชน์
benefitsIdCard — เลขบัตรประชาชนผู้รับ
benefitsRate — อัตราส่วน (%)
address — ที่อยู่
providentFundRateId — FK
relationShip — ความสัมพันธ์ (บิดา, มารดา, บุตร, ...)5. Application workflow (สมัครเข้ากองทุน)
// concept
const createFundApplication = async (args) => {
return await FundApplication.create({
empCode: args.empCode,
email: args.email,
identityCard: args.identityCard,
requestDate: new Date()
})
}Export ไป Tisco (ธนาคาร)
// concept — createExportFundApplicationFile
const createExportFundApplicationFile = async (condition) => {
const applications = await FundApplication.findAll(condition)
// สร้าง Excel
const buffer = await buildExcel(applications)
return { buffer, filename: 'รายชื่อขอเข้าใช้งาน_Tisco.xlsx' }
}6. Status enum
| Status | ความหมาย |
|---|---|
DRAFT | บันทึกชั่วคราว |
PENDING | ส่งเรียบร้อย รอดำเนินการ |
SUCCESS | สำเร็จ (sync ไป cu-central-api แล้ว) |
7. Cron — updateSuccessFundChangeRateByJob
if (+APP_NO === 1) {
jobs.push(schedule.scheduleJob('* 1 * * * *', async () => {
await updateSuccessFundChangeRateByJob()
}))
}- รันทุกนาที (ที่วินาทีที่ 1)
- เฉพาะ APP_NO=1 — ป้องกัน multi-instance ทำงานซ้ำ
ข้อระวัง
- ถ้ามีหลาย instance (APP_NO != 1) — cron ไม่ทำงานเลย
- ต้องตั้ง env
APP_NO=1ที่ instance เดียวเท่านั้น
8. SourceDB sharding
// concept — parse sourceDb จาก profileKey
const sourceDb = currentSession.currentProfile.split(":")[1]profileKey format: [empId]:[sourceDb] — ใช้ sourceDb เป็น shard ใน cu-central-api
9. Cross-service calls
| vtrc-api function | cu-central-api query | วัตถุประสงค์ |
|---|---|---|
getEmployeeProvidentFundDetail | fetchEmployeeProvidentFund | ข้อมูลสมาชิก + ยอดสมทบ |
getEmployeeSalary | fetchEmployeeSalary | เงินเดือน (สำหรับคำนวณสมทบ) |
transferChangeBenefitPersonCD | hrBenefitPersonCD | sync benefit/rate change |
getPersonalCD | personal | profile พื้นฐาน |
10. Magic numbers / hardcoded values
| Value | ความหมาย | ที่มา |
|---|---|---|
'BENEFIT', 'RATE', 'APPLICATION' | workflow types | fund.js:24-28 |
'DRAFT', 'PENDING', 'SUCCESS' | status | ทั่วไป |
'Tisco' | ชื่อธนาคาร (hardcoded ใน filename) | excel.js:104 |
APP_NO === 1 | cron gate | crons.js:14 |
11. ข้อระวังทั้งหมด
1. Cron gate APP_NO
ถ้า deploy หลาย instance โดยไม่ได้ตั้ง APP_NO ให้ต่างกัน — cron อาจทำงานซ้ำหรือไม่ทำงานเลย
2. Config window แบบ hard-coded dates
ถ้า HR ลืมอัปเดต MasterConfigs ตามปี → พนักงานเปลี่ยนอัตราไม่ได้
3. transferChangeBenefitPersonCD ชื่อผิด
ชื่อฟังก์ชันบอกว่า "BenefitPerson" แต่จริง ๆ ใช้สำหรับทั้ง benefit และ rate — ทำให้สับสน
4. ไม่มี rollback ถ้า CD sync fail
ถ้า transferChangeBenefitPersonCD ล้มเหลว — status ยังเป็น SUCCESS อยู่ แต่ CD ไม่ได้ข้อมูล
5. Cron ไม่มี lock
ถ้า cron รันนานเกิน 1 นาที — instance ถัดไปอาจเริ่มซ้ำ
12. Summary — fund domain
| ด้าน | ทำใน vtrc-api | ส่งต่อ cu-central-api |
|---|---|---|
| ข้อมูลสมาชิก | — | ใช่ (ทั้งหมด) |
| ยอดสมทบปัจจุบัน | — | ใช่ |
| Workflow status | ใช่ | — |
| Config window | ใช่ (MasterConfigs) | — |
| Sync rate/benefit change | — | ใช่ |
| Export Tisco Excel | ใช่ | — |
| Cron flip PENDING → SUCCESS | ใช่ | — |
ขั้นตอนถัดไป
ไป บท 11.6 Study Leave →