2025-10-02 18:42:14 +07:00
import { AppDataSource } from "../database/data-source" ;
2025-10-03 13:05:24 +07:00
import { Profile } from "./../entities/Profile" ;
2025-10-02 18:42:14 +07:00
import { ProfileEmployee } from "../entities/ProfileEmployee" ;
import { OrgRoot } from "../entities/OrgRoot" ;
import { OrgChild1 } from "../entities/OrgChild1" ;
import { OrgChild2 } from "../entities/OrgChild2" ;
import { OrgChild3 } from "../entities/OrgChild3" ;
import { OrgChild4 } from "../entities/OrgChild4" ;
import { Brackets , Repository } from "typeorm" ;
import Extension from "../interfaces/extension" ;
import permission from "../interfaces/permission" ;
import { RequestWithUser } from "../middlewares/user" ;
2025-10-03 13:05:24 +07:00
export interface LeaveFilter {
2025-10-02 18:42:14 +07:00
page : number ;
pageSize : number ;
searchField ? : "firstName" | "lastName" | "fullName" | "citizenId" | "position" | "posNo" ;
searchKeyword? : string ;
posType? : string ;
posLevel? : string ;
isProbation? : boolean ;
node? : number ;
nodeId? : string ;
isAll? : boolean ;
retireType? : string ;
sortBy? : string ;
sort : "ASC" | "DESC" ;
}
export interface OrganizationCondition {
condition : string ;
params : Record < string , any > ;
}
export class ProfileLeaveService {
2025-10-03 13:05:24 +07:00
private profileEmployeeRepo : Repository < ProfileEmployee > ;
private profileRepo : Repository < Profile > ;
2025-10-02 18:42:14 +07:00
private orgRootRepository : Repository < OrgRoot > ;
private child1Repository : Repository < OrgChild1 > ;
private child2Repository : Repository < OrgChild2 > ;
private child3Repository : Repository < OrgChild3 > ;
private child4Repository : Repository < OrgChild4 > ;
constructor ( ) {
2025-10-03 13:05:24 +07:00
this . profileEmployeeRepo = AppDataSource . getRepository ( ProfileEmployee ) ;
this . profileRepo = AppDataSource . getRepository ( Profile ) ;
2025-10-02 18:42:14 +07:00
this . orgRootRepository = AppDataSource . getRepository ( OrgRoot ) ;
this . child1Repository = AppDataSource . getRepository ( OrgChild1 ) ;
this . child2Repository = AppDataSource . getRepository ( OrgChild2 ) ;
this . child3Repository = AppDataSource . getRepository ( OrgChild3 ) ;
this . child4Repository = AppDataSource . getRepository ( OrgChild4 ) ;
}
2025-10-03 13:05:24 +07:00
/** สร้าง query สำหรับการค้นหาตามฟิลด์ต่างๆ */
buildSearchQuery ( searchField? : string , type : string = "profile" ) : string {
2025-10-02 18:42:14 +07:00
switch ( searchField ) {
case "citizenId" :
2025-10-03 13:05:24 +07:00
return ` ${ type } .citizenId LIKE :keyword ` ;
2025-10-02 18:42:14 +07:00
case "position" :
2025-10-03 13:05:24 +07:00
return ` ${ type } .position LIKE :keyword ` ;
2025-10-02 18:42:14 +07:00
case "posNo" :
return `
2025-10-03 13:05:24 +07:00
( CONCAT ( profileSalary . posNoAbb , profileSalary . posNo ) LIKE :keyword )
OR ( CONCAT ( profileSalary . posNoAbb , " " , profileSalary . posNo ) LIKE :keyword )
OR ( profileSalary . posNo LIKE :keyword )
2025-10-02 18:42:14 +07:00
` ;
default :
2025-10-03 13:05:24 +07:00
return ` CONCAT( ${ type } .prefix, ${ type } .firstName, ' ', ${ type } .lastName) LIKE :keyword ` ;
2025-10-02 18:42:14 +07:00
}
}
2025-10-03 13:05:24 +07:00
/** สร้างเงื่อนไขการค้นหาตาม node และ nodeId */
2025-10-02 18:42:14 +07:00
async buildNodeCondition (
node? : number ,
nodeId? : string ,
isAll? : boolean ,
) : Promise < OrganizationCondition > {
let condition = "1=1" ;
let nodeAll = "" ;
const params : Record < string , any > = { } ;
if ( ! node || ! nodeId ) {
return { condition , params } ;
}
// สร้าง nodeAll condition - เพิ่มการตรวจสอบ IS NULL
if ( isAll === false && node < 4 ) {
const nextLevels = [ "orgChild1" , "orgChild2" , "orgChild3" , "orgChild4" ] ;
2025-10-03 13:05:24 +07:00
nodeAll = ` AND (profileSalary. ${ nextLevels [ node ] } IS NULL) ` ;
2025-10-02 18:42:14 +07:00
}
try {
switch ( node ) {
case 0 : {
const orgRoot = await this . orgRootRepository . findOne ( { where : { id : nodeId } } ) ;
if ( orgRoot ) {
2025-10-03 13:05:24 +07:00
condition = "(profileSalary.orgRoot = :orgRoot)" ;
2025-10-02 18:42:14 +07:00
params . orgRoot = orgRoot . orgRootName ;
}
break ;
}
case 1 : {
const orgChild1 = await this . child1Repository . findOne ( { where : { id : nodeId } } ) ;
if ( orgChild1 ) {
2025-10-03 13:05:24 +07:00
condition = "(profileSalary.orgChild1 = :orgChild1)" ;
2025-10-02 18:42:14 +07:00
params . orgChild1 = orgChild1 . orgChild1Name ;
}
break ;
}
case 2 : {
const orgChild2 = await this . child2Repository . findOne ( { where : { id : nodeId } } ) ;
if ( orgChild2 ) {
2025-10-03 13:05:24 +07:00
condition = "(profileSalary.orgChild2 = :orgChild2)" ;
2025-10-02 18:42:14 +07:00
params . orgChild2 = orgChild2 . orgChild2Name ;
}
break ;
}
case 3 : {
const orgChild3 = await this . child3Repository . findOne ( { where : { id : nodeId } } ) ;
if ( orgChild3 ) {
2025-10-03 13:05:24 +07:00
condition = "(profileSalary.orgChild3 = :orgChild3)" ;
2025-10-02 18:42:14 +07:00
params . orgChild3 = orgChild3 . orgChild3Name ;
}
break ;
}
case 4 : {
const orgChild4 = await this . child4Repository . findOne ( { where : { id : nodeId } } ) ;
if ( orgChild4 ) {
2025-10-03 13:05:24 +07:00
condition = "(profileSalary.orgChild4 = :orgChild4)" ;
2025-10-02 18:42:14 +07:00
params . orgChild4 = orgChild4 . orgChild4Name ;
}
break ;
}
}
} catch ( error ) {
console . error ( "Error building node condition:" , error ) ;
}
return { condition : condition + nodeAll , params } ;
}
2025-10-03 13:05:24 +07:00
/** สร้างเงื่อนไขการค้นหาตาม permission */
2025-10-02 18:42:14 +07:00
async buildPermissionCondition (
request : RequestWithUser ,
isAll? : boolean ,
2025-10-03 13:05:24 +07:00
permissionType : string = "SYS_REGISTRY_OFFICER" ,
2025-10-02 18:42:14 +07:00
) : Promise < OrganizationCondition > {
2025-10-03 13:05:24 +07:00
const _data = await new permission ( ) . PermissionOrgList ( request , permissionType ) ;
2025-10-02 18:42:14 +07:00
let condition = "1=1" ;
let nodeAll = "" ;
const params : Record < string , any > = { } ;
try {
if ( _data . root ) {
const orgRootPms = await this . orgRootRepository . findOne ( { where : { id : _data.root } } ) ;
if ( orgRootPms ) {
condition = "(profileSalary.orgRoot = :orgRootPms OR profileSalary.id IS NULL)" ;
params . orgRootPms = orgRootPms . orgRootName ;
}
if ( isAll === false )
nodeAll = " AND (profileSalary.orgChild1 IS NULL OR profileSalary.id IS NULL)" ;
} else if ( _data . child1 ) {
const orgChild1Pms = await this . child1Repository . findOne ( { where : { id : _data.child1 } } ) ;
if ( orgChild1Pms ) {
condition = "(profileSalary.orgChild1 = :orgChild1Pms OR profileSalary.id IS NULL)" ;
params . orgChild1Pms = orgChild1Pms . orgChild1Name ;
}
if ( isAll === false )
nodeAll = " AND (profileSalary.orgChild2 IS NULL OR profileSalary.id IS NULL)" ;
} else if ( _data . child2 ) {
const orgChild2Pms = await this . child2Repository . findOne ( { where : { id : _data.child2 } } ) ;
if ( orgChild2Pms ) {
condition = "(profileSalary.orgChild2 = :orgChild2Pms OR profileSalary.id IS NULL)" ;
params . orgChild2Pms = orgChild2Pms . orgChild2Name ;
}
if ( isAll === false )
nodeAll = " AND (profileSalary.orgChild3 IS NULL OR profileSalary.id IS NULL)" ;
} else if ( _data . child3 ) {
const orgChild3Pms = await this . child3Repository . findOne ( { where : { id : _data.child3 } } ) ;
if ( orgChild3Pms ) {
condition = "(profileSalary.orgChild3 = :orgChild3Pms OR profileSalary.id IS NULL)" ;
params . orgChild3Pms = orgChild3Pms . orgChild3Name ;
}
if ( isAll === false )
nodeAll = " AND (profileSalary.orgChild4 IS NULL OR profileSalary.id IS NULL)" ;
} else if ( _data . child4 ) {
const orgChild4Pms = await this . child4Repository . findOne ( { where : { id : _data.child4 } } ) ;
if ( orgChild4Pms ) {
condition = "(profileSalary.orgChild4 = :orgChild4Pms OR profileSalary.id IS NULL)" ;
params . orgChild4Pms = orgChild4Pms . orgChild4Name ;
}
}
} catch ( error ) {
console . error ( "Error building permission condition:" , error ) ;
}
return { condition : condition + nodeAll , params } ;
}
2025-10-03 13:05:24 +07:00
/** แปลงข้อมูลลูกจ้างก่อน response */
2025-10-02 18:42:14 +07:00
transformEmployeeData ( employee : ProfileEmployee ) : any {
const dateEmployment =
employee . profileEmployeeEmployment ? . length === 0 || ! employee . profileEmployeeEmployment
? null
: employee . profileEmployeeEmployment . reduce ( ( latest , current ) = > {
return latest . date > current . date ? latest : current ;
} ) . date ;
// ตรวจสอบว่า profileSalary มีข้อมูลหรือไม่
const salary =
employee . profileSalary && employee . profileSalary . length > 0
? employee . profileSalary [ 0 ]
: null ;
const posNo =
salary ? . posNoAbb && salary ? . posNo
? ` ${ salary . posNoAbb } ${ salary . posNo } `
: salary ? . posNo || "" ;
// สร้าง organization hierarchy - ใช้ข้อมูลจาก temp fields ถ้า salary ไม่มี
const org = salary
? [ salary . orgChild4 , salary . orgChild3 , salary . orgChild2 , salary . orgChild1 , salary . orgRoot ]
. filter ( Boolean )
. join ( "\n" )
: [
employee . child4Temp ,
employee . child3Temp ,
employee . child2Temp ,
employee . child1Temp ,
employee . rootTemp ,
]
. filter ( Boolean )
. join ( "\n" ) ;
// สร้าง node information
const getNodeInfo = ( nodeTemp : string ) = > {
switch ( nodeTemp ) {
case "0" :
return {
name : employee.rootTemp ,
shortName : employee.rootShortNameTemp ,
} ;
case "1" :
return {
name : employee.child1Temp ,
shortName : employee.child1ShortNameTemp ,
} ;
case "2" :
return {
name : employee.child2Temp ,
shortName : employee.child2ShortNameTemp ,
} ;
case "3" :
return {
name : employee.child3Temp ,
shortName : employee.child3ShortNameTemp ,
} ;
case "4" :
return {
name : employee.child4Temp ,
shortName : employee.child4ShortNameTemp ,
} ;
default :
return { name : null , shortName : null } ;
}
} ;
const nodeInfo = getNodeInfo ( employee . nodeTemp || "0" ) ;
return {
id : employee.id ,
avatar : employee.avatar ,
avatarName : employee.avatarName ,
prefix : employee.prefix ,
rank : employee.rank ,
firstName : employee.firstName ,
lastName : employee.lastName ,
citizenId : employee.citizenId ,
posLevel : employee.posLevel?.posLevelName || null ,
posType : employee.posType?.posTypeName || null ,
posTypeShortName : employee.posType?.posTypeShortName || null ,
posLevelId : employee.posLevel?.id || null ,
posTypeId : employee.posType?.id || null ,
positionId : employee.positionIdTemp ,
posmasterId : employee.posmasterIdTemp ,
position : employee.position ,
posNo ,
employeeClass : employee.employeeClass ,
govAge : Extension.CalculateGovAge ( employee . dateAppoint , 0 , 0 ) ,
age : Extension.CalculateAgeStrV2 ( employee . birthDate , 0 , 0 , "GET" ) ,
dateEmployment ,
dateAppoint : employee.dateAppoint ,
dateStart : employee.dateStart ,
createdAt : employee.createdAt ,
dateRetireLaw : employee.dateRetireLaw ,
draftOrganizationOrganization : nodeInfo.name ,
draftPositionEmployee : employee.positionTemp ,
draftOrgEmployeeStatus : employee.statusTemp ,
node : employee.nodeTemp ,
nodeId : employee.nodeIdTemp ,
nodeName : nodeInfo.name ,
nodeShortName : nodeInfo.shortName ,
root : employee.rootTemp || null ,
rootId : employee.rootIdTemp || null ,
rootShortName : employee.rootShortNameTemp || null ,
child1 : employee.child1Temp || null ,
child1Id : employee.child1IdTemp || null ,
child1ShortName : employee.child1ShortNameTemp || null ,
child2 : employee.child2Temp || null ,
child2Id : employee.child2IdTemp || null ,
child2ShortName : employee.child2ShortNameTemp || null ,
child3 : employee.child3Temp || null ,
child3Id : employee.child3IdTemp || null ,
child3ShortName : employee.child3ShortNameTemp || null ,
child4 : employee.child4Temp || null ,
child4Id : employee.child4IdTemp || null ,
child4ShortName : employee.child4ShortNameTemp || null ,
org ,
} ;
}
2025-10-03 13:05:24 +07:00
/** ค้นหาลูกจ้างที่พ้นจากราชการ */
2025-10-02 18:42:14 +07:00
async getLeaveEmployees (
request : RequestWithUser ,
2025-10-03 13:05:24 +07:00
filter : LeaveFilter ,
2025-10-02 18:42:14 +07:00
) : Promise < { data : any [ ] ; total : number } > {
const {
page ,
pageSize ,
searchField ,
searchKeyword = "" ,
posType ,
posLevel ,
isProbation ,
node ,
nodeId ,
isAll ,
retireType ,
sortBy = "profileEmployee.dateLeave" ,
sort ,
} = filter ;
// สร้าง query conditions แบบ parallel
const [ nodeCondition , permissionCondition ] = await Promise . all ( [
this . buildNodeCondition ( node , nodeId , isAll ) ,
2025-10-03 13:05:24 +07:00
this . buildPermissionCondition ( request , isAll , "SYS_REGISTRY_EMP" ) ,
2025-10-02 18:42:14 +07:00
] ) ;
2025-10-03 13:05:24 +07:00
const searchQuery = this . buildSearchQuery ( searchField , "profileEmployee" ) ;
2025-10-02 18:42:14 +07:00
// สร้าง main query - เปลี่ยนจาก leftJoinAndSelect เป็น leftJoin สำหรับ profileSalary
2025-10-03 13:05:24 +07:00
const queryBuilder = this . profileEmployeeRepo
2025-10-02 18:42:14 +07:00
. createQueryBuilder ( "profileEmployee" )
. leftJoinAndSelect ( "profileEmployee.posLevel" , "posLevel" )
. leftJoinAndSelect ( "profileEmployee.posType" , "posType" )
. leftJoinAndSelect ( "profileEmployee.profileEmployeeEmployment" , "profileEmployeeEmployment" )
. leftJoin (
"profileEmployee.profileSalary" ,
"profileSalary" ,
"profileSalary.order = (SELECT MAX(ps.order) FROM profileSalary ps WHERE ps.profileEmployeeId = profileEmployee.id and ps.positionName != 'เกษียณอายุราชการ')" ,
)
. addSelect ( [
"profileSalary.id" ,
"profileSalary.order" ,
"profileSalary.posNo" ,
"profileSalary.posNoAbb" ,
"profileSalary.orgRoot" ,
"profileSalary.orgChild1" ,
"profileSalary.orgChild2" ,
"profileSalary.orgChild3" ,
"profileSalary.orgChild4" ,
] )
. where (
new Brackets ( ( qb ) = > {
qb . where ( "profileEmployee.isLeave = :isLeave" , { isLeave : true } ) . orWhere (
"profileEmployee.isRetirement = :isRetirement" ,
{ isRetirement : true } ,
) ;
} ) ,
)
2025-10-03 13:05:24 +07:00
. andWhere ( "profileEmployee.employeeClass LIKE :type" , { type : "PERM" } )
. andWhere (
new Brackets ( ( qb ) = > {
qb . orWhere (
searchKeyword != undefined && searchKeyword != null && searchKeyword != ""
? searchQuery
: "1=1" ,
{
keyword : ` % ${ searchKeyword } % ` ,
} ,
) ;
} ) ,
) ;
2025-10-02 18:42:14 +07:00
// เพิ่มเงื่อนไขการค้นหา
if ( posType ) {
queryBuilder . andWhere ( "posType.posTypeName LIKE :keyword1" , { keyword1 : ` ${ posType } ` } ) ;
}
if ( posLevel ) {
queryBuilder . andWhere (
2025-10-03 13:05:24 +07:00
"CONCAT(posType.posTypeShortName, ' ', posLevel.posLevelName) LIKE :keyword2" ,
2025-10-02 18:42:14 +07:00
{ keyword2 : ` ${ posLevel } ` } ,
) ;
}
if ( isProbation !== undefined && isProbation !== null ) {
queryBuilder . andWhere ( ` profileEmployee.isProbation = ${ isProbation } ` ) ;
}
if ( retireType ) {
queryBuilder . andWhere ( "profileEmployee.leaveType = :retireType" , { retireType } ) ;
}
2025-10-03 13:05:24 +07:00
// เพิ่ม permission และ node conditions
queryBuilder
. andWhere ( permissionCondition . condition , permissionCondition . params )
. andWhere ( nodeCondition . condition , nodeCondition . params ) ;
console . log ( "Permission Condition:" , permissionCondition ) ;
console . log ( "Node Condition:" , nodeCondition ) ;
// เพิ่ม sorting และ pagination
queryBuilder
. orderBy ( sortBy , sort )
. skip ( ( page - 1 ) * pageSize )
. take ( pageSize ) ;
const [ records , total ] = await queryBuilder . getManyAndCount ( ) ;
// print query for debug
// console.log("SQL Query:", queryBuilder.getSql());
// แปลงข้อมูลแบบ parallel
const data = await Promise . all (
records . map ( ( record ) = > Promise . resolve ( this . transformEmployeeData ( record ) ) ) ,
) ;
return { data , total } ;
}
/ * *
* แ ป ล ง ข ้ อ ม ู ล ล ู ก จ ้ า ง ก ่ อ น response
* /
transformOfficerData ( employee : Profile ) : any {
// ตรวจสอบว่า profileSalary มีข้อมูลหรือไม่
const salary =
employee . profileSalary && employee . profileSalary . length > 0
? employee . profileSalary [ 0 ]
: null ;
const posNo =
salary ? . posNoAbb && salary ? . posNo
? ` ${ salary . posNoAbb } ${ salary . posNo } `
: salary ? . posNo || "" ;
const posExecutive = salary ? . positionExecutive ? salary.positionExecutive : null ;
const root = salary ? . orgRoot ? salary.orgRoot : null ;
// สร้าง organization hierarchy - ใช้ข้อมูลจาก temp fields ถ้า salary ไม่มี
const org = salary
? [ salary . orgChild4 , salary . orgChild3 , salary . orgChild2 , salary . orgChild1 , salary . orgRoot ]
. filter ( Boolean )
. join ( "\n" )
: [ "" , "" , "" , "" , "" ] . filter ( Boolean ) . join ( "\n" ) ;
const orgRootShortName = salary ? . posNoAbb ? salary.posNoAbb : null ;
return {
id : employee.id ,
avatar : employee.avatar ,
avatarName : employee.avatarName ,
dateAppoint : employee.dateAppoint ,
prefix : employee.prefix ,
rank : employee.rank ,
firstName : employee.firstName ,
lastName : employee.lastName ,
citizenId : employee.citizenId ,
posLevel : employee.posLevel?.posLevelName || null ,
posType : employee.posType?.posTypeName || null ,
posLevelId : employee.posLevel?.id || null ,
posTypeId : employee.posType?.id || null ,
position : employee.position ,
posExecutive ,
posNo ,
rootId : null ,
root ,
orgRootShortName ,
orgRevisionId : null ,
org ,
} ;
}
/ * *
* ค ้ น ห า ข ้ า ร า ช ก า ร ท ี ่ พ ้ น จ า ก ร า ช ก า ร
* /
async getLeaveOfficer (
request : RequestWithUser ,
filter : LeaveFilter ,
) : Promise < { data : any [ ] ; total : number } > {
const {
page ,
pageSize ,
searchField ,
searchKeyword = "" ,
posType ,
posLevel ,
isProbation ,
node ,
nodeId ,
isAll ,
retireType ,
sortBy = "profile.dateLeave" ,
sort ,
} = filter ;
// สร้าง query conditions แบบ parallel
const [ nodeCondition , permissionCondition ] = await Promise . all ( [
this . buildNodeCondition ( node , nodeId , isAll ) ,
this . buildPermissionCondition ( request , isAll , "SYS_REGISTRY_OFFICER" ) ,
] ) ;
const searchQuery = this . buildSearchQuery ( searchField ) ;
// สร้าง main query - เปลี่ยนจาก leftJoinAndSelect เป็น leftJoin สำหรับ profileSalary
const queryBuilder = this . profileRepo
. createQueryBuilder ( "profile" )
. leftJoinAndSelect ( "profile.posLevel" , "posLevel" )
. leftJoinAndSelect ( "profile.posType" , "posType" )
. leftJoin (
"profile.profileSalary" ,
"profileSalary" ,
"profileSalary.order = (SELECT MAX(ps.order) FROM profileSalary ps WHERE ps.profileId = profile.id and ps.positionName != 'เกษียณอายุราชการ')" ,
)
. addSelect ( [
"profileSalary.id" ,
"profileSalary.order" ,
"profileSalary.posNo" ,
"profileSalary.posNoAbb" ,
"profileSalary.orgRoot" ,
"profileSalary.orgChild1" ,
"profileSalary.orgChild2" ,
"profileSalary.orgChild3" ,
"profileSalary.orgChild4" ,
"profileSalary.positionExecutive" ,
] )
. where (
new Brackets ( ( qb ) = > {
qb . where ( "profile.isLeave = :isLeave" , { isLeave : true } ) . orWhere (
"profile.isRetirement = :isRetirement" ,
{ isRetirement : true } ,
) ;
} ) ,
)
. andWhere (
new Brackets ( ( qb ) = > {
qb . orWhere (
searchKeyword != undefined && searchKeyword != null && searchKeyword != ""
? searchQuery
: "1=1" ,
{
keyword : ` % ${ searchKeyword } % ` ,
} ,
) ;
} ) ,
) ;
// เพิ่มเงื่อนไขการค้นหา
if ( posType ) {
queryBuilder . andWhere ( "posType.posTypeName LIKE :keyword1" , { keyword1 : ` ${ posType } ` } ) ;
}
if ( posLevel ) {
2025-10-03 16:44:43 +07:00
queryBuilder . andWhere ( "posLevel.posLevelName LIKE :keyword2" , { keyword2 : ` ${ posLevel } ` } ) ;
2025-10-03 13:05:24 +07:00
}
if ( isProbation !== undefined && isProbation !== null ) {
queryBuilder . andWhere ( ` profile.isProbation = ${ isProbation } ` ) ;
}
if ( retireType ) {
queryBuilder . andWhere ( "profile.leaveType = :retireType" , { retireType } ) ;
2025-10-02 18:42:14 +07:00
}
// เพิ่ม permission และ node conditions
queryBuilder
. andWhere ( permissionCondition . condition , permissionCondition . params )
. andWhere ( nodeCondition . condition , nodeCondition . params ) ;
// เพิ่ม sorting และ pagination
queryBuilder
. orderBy ( sortBy , sort )
. skip ( ( page - 1 ) * pageSize )
. take ( pageSize ) ;
const [ records , total ] = await queryBuilder . getManyAndCount ( ) ;
2025-10-03 13:05:24 +07:00
// print query for debug
// console.log("SQL Query:", queryBuilder.getSql());
2025-10-02 18:42:14 +07:00
// แปลงข้อมูลแบบ parallel
const data = await Promise . all (
2025-10-03 13:05:24 +07:00
records . map ( ( record ) = > Promise . resolve ( this . transformOfficerData ( record ) ) ) ,
2025-10-02 18:42:14 +07:00
) ;
return { data , total } ;
}
}