Skip to content

1 · Repository map — benefit-api

บทนี้เป็นแผนที่ระดับสูงของ repository benefit-api/ ครอบคลุม tech stack โครงสร้างโฟลเดอร์ ตัวเลขสถิติของไฟล์ entry-point trace และ auth strategy ที่ใช้ทั่ว repo เพื่อให้บทถัดไป (core pipeline, persistence, integrations) สามารถอ้างชื่อ module/entity/ guard ได้โดยไม่ต้องอธิบายซ้ำ

Tech stack

benefit-api เป็น NestJS 9 service ที่ใช้ TypeORM เป็น ORM ตัวเดียว (ไม่มี Mongoose ในกระบวนการผลิตจริง — ระบบทุก connection ที่พบใน benefit-api/src/app.module.ts (branch: prod):40-57 เป็น TypeORM connection ทั้งหมด) ภาษาคือ TypeScript 4.7 ทำงานบน Node.js 20 (ดู benefit-api/Dockerfile (branch: prod):1) และเปิด 3 database connection พร้อมกัน ได้แก่ main (schema benefit บน SQL Server), vtrc (schema ของ legacy core ซึ่งชี้ไปยัง MariaDB หรือ MSSQL ตาม env), และ hrmi (SQL Server เก็บ HR master data)

package ที่เกี่ยวข้องกับการทำงานหลักใน benefit-api/package.json (branch: prod):26-61 คือกลุ่ม NestJS core (@nestjs/common, @nestjs/core, @nestjs/platform-express ที่ v9), กลุ่ม config (@nestjs/config, @nestjs/swagger 6.1, @nestjs/terminus 9.2), กลุ่ม ORM/database (@nestjs/typeorm 9, typeorm 0.3, mssql 9, mysql 2), กลุ่ม auth (@nestjs/passport 9, passport 0.6, passport-jwt 4, passport-local 1), กลุ่ม scheduler (@nestjs/schedule 2.2), กลุ่ม HTTP outbound (@nestjs/axios 2, axios 1.4), และกลุ่ม utility เฉพาะงาน (puppeteer 19 สำหรับ render PDF, exceljs 4 สำหรับ export Excel, ejs 3 สำหรับ template, ssh2-sftp-client 10 และ sftp-upload 1 สำหรับส่งไฟล์ไป FMIS, adm-zip, iconv-lite, moment, uuid)

ชื่อ package จริงใน benefit-api/package.json (branch: prod):2 คือ vtrc-restfull-v2 ไม่ใช่ benefit-api — ถ้า grep หา package name ใน CI/CD config จะไม่เจอคำว่า benefit-api เลย

Branch และสถิติไฟล์

repo อยู่บน canonical branch prod ตามที่ระบุใน workspace rule โดยมีไฟล์ tracked ทั้งหมด 172 ไฟล์ เมื่อนับเฉพาะ .ts ภายใต้ src/, libs/, config/, docs/, test/ จะได้ 127 ไฟล์ ส่วนที่เหลือคือ yarn.lock, Dockerfile, docker-compose.yml, nest-cli.json, tsconfig.json, package.json, .env, .eslintrc.js, bitbucket-pipelines.yml, sql/ (DDL scripts), uploads/ (sample files), และ docs เมื่อแบ่งตามโฟลเดอร์ภายใต้ src/ จะได้สัดส่วนประมาณนี้

โฟลเดอร์จำนวนไฟล์บทบาท
src/modules/14 module × ~3 ไฟล์ (controller + service + module) = ~42 ไฟล์feature module ตาม Nest convention
src/entities/28 entityTypeORM entity ต่อตารางใน schema benefit
src/dto/18 DTOclass-validator DTO ที่ใช้กับ controller
src/crontab/7 ไฟล์node-cron job (ผ่าน @nestjs/schedule)
libs/19 ไฟล์shared utility, guard, filter, logger, PDF template
config/1 ไฟล์env var → TypeORM options
docs/1 ไฟล์Swagger setup

โครงสร้างโฟลเดอร์

text
benefit-api/
├── src/
│   ├── main.ts                    bootstrap ของ Nest app, global prefix, static uploads, global guards/filters/pipes
│   ├── app.module.ts              root module — ประกาศ 3 TypeORM connection + import ทุก feature module
│   ├── app.controller.ts          health/root controller
│   ├── app.service.ts
│   ├── dto/                       18 DTO ไฟล์ — class-validator decorator ต่อ module
│   ├── entities/                  28 TypeORM entity — ต่อตารางใน schema `benefit`
│   ├── crontab/                   node-cron ผ่าน @nestjs/schedule
│   │   ├── cronTab.module.ts
│   │   ├── cronTab.service.ts     ประกาศ @Cron ทั้งหมดของ repo (2 jobs)
│   │   ├── funeral/               funeral.task.service.ts — import/update ข้อมูลฌาปนกิจจาก HRMI
│   │   └── inheritance/           inheritance.task.service.ts — import/update มรดกตกทอด
│   └── modules/                   14 feature module (Nest convention: controller + service + module)
│       ├── wdhospitals/           เบิกค่ารักษาพยาบาล (WdHospitals) — endpoint หลักเป็น @Public()
│       ├── disaster/              เบิกเงินช่วยเหลือภัยพิบัติ (WfDisasterHD)
│       ├── approveDisaster/       อนุมัติเงินภัยพิบัติ + สร้าง payment transaction + เรียก centralize
│       ├── payments/              จัดการ paymentTransaction ทุกประเภท (docType 1-5) — ใหญ่สุดเป็นอันดับ 2
│       ├── disbursement/          เบิกสวัสดิการอื่น (other welfare) + cash payment
│       ├── exportPaymentReport/   export ไฟล์จ่ายเงินไป FMIS ผ่าน SFTP (ไฟล์ใหญ่สุดใน repo, ~7,740 บรรทัด)
│       ├── studyLeave/            ค่าธรรมเนียมการศึกษา (docType 3)
│       ├── setupPayor/            master data ผู้จ่ายเงิน
│       ├── chartOfAccount/        Chart of Account master data
│       ├── dropdown/              dropdown/master lookup กลาง (mDropdown table)
│       ├── uploadFiles/           เก็บไฟล์ base64 ลง local disk (ไม่ผ่าน vtrc-common)
│       ├── export-payments/       export payment list (เล็ก, มี spec)
│       └── batchJob/              manual trigger สำหรับ cron jobs (ops เรียกย้อนหลัง)
├── libs/
│   ├── jwt/                       jwt.strategy.ts + jwt-auth.guard.ts (global guard + @Public() override)
│   ├── log/                       logs.middleware.ts — I3GatewayLogger (custom logger wrapper)
│   ├── exception/                 http-exception.filter.ts — global exception filter
│   ├── utils/                     welfare.util.ts, lists.util.ts, running.util.ts, dropdown.util.ts, check-emp-wd.util.ts
│   ├── common/                    base64-to-file.ts, remove-file.ts, paginate.ts, validator-date.ts
│   └── pdfTemplate/               ejs templates สำหรับ FMIS report + payment slip (puppeteer render เป็น PDF)
├── config/configuration.ts        env var → TypeORM options (3 connections) + business constant (status labels)
├── docs/document.config.ts        Swagger document config
├── sql/                           DDL scripts สำหรับ schema `benefit`
├── uploads/                       ไฟล์ที่ผู้ใช้ upload จริง — มี PNG ตัวอย่าง commit ติดมาด้วย
└── Dockerfile                     node:20-alpine + chromium (สำหรับ puppeteer)

Dependencies ที่สำคัญ

ส่วนนี้สรุป dependency ที่โหลดใน app.module.ts และใช้จริงใน service layer อ่านแบบเต็มได้ใน benefit-api/package.json (branch: prod):26-61

NestJS modules ที่ลงทะเบียนใน root module (benefit-api/src/app.module.ts (branch: prod):33-75) คือ ConfigModule.forRoot (global, load factory configuration), TypeOrmModule.forRootAsync × 3 (ชื่อ connection main, vtrc, hrmi), ScheduleModule.forRoot, HttpModule ของ @nestjs/axios, TerminusModule สำหรับ health check, และ feature module 14 ตัว

ORM library ที่ใช้คือ typeorm 0.3 ผ่าน @nestjs/typeorm 9 — service ทุกตัว inject EntityManager ผ่าน decorator @InjectEntityManager('main' | 'vtrc' | 'hrmi') และเรียก method find, findOne, save, update, transaction, createQueryBuilder, และ query (raw SQL string) ตามต้องการ ไม่มีการสร้าง @InjectRepository pattern แยกต่างหากเพราะทุก module ใช้ shared entity manager ที่ inject ใน service constructor

auth library คือ @nestjs/passport + passport-jwt โดยมี JwtStrategy (benefit-api/libs/jwt/jwt.strategy.ts (branch: prod):7-23) เป็น strategy ที่ verify JWT ด้วย secretOrKey จาก env SECRET_KEY และมี JwtAuthGuard (benefit-api/libs/jwt/jwt-auth.guard.ts (branch: prod):6-37) เป็น global guard ที่ถูกเปิดใช้ใน main.ts (ดู benefit-api/src/main.ts (branch: prod):32) — ทุก route จะถูก guard อัตโนมัติยกเว้นมี decorator @Public() ติดอยู่

integration library ที่ใช้ติดต่อ service อื่นมี 3 ตัวคือ axios (สำหรับ HTTP call ไป centralize-api, vtrc-api, vtrc-common), ssh2-sftp-client และ sftp-upload (สำหรับส่งไฟล์ export ไป FMIS) และ puppeteer (สำหรับ render PDF slip/report)

สถิติที่ควรจำ

ตัวเลขค่าที่มา
Tracked files (branch prod)172git ls-files | wc -l
TypeScript files (src + libs + config + docs + test)127find src libs config docs test -type f -name "*.ts"
Feature module14benefit-api/src/app.module.ts (branch: prod):58-74
TypeORM entity28benefit-api/src/entities/
DTO ไฟล์18benefit-api/src/dto/
TypeORM connection3 (main, vtrc, hrmi)benefit-api/src/app.module.ts (branch: prod):40-57
Cron job ที่ประกาศจริง2 (importDataFromHRMI, updateDataToHRMI)benefit-api/src/crontab/cronTab.service.ts (branch: prod):22,37
ไฟล์ใหญ่สุดเป็นบรรทัดexportpaymentreport.service.ts (~7,740 บรรทัด)benefit-api/src/modules/exportPaymentReport/exportpaymentreport.service.ts (branch: prod)
ไฟล์ใหญ่อันดับ 2payments.service.ts (1,450 บรรทัด)benefit-api/src/modules/payments/payments.service.ts (branch: prod)
Default listen port9320 (override ได้ด้วย env PORT)benefit-api/src/main.ts (branch: prod):21 และ benefit-api/Dockerfile (branch: prod):28

Entry point trace

main.ts เป็น bootstrap หลักของ Nest และกำหนด global middleware ทั้งหมด ลำดับการทำงานมีดังนี้

  1. NestFactory.create<NestExpressApplication>(AppModule, { logger: new I3GatewayLogger('System') }) สร้าง app พร้อม custom logger (benefit-api/src/main.ts (branch: prod):15-17) — ใช้ NestExpressApplication เพื่อเข้าถึง useStaticAssets สำหรับโฟลเดอร์ uploads/
  2. ConfigModule.forRoot({ load: [configuration], isGlobal: true }) ที่ประกาศใน AppModule (benefit-api/src/app.module.ts (branch: prod):36-39) โหลด configuration() factory จาก config/configuration.ts ซึ่งแปลง env var ทั้งหมดเป็น TypeORM options object ภายใต้ key db, dbHRMI, vtrcDB และเก็บค่า scalar เช่น port, jwtSecretKey, centralizeServer, vtrcServer ไว้ใน ConfigService (benefit-api/config/configuration.ts (branch: prod):3-80)
  3. TypeOrmModule.forRootAsync × 3 ใน AppModule (benefit-api/src/app.module.ts (branch: prod):40-57) เปิด connection main, vtrc, hrmi โดยใช้ useFactory ที่อ่าน configService.get('db' | 'vtrcDB' | 'dbHRMI') — ทุก connection ตั้ง logging: ['error', 'query'] ซึ่งหมายความว่าทุก SQL query จะถูก log ออก stdout ในทุก environment
  4. ScheduleModule.forRoot() (benefit-api/src/app.module.ts (branch: prod):67) เปิดใช้ @Cron decorator เพื่อให้ CronTabService.importDataFromHRMI และ updateDataToHRMI ทำงานตาม schedule (benefit-api/src/crontab/cronTab.service.ts (branch: prod):22,37)
  5. app.setGlobalPrefix(\${process.env.BASE_PATH}api/v1`) (benefit-api/src/main.ts (branch: prod):25) กำหนด prefix ของทุก route — ค่าจริงใน prod คือ /benefit-api/api/v1/...`
  6. app.useStaticAssets(join(process.cwd(), '/uploads'), { prefix: '/benefit-api/' }) (benefit-api/src/main.ts (branch: prod):26-28) เปิด static file serving สำหรับไฟล์ที่ UploadService เขียนลง disk
  7. app.useGlobalFilters(new HttpExceptionFilter()) (benefit-api/src/main.ts (branch: prod):31) ติดตั้ง global exception filter ที่ catch เฉพาะ HttpException แล้ว reformat response เป็น { statusCode, message } (benefit-api/libs/exception/http-exception.filter.ts (branch: prod):8-21)
  8. app.useGlobalGuards(new JwtAuthGuard(reflector)) (benefit-api/src/main.ts (branch: prod):32) บังคับให้ทุก route ผ่าน JWT validation ก่อน เว้นแต่มี @Public() บน method/class
  9. app.useGlobalPipes(new ValidationPipe({ whitelist: true, forbidNonWhitelisted: true })) (benefit-api/src/main.ts (branch: prod):33) strip field ที่ไม่อยู่ใน DTO class ออก และ reject request ที่ส่ง field เกินมา
  10. app.enableCors({ origin: '*', allowedHeaders: '*', methods: 'GET,PUT,POST,DELETE,PATCH,OPTIONS' }) (benefit-api/src/main.ts (branch: prod):34-38) เปิด CORS สำหรับทุก origin (permissive)
  11. SwaggerModule.setup (benefit-api/src/main.ts (branch: prod):40-41) เปิด Swagger UI ที่ ${BASE_PATH}docs
  12. app.listen(port, '0.0.0.0') (benefit-api/src/main.ts (branch: prod):42) start HTTP server บน port จาก env PORT (default 3000 ใน code, แต่ Dockerfile EXPOSE 9320 จึงเท่ากับ 9320 จริง)

Auth strategy ที่ใช้ทั่ว repo

รูปแบบ auth ของ benefit-api คือ global JWT guard + @Public() opt-out ซึ่งเป็น pattern เดียวกับ centralize-api และ NestJS service อื่น ๆ ในวงการ vtrc ไม่ใช่ pattern แบบ vtrc-api ที่ใช้ @auth(accessRole, accessMenu) directive ฝั่ง GraphQL

การไหลของ auth มีดังนี้

  1. request มาถึง route ใด ๆ — JwtAuthGuard.canActivate (benefit-api/libs/jwt/jwt-auth.guard.ts (branch: prod):15-24) เช็คก่อนว่า method/class มี metadata isPublic: true (ที่ตั้งด้วย @Public()) หรือไม่ ถ้ามีให้ return true ทันที ข้าม JWT
  2. ถ้าไม่ใช่ @Public() จะเรียก super.canActivate(context) ซึ่งเข้าสู่ passport-jwt flow — ExtractJwt.fromAuthHeaderAsBearerToken() (benefit-api/libs/jwt/jwt.strategy.ts (branch: prod):12) ดึง JWT จาก header Authorization: Bearer <token> แล้ว verify signature ด้วย secretOrKey จาก env SECRET_KEY
  3. JwtStrategy.validate(payload) (benefit-api/libs/jwt/jwt.strategy.ts (branch: prod):18-22) รับ decoded payload แล้ว return กลับเป็น user object ซึ่ง Nest จะ inject ลง @Request() { user } ใน controller ทุกตัว — user จะมี field uid, profile.empCode, profile.fullName, profile.sourceDB
  4. JwtAuthGuard.handleRequest (benefit-api/libs/jwt/jwt-auth.guard.ts (branch: prod):26-36) ถ้า verify ล้มเหลวจะ throw UnauthorizedException({ statusCode: 401, error: info.message }) กลับไปที่ client

ที่สำคัญคือ guard ตัวนี้ ไม่ได้เช็ค role/menu/permission เลย — payload ที่ decode ได้จาก JWT จะถูกส่งผ่านไป controller ตรง ๆ การ authorize ว่า user คนนี้มีสิทธิ์เข้าถึงเอกสารหรือไม่จึงตกเป็นความรับผิดชอบของแต่ละ service ซึ่งในกรณีส่วนใหญ่ service ก็ไม่ได้เช็คเพิ่ม (ดู 02 Core pipeline สำหรับการวิเคราะห์เชิงลึก)

route ที่ใช้ @Public() อย่างชัดเจนคือ WdhospitalsController.add, update/:status, detail/:id (benefit-api/src/modules/wdhospitals/wdhospitals.controller.ts (branch: prod):24,30,36) — ทั้ง 3 endpoint นี้ถูกเรียกโดย vtrc-api ผ่าน HTTP ไม่ใช่ user จึงไม่มี JWT ติดมาด้วย module อื่น ๆ เช่น disaster, approveDisaster, payments ไม่มี @Public() ที่ endpoint ใด ๆ จึงต้องผ่าน JWT เสมอ

Naming ที่ต้องระวัง

ชื่อ package ใน package.json คือ vtrc-restfull-v2 (benefit-api/package.json (branch: prod):2) — ถ้า grep benefit-api ใน CI/CD script หรือ log pipeline จะไม่เจอชื่อนี้ ต้องค้นหาด้วย vtrc-restfull-v2 แทน

UploadService (benefit-api/src/modules/uploadFiles/upload.service.ts (branch: prod):7) เก็บไฟล์ที่ local disk ภายใต้ ./uploads/${prefixPath}/${dirDate}/ ตรง ๆ ผ่าน base64ToFile ใน libs/common/base64-to-file.ts — คนละ mechanism กับ vtrc-common file service ที่ module disbursement และ payments (ตอนอ่าน welfareAttachment) เรียกผ่าน HTTP ไป ${VTRC_COMMON_SERVER}vtrc-common/api/v1/file/getFileArray (benefit-api/src/modules/payments/payments.service.ts (branch: prod):901-912) — repo เดียวมี 2 ระบบเก็บไฟล์คู่ขนาน ถ้าแก้ไฟล์ attachment flow ต้องเช็คก่อนวย module ไหนใช้ mechanism ไหน

Entity base class

entity ทุกตัวใน src/entities/ สืบทอดจากหนึ่งในสอง base class คือ BaseEntity (benefit-api/src/entities/base.ts (branch: prod):3-21) สำหรับ master/lookup table (id, createdAt, createdBy, updatedAt, updatedBy, isDeleted) และ BaseBenefitEntity (benefit-api/src/entities/baseBenefit.entity.ts (branch: prod):3-24) สำหรับ transaction table (id, createdAt, createdByEmpcode, createdByFullname, updatedAt, updatedByEmpcode, updatedByFullname) — สังเกตว่า base class ทั้งสองมี field audit ต่างกันเล็กน้อย (string createdBy vs. split createdByEmpcode + createdByFullname) ซึ่งสะท้อนว่า schema benefit ถูกออกแบบโดยคนละช่วงเวลาหรือคนละทีม ตอนเขียน entity ใหม่ต้องเลือก base class ให้ตรงกับ migration script ใน sql/

PaymentTransactionEntity (benefit-api/src/entities/paymentTransaction.entity.ts (branch: prod):7-210) เป็น entity ที่กลายเป็นจุดศูนย์กลางของระบบเพราะเก็บ payment transaction ของทุก docType (1 = WdHospitals, 2 = Disaster, 3 = StudyLeave, 4 = other, 5 = inheritance) — service หลายตัวใน payments, disaster, approveDisaster, disbursement, studyLeave อ่าน/เขียนตารางนี้ร่วมกัน ทำให้การแก้ field หรือ index ของ entity นี้มี blast radius กว้าง

WelfareHeader (benefit-api/src/entities/welfareHeader.entity.ts) เป็นอีก entity สำคัญที่เก็บ header ของเอกสารเบิก "สวัสดิการอื่น" (docType 4) ครอบคลุม relation welfareDetail, welfareBankDetail, welfareAttachment, welfareHistory — module disbursement และ payments (listOtherWelfare, getOtherWelfareDetailById) เรียกใช้ผ่าน leftJoinAndSelect chain

entity ของ disaster family ประกอบด้วย WfDisasterHD (header), WfDisasterDocument (รายการเอกสารประกอบ), WfDisasterHistories (audit trail), MAttachfile (ไฟล์แนบรวม), MDisasterCondition (master ประเภทภัยพิบัติ) — WfDisasterHD มี relation cascade ไปยังทั้ง 4 ทำให้การ save header ทีเดียวจะกระทบลูกทั้งหมด (ดู 02 Core pipeline)

entity ของ WdHospitals family ใน schema benefit ประกอบด้วย WdHospitalsEntity, WdHospitalsHistoryEntity, WdHospitalSicksEntity, WdDocumentsEntity, WdExportPaymentEntity/WdExportPaymentDetailEntity/WdExportPaymentHistoryEntity — เป็นการ mirror ข้อมูลจาก legacy WDHospitals table ใน schema vtrc เพื่อให้ benefit-api อ่านได้โดยไม่ต้อง cross-database query ในทุกกรณี

Env var และ configuration factory

config/configuration.ts (benefit-api/config/configuration.ts (branch: prod):3-80) เป็น factory function ที่แปลง env var ทั้งหมดเป็น config object ที่ NestJS ConfigService เข้าถึงได้ โครงสร้าง key สำคัญมีดังนี้

key ใน configenv var ที่อ่านการใช้งาน
portPORTlisten port — Dockerfile expose 9320
jwtSecretKeySECRET_KEYJWT signing/verification ใน JwtStrategy
centralizeServerCENTRALIZE_SERVERbase URL ของ centralize-api
vtrcServerVTRC_SERVERbase URL ของ vtrc-api
dbDB_TYPE, DB_HOST, DB_PORT, DB_USER, DB_PASSWORD, DB_NAMETypeORM options สำหรับ connection main (schema benefit)
dbHRMIDB_HRMI_*TypeORM options สำหรับ connection hrmi (SQL Server HR master)
vtrcDBVTRC_DB_*TypeORM options สำหรับ connection vtrc (legacy core schema)
disbursement.status(hardcode)label ภาษาไทยของสถานะ disbursement
exportPaymentTransactionDT.status(hardcode)label ภาษาไทยของสถานะ payment

สังเกตว่า VTRC_COMMON_SERVER (ที่ใช้ใน payments.service.ts:901) ไม่ได้ถูก declare ใน configuration.ts — service อ่านตรงจาก process.env ผ่าน ConfigService.get('VTRC_COMMON_SERVER') ซึ่งก็ได้ค่า เพราะ ConfigModule load env โดย default แต่ไม่มี type-safe key ใน factory — เป็น inconsistency เล็กน้อยที่อาจทำให้ miss config ตอน deploy ได้

ทุก connection ตั้ง logging: ['error', 'query'] (benefit-api/config/configuration.ts (branch: prod):17,35,53) ซึ่งหมายความว่าทุก SQL query จะถูก log ออก stdout ในทุก environment รวมถึง prod — เป็นค่าเริ่มต้นที่ค่อนข้าง verbose และอาจรั่วข้อมูล sensitive ลง log file

ทุก connection ตั้ง extra: { trustServerCertificate: true, encrypt: false, connectionTimeout: 15000, requestTimeout: 15000 } (benefit-api/config/configuration.ts (branch: prod):19-24,37-42,55-60) — encrypt: false เป็นการลดความปลอดภัยของ database connection เพื่อให้ใช้งานกับ SQL Server เก่าได้

autoLoadEntities: process.env.NODE_ENV === 'dev' และ synchronize: process.env.NODE_ENV === 'dev' (benefit-api/config/configuration.ts (branch: prod):15-16) — เฉพาะ connection main เท่านั้นที่เปิด synchronize ใน dev ส่วน vtrc และ hrmi ปิดไว้เสมอเพื่อกัน schema drift

Build, deploy, runtime

Dockerfile (benefit-api/Dockerfile (branch: prod):1-30) ใช้ base image node:20-alpine ติดตั้ง chromium package (benefit-api/Dockerfile (branch: prod):8-14) เพื่อให้ puppeteer ทำงานได้โดยไม่โหลด Chromium เอง (ตั้ง env PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true ที่บรรทัด 18) — ขนาด image จึงใหญ่กว่า Node.js service ทั่วไปมาก จาก chromium + library dependencies (harfbuzz, freetype, ttf-freefont, nss)

build script ใน package.json (benefit-api/package.json (branch: prod):13) คือ nest build (compile TS → JS ด้วย @nestjs/cli) ส่งผลลัพธ์ไป dist/src/main.jsstart:prod script (benefit-api/package.json (branch: prod):18) รัน node dist/src/main โดยตรง ไม่ใช้ nest start

CI/CD pipeline อยู่ใน bitbucket-pipelines.yml ใช้ Bitbucket Pipelines (ไม่ใช่ GitHub Actions) — deploy ผ่าน docker-compose ไปยัง server ภายในของ Thai Red Cross รายละเอียดอยู่ใน volume 13 (deploy)

runtime directory layout บน server ภายหลัง deploy จะมีโฟลเดอร์ uploads/ ที่ runtime เขียนไฟล์แนบของ user ลงไปจริง — ใน container นี้ไม่ใช่ volume แยก แต่อยู่ใน working directory ของ container ดังนั้นถ้า container restart ไฟล์แนบที่ user upload หลังจาก deploy ครั้งล่าสุดจะ หายไป ถ้าไม่ได้ mount volume แยก ตรวจสอบกับ ops ว่ามีการ mount จริงหรือไม่ (UNVERIFIED — ต้องถามทีม ops)

Feature module reference

ตารางนี้สรุป endpoint หลักของแต่ละ module เพื่อให้กระโดดไป source code ได้เร็ว

ModuleController pathendpoint ที่สำคัญใช้ @Public() ไหม
disaster/disasterPOST /create_disaster, PUT /update_disaster, GET /get_list, GET /get_disaster_conditionไม่
approveDisaster/approveDisasterPOST /saveApprove, GET /listBenefitForApproverไม่
payments/paymentsPOST /add, POST /update/:status, POST /cashPayment, POST /cancelWelfare, POST /checkEmpDetailไม่
wdhospitals/wdHospitalsPOST /add, POST /update/:status, GET /detail/:id, GET /employee, GET /maker, GET /approverใช่ (3 endpoint แรก)
disbursement/disbursementcreate/update/list other welfareไม่
exportPaymentReport/exportPaymentReportexport FMIS file, render PDF slipไม่
studyLeave/studyLeavecreate/update study leaveไม่
setupPayor/setupPayorCRUD payor masterไม่
chartOfAccount/chartOfAccountCRUD COA masterไม่
dropdown/dropdownlookup masterไม่
uploadFiles/uploadเก็บไฟล์ base64 → local diskผสม
export-payments/exportPaymentsexport payment listไม่
batchJob/batchJobmanual trigger cronไม่

endpoint ทั้งหมดขึ้นต้นด้วย prefix /benefit-api/api/v1 (จาก app.setGlobalPrefix ใน main.ts:25) — full URL จึงเป็น /benefit-api/api/v1/disaster/create_disaster เป็นต้น

Swagger UI เปิดที่ /benefit-api/docs (benefit-api/src/main.ts (branch: prod):40-41) — ใช้ตรวจสอบ DTO shape และลองยิง request ได้ในแบบ interactive

Cron jobs

CronTabService (benefit-api/src/crontab/cronTab.service.ts (branch: prod)) ประกาศ cron job เพียง 2 ตัวผ่าน decorator @Cron ของ @nestjs/schedule

typescript
@Cron('10 22 * * *')
private async importDataFromHRMI() {
  await this.funeralTaskService.doImportFuneral(null);
  await this.inheritanceTaskService.doImportInheritance(null);
}

@Cron(CronExpression.EVERY_DAY_AT_9PM)
private async updateDataToHRMI() {
  await this.funeralTaskService.doUpdateFuneralForHRMI(null);
  await this.inheritanceTaskService.doUpdateInheritanceForHRMI(null);
}

importDataFromHRMI รันทุกวันเวลา 22:10 (TZ Asia/Bangkok จาก Dockerfile:5) — ดึงข้อมูลฌาปนกิจและมรดกตกทอดจาก HRMI เข้า benefit

updateDataToHRMI รันทุกวันเวลา 21:00 — ส่งข้อมูลสถานะการจ่ายเงินกลับไป HRMI

ทั้งสอง job ทำงาน sequential ภายใน method เดียว ไม่มี retry ไม่มี backoff — ถ้า HRMI down จะ fail ทั้งก้อนแล้วรอรอบถัดไปวันต่อมา ดูรายละเอียดใน 05 Cron jobs

batchJob module (benefit-api/src/modules/batchJob/manualBatch.controller.ts) เป็น HTTP endpoint สำหรับ manual trigger งานเดียวกัน — มีไว้ให้ ops รันย้อนหลังได้เมื่อ cron fail

Logging และ observability

repo ใช้ custom logger I3GatewayLogger (benefit-api/libs/log/logs.middleware.ts) ที่ถูกใช้ทั้งใน main.ts (benefit-api/src/main.ts (branch: prod):16,19) และใน service ทุกตัวผ่าน pattern private logger = new I3GatewayLogger(ClassName.name) — เป็น wrapper เล็กน้อยบน Logger ของ NestJS เพื่อรวม log format ไม่ได้เพิ่ม feature อะไรมาก ไม่มี correlation ID ไม่มี structured logging (ออกมาเป็น string) — การ trace request ข้าม service จึงยาก

console.log ปรากฏหลายสิบครั้งใน service (เช่น benefit-api/src/modules/approveDisaster/approvedisaster.service.ts (branch: prod):145,265,403,418,421) — เป็น debug leftover ที่ไม่ควรอยู่ใน production code แต่ก็ไม่ได้ถูก lint ออก

TypeORM log level ['error', 'query'] ที่ตั้งใน configuration.ts ทำให้ทุก SQL query ถูก log ออก stdout — ในกรณี raw SQL string interpolation ที่มีค่าจาก user (เช่นใน check-emp-wd.util.ts:204, payments.service.ts:494) ค่าเหล่านั้นจะปรากฏใน log ทำให้ risk เรื่อง sensitive data exposure ใน log file สูง

ไม่มีการเชื่อมต่อกับ APM หรือ metrics collector (Prometheus, Datadog, Sentry) ใด ๆ ใน codebase — observability ของ benefit-api จึงอาศัย log ของ Bitbucket Pipelines และ log ของ container เท่านั้น

Testing

repo มี spec file เพียง 5 ไฟล์เท่านั้นคือ export-payments.service.spec.ts, export-payments.controller.spec.ts, dropdown.service.spec.ts, dropdown.controller.spec.ts, upload.controller.spec.ts, upload.service.spec.ts, disaster.controller.spec.ts, disaster.service.spec.ts — ครอบเพียง 4 ใน 14 module (export-payments, dropdown, uploadFiles, disaster) และ module ใหญ่ที่สุดอย่าง payments และ exportPaymentReport ไม่มี test เลย

config Jest ใน benefit-api/package.json (branch: prod):87-106 ตั้ง testRegex: .*.spec\\.ts$, transform ใช้ ts-jest, testEnvironment: node — script test (benefit-api/package.json (branch: prod):20) รัน jest ปกติ ส่วน test:cov (benefit-api/package.json (branch: prod):22) ใช้ --coverage แต่ไม่ได้ตั้ง threshold ไว้ใน config จึงไม่บังคับ

ไม่มี e2e test ที่ใช้งานได้จริง — test:debug script อ้าง ./test/jest-e2e.json แต่ไม่มี e2e spec ใน test/ นอกจาก config ว่าง ๆ

ทำไมต้องรู้เรื่องนี้ก่อน

บทถัดไป (core pipeline, persistence, integrations, scorecard) จะอ้างชื่อ module/entity/guard จากแผนที่นี้ตลอด ถ้าเจอ PaymentTransactionEntity แล้วงง ให้กลับมาดูว่ามันอยู่ใน src/entities/paymentTransaction.entity.ts และถูกใช้ร่วมกันโดยทุก module ที่สร้างรายการเบิกจ่าย ถ้าเจอ @Public() บน endpoint ให้นึกถึง global JwtAuthGuard ที่ติดตั้งใน main.ts:32 และถ้าเจอ query ไป vtrc entity manager ให้นึกถึง connection vtrc ใน app.module.ts:46-51 ที่ชี้ไป schema ของ legacy core — ไม่ใช่ schema benefit ของตัวเอง

ไป บท 2 Core pipeline