4.6 · TypeDefs + resolvers catalog
บทนี้ catalog GraphQL schema (31 SDL files, 2,629 LOC) และ resolvers (29 files, 1,826 LOC) เพื่อให้ navigate codebase ได้ พร้อมชี้ patterns และ inconsistencies ที่สำคัญ
ขนาดรวม
| ส่วน | LOC | ไฟล์ |
|---|---|---|
| TypeDefs (SDL) | 2,629 | 31 |
| Resolvers | 1,826 | 29 |
| รวม schema layer | ~4,500 | 60 |
เล็กกว่า vtrc-api (queries.js ไฟล์เดียว 4,500 LOC) — แต่กระจายไฟล์ ทำให้ navigate ง่ายกว่า
TypeDefs catalog (31 ไฟล์)
| ไฟล์ | LOC | @auth ใช้? | หัวข้อหลัก |
|---|---|---|---|
root.js | 35 | declare directive | Query, Mutation, Subscribtion (typo), SortingBy, YesNo, JSON, Date |
authentication.js | 56 | pwdVerification @auth | AuthResponse, AuthProfileResponse, AuthDomains, AppCredentials |
personal.js | 116 | personalProfile @auth, employeeProfileHistory @auth | PersonalProfile, EmployeeDetail, ProfileHistory |
employee.js | 271 | 3 fields @auth | EmployeeProfile, WorkingProfile, EmployeeFinancial, AccordingProfile, EmployeeAddress, MeisDivision, EmplGroup, EmpLevel, PositionManage/Technical, ImageContent |
family.js | 58 | employeeFamily @auth | EmployeeFamily, FamilyPersonInfo, FamilyRelationship enum |
organization.js | 97 | none | Organization, OrganizationUnit, OrgazationAddress (typo) |
userVerification.js | 102 | none | requestVerificationCode, profileVerificationChecker, profileVerification |
registration.js | 126 | changePassword @auth | createAccountByVerifyCode, changePasswordByVerifyCode, requestOtp, createSSONotifyUser mutations |
timeAttendance.js | 33 | timeAttendanceByDate @auth | TimeAttendance, TimeAttendanceResponse |
payrollSlip.js | 86 | 2 fields @auth | payrollSlipDetail, payrollSlipDetail6Month |
payrollSlip_removed.js | 112 | — | DEAD FILE — ไม่ถูก import |
finance.js | 26 | employeeFinanceType @auth | EmployeeFinanace (typo), EmployeeBookBank |
bank.js | 40 | none | Bank, BankBranch, BankBook |
tax.js | 106 | none | EmployeeTax (90+ fields, full Thai tax form) |
fund.js | 86 | commented #@auth | ProvidentFund, FundPolicy |
compensation.js | 52 | employeeCompensation @auth | EmployeeCompensation, EmployeeCompensationIncomeDeduct |
referenceData.js | 36 | none | refOrganizations, refOrganizationBelongs, refNewOrganizationUnits |
masterData.js | 86 | none | masterOrgSystem, masterPlace |
searchProfile.js | 44 | searchUser @auth(accessLevel: 4, ignoreAuth: true) | SearchUserResponse |
hrEnum.js | 25 | none | hrEnum query |
notification.js | 25 | none | sendSMSNotification mutation |
hrmi.js | 394 | none | largest SDL — leave/medical/approver domain |
childTuition.js | 88 | none | fetchListChildTuitions, fetchChildTuitionDetail |
salaryCert.js | 59 | none | salaryCertDetail, salaryCert |
hrPerProbation.js | 15 | none | checkHrPerProbationNotApprove |
twoWay.js | 65 | none | 6 queries for 2-way sync |
sso.js | 22 | none | fetchNonEmpSSO, nonEmpSSO |
studyLeaveRequest.js | 105 | none | getEmpStudyLeaveRequestCD, getPersonalCD |
statLog.js | 26 | none | statLog, statLogList |
hr/hrTaxes.js | 113 | hrTaxes @auth | HrTaxesAttr input (~80 fields) |
hr/hrAddress.js | 39 | hrAddress @auth | HrAddressAttr input |
hr/hrPhoneNumber.js | 23 | hrPhoneNumber @auth | HrPhoneNumberAttr input |
hrmi.js — SDL ใหญ่สุด (394 LOC)
ไฟล์เดียวที่ cover leave + medical + approver domain — port มาจาก HRMI leave-management system เก่า
หัวข้อในไฟล์
hrmi.js (394 LOC)
├── Leave
│ ├── getLeaveType
│ ├── getLeaveDocumentList(sourceDb)
│ ├── getLeaveDocumentDetail(sourceDb)
│ ├── getLeaveDay / getLeaveDayAll
│ ├── getWorkingDay
│ ├── getLeaveShiftTime / getShiftTime
│ ├── createLeaveDocument
│ ├── editLeaveDocument
│ └── deleteLeaveDocument
├── Medical
│ └── getMedical(sourceDb)
├── Approver
│ ├── getApprover
│ └── getLeaveApprover
├── Attachment
│ ├── createAttachmentFile
│ ├── updateAttachmentFile
│ └── deleteAttachmentFile
├── Org queries
│ ├── fetchEmpWithOrgnize(sourceDb)
│ ├── fetchEmpwithCoA(sourceDb)
│ └── employeeDetailProfile(sourceDb)
└── enums
├── ApproveStatus
├── DurationType
└── ApproverTypesourceDb parameter กระจายไปเกือบทุก query — เป็น evidence ของ multi-tenant pattern (บท 4.5)
Resolvers catalog (29 ไฟล์)
| ไฟล์ | LOC | Resolvers หลัก |
|---|---|---|
main.js | 14 | Query.__type / __schema → throw (introspection guard) |
authentication.js | 72 | pwdVerification, signInDomains (hardcoded 26-row org list) |
personal.js | 79 | personalProfile, employeeDetail, employeeProfileHistory |
employee.js | 127 | employeeProfile, accordingProfile, employeeFinancial, employeeManagement |
organization.js | 42 | refOrganizations, refOrganizationBelongs |
userVerification.js | 74 | requestVerificationCode, profileVerificationChecker, profileVerification |
registration.js | 386 | isAccountExists, createAccountByVerifyCode, changePassword, requestOtp, createSSONotifyUser |
timeAttendance.js | 33 | timeAttendanceByDate |
payrollSlip.js | 240 | payrollSlipDetail, payrollSlipDetail6Month |
finanace.js | 26 | employeeFinanceType (typo) |
compensation.js | 29 | employeeCompensation |
fund.js | 23 | fetchEmployeeProvidentFund, createChangeBenefitPerson |
masterData.js | 31 | masterOrgSystem, masterOrgSystemCreate, masterOrgSystemUpdate |
searchProfile.js | 23 | searchUser (apiKey-only ผ่าน ignoreAuth) |
hrEnum.js | 17 | hrEnum |
notification.js | 9 | sendSMSNotification |
hrmi.js | 244 | 24 resolvers — leave/medical/approver/attachment |
bank/book.js | 38 | bankBookEmployee, bankBookEmployeeArray |
childTuition.js | 20 | 4 child tuition queries |
salaryCert.js | 12 | salaryCertDetail |
hrPerProbation.js | 12 | checkHrPerProbationNotApprove |
twoWay.js | 37 | 6 queries for 2-way sync |
sso.js | 12 | fetchNonEmpSSO |
studyLeaveRequest.js | 18 | getEmpStudyLeaveRequestCD, getPersonalCD |
statLog.js | 12 | statLog |
hr/hrTaxes.js | 33 | hrTaxes mutation (wraps dbHrTemp.transaction()) |
hr/hrAddress.js | 54 | hrAddress mutation (OTP ถูกปิด) |
hr/hrPhoneNumber.js | 52 | hrPhoneNumber mutation (OTP ยังทำงาน) |
OTP inconsistency (Critical)
hr/hrAddress.js — OTP ปิดไปตั้งแต่ 8 Jul 2021
// ## REMOVE OTP 08Jul2021 ##
// if (!await verifyOtpCode(...)) {
// throw new Error('OTP_INVALID')
// }🔴 High — address mutation (ที่ include email + phone fields ตาม HrAddressAttr input) ไม่ต้องผ่าน OTP
hr/hrPhoneNumber.js — OTP ยังทำงาน
// OTP verification ยัง active
if (!await verifyOtpCode(...)) {
throw new Error('OTP_INVALID')
}Inconsistency
sibling resolvers 2 ตัว (address + phone) ใช้ pattern ต่างกัน — address แก้ได้ไม่ต้อง OTP แต่ phone ต้อง ควรมี single source of truth สำหรับ "field ไหนบ้างที่ต้อง OTP"
signInDomains — hardcoded org list
authentication.js resolver signInDomains คืน list ของ org ที่ login ได้ โดย hardcoded 26 rows ใน code
authentication.js:35-72
const domains = [
{ id: 'CHULA', nameTH: 'จุฬา', ... },
{ id: 'SOMD', nameTH: 'สมเด็จ', ... },
// ... 24 more
]🔴 High — org list ไม่ได้มาจาก DB ทำให้เพิ่ม/แก้ org ต้อง deploy code ใหม่ ควรย้ายไป masterData table
registration.js — ไฟล์ใหญ่สุด (386 LOC)
cover account creation + password management flow
registration.js (386 LOC)
├── isAccountExists (check ก่อน create)
├── createAccountByVerifyCode
├── changePasswordByVerifyCode
├── changePassword
├── requestOtp
├── getVerifyCodeByEmail
└── createSSONotifyUserflow ทั้งหมดผูกกับ SSO + AD + SMS gateway — เป็นไฟล์ที่ซับซ้อนที่สุดใน resolver layer
payrollSlip.js — 240 LOC
resolver ที่คำนวณ payroll slip detail
payrollSlip.js (240 LOC)
├── PayrollSlip.bookNo (resolve field — sensor book number)
├── payrollSlipDetail
└── payrollSlipDetail6Monthดึงข้อมูลจาก DB view uvi3PaySlipHD + uvi3PaySlipDT (models/hrmi/salary/) — เป็น view ที่ MSSQL side define ไว้
Patterns ที่สังเกตได้
1. Resolver ไม่ได้แยกตาม bounded context
ไฟล์ resolver ผสมหลาย domain — hrmi.js เดียว cover leave + medical + approver + attachment + org queries (244 LOC) ไม่ได้แยกเป็น leave.js, medical.js, approver.js ตาม domain
ใน Go target จะแยกเป็น bounded context ชัดเจน
2. ไม่มี DataLoader pattern
ทุก query ที่ resolve nested field ทำ N+1 — เช่น Employee ที่มี workingProfile association ถ้า query 100 employee พร้อมกัน จะมี 100+1 query
ใน Go target ใช้ dataloader pattern หรือ join โดยตรง
3. sourceDb กระจายทั้ง schema
parameter sourceDb อยู่ในเกือบทุก query ของ hrmi.js — caller (vtrc-api) ต้องรู้ว่าต้อง pass อะไร
ถ้า vtrc-api pass ผิด = cross-tenant data leak (บท 4.5)
Dead SDL — payrollSlip_removed.js
112 LOC ที่ไม่ถูก import — เป็นเวอร์ชันเก่าที่ใช้ empCode แทน profileKey
payrollSlipInfo(empCode: String, year: Int, month: Int): Payroll ← legacy signaturelive version (payrollSlip.js) ใช้ profileKey แทน
payrollSlipDetail(profileKey: String, ...): PayrollSlipDetail ← currentmigration จาก empCode → profileKey เกิดขึ้นเพื่อ support multi-tenant (เพราะ empCode ไม่ unique ข้าม shard)
สรุป
| Pattern | สถานะ |
|---|---|
| Schema size | 🟢 เล็กกว่า vtrc-api |
| File organization | 🟡 ผสม domain ในไฟล์เดียว (hrmi.js) |
@auth coverage | 🟡 ไม่ครบทุก field — หลาย field public |
| OTP consistency | 🔴 hrAddress vs hrPhoneNumber ต่างกัน |
| Hardcoded data | 🟡 signInDomains 26 rows |
| Dead SDL | 🟡 payrollSlip_removed.js |
| N+1 patterns | 🔴 ไม่มี DataLoader |
| Typos in schema | 🟡 EmployeeFinanace, OrgazationAddress, Subscribtion |
อ่านต่อ → 4.7 External integrations