Skip to content

12.5 · Provident Fund (กองทุนสงเคราะห์)

บทนี้เจาะลึก business logic ของกองทุนสงเคราะห์ — member lifecycle, workflow types (BENEFIT/RATE/APPLICATION), config window สำหรับการเปลี่ยนอัตราสมทบ และการ sync ไป cu-central-api


ภาพรวม

ด้านค่า
ไฟล์ controllerlib/controllers/fund/fund.js (~930 บรรทัด)
PDF reportlib/controllers/fund/fund.report.js
Repositorylib/repositories/fund/{fund,fundRequest,fundRequestHistory,fundRateChange,fundBenefitPerson,fundApplication,fundDocument}.js
Forward controllercentralize/controllers/fund.js
Workflow types3 ประเภท (BENEFIT, RATE, APPLICATION)
StatusDRAFT → PENDING → SUCCESS
CronupdateSuccessFundChangeRateByJob (ทุกนาที ถ้า APP_NO=1)

1. Member lifecycle

32:37:vtrc-api/api/src/lib/controllers/fund/fund.js
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 ประเภท

24:28:vtrc-api/api/src/lib/controllers/fund/fund.js
const CONST_FUND_TYPE = {
    BENEFIT: "BENEFIT",          // เปลี่ยนผู้รับผลประโยชน์
    RATE: "RATE",                // เปลี่ยนอัตราสมทบ
    APPLICATION: "APPLICATION"   // สมัครเข้ากองทุน
}

Workflow ร่วม — createFundRequestProcess

184:vtrc-api/api/src/lib/controllers/fund/fund.js
// 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

123:127:vtrc-api/api/src/lib/controllers/fund/fund.js
// concept — MasterConfigs table
const configs = await MasterConfigs.findAll({
  where: { isActive: 1 }
})
// ProvidentfundChangeDateFrom
// ProvidentfundChangeDateTo
// ChangeMin, ChangeMax
// ProvidentFundEffectiveDate

setProvPercentBtnActive

js
// concept — ตรวจว่าเปิดปุ่มเปลี่ยนอัตราได้ไหม
const setProvPercentBtnActive = (changeDateFrom, changeDateTo, memberStatus) => {
  const today = new Date()
  return (
    memberStatus === 'Member' &&
    today >= changeDateFrom &&
    today <= changeDateTo
  )
}

Cron — updateSuccessFundChangeRateByJob

400:430:vtrc-api/api/src/lib/controllers/fund/fund.js
// 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

436:vtrc-api/api/src/lib/controllers/fund/fund.js
// 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 (สมัครเข้ากองทุน)

464:vtrc-api/api/src/lib/controllers/fund/fund.js
// concept
const createFundApplication = async (args) => {
  return await FundApplication.create({
    empCode: args.empCode,
    email: args.email,
    identityCard: args.identityCard,
    requestDate: new Date()
  })
}

Export ไป Tisco (ธนาคาร)

81:vtrc-api/api/src/lib/controllers/excel/excel.js
// 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

14:19:vtrc-api/api/src/crons.js
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

194:vtrc-api/api/src/lib/controllers/fund/fund.js
// 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 functioncu-central-api queryวัตถุประสงค์
getEmployeeProvidentFundDetailfetchEmployeeProvidentFundข้อมูลสมาชิก + ยอดสมทบ
getEmployeeSalaryfetchEmployeeSalaryเงินเดือน (สำหรับคำนวณสมทบ)
transferChangeBenefitPersonCDhrBenefitPersonCDsync benefit/rate change
getPersonalCDpersonalprofile พื้นฐาน

10. Magic numbers / hardcoded values

Valueความหมายที่มา
'BENEFIT', 'RATE', 'APPLICATION'workflow typesfund.js:24-28
'DRAFT', 'PENDING', 'SUCCESS'statusทั่วไป
'Tisco'ชื่อธนาคาร (hardcoded ใน filename)excel.js:104
APP_NO === 1cron gatecrons.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