2025-08-13 16:47:01 +07:00
import { Controller , Route , Security , Tags , Path , Request , Response , Get , Query } from "tsoa" ;
2025-08-07 13:05:58 +07:00
import { AppDataSource } from "../database/data-source" ;
import HttpSuccess from "../interfaces/http-success" ;
import HttpStatusCode from "../interfaces/http-status" ;
import HttpError from "../interfaces/http-error" ;
import { ApiName } from "../entities/ApiName" ;
2025-08-08 09:26:48 +07:00
import { isPermissionRequest } from "../middlewares/authWebService" ;
import { RequestWithUserWebService } from "../middlewares/user" ;
2025-08-08 17:14:29 +07:00
import { OrgRevision } from "../entities/OrgRevision" ;
2025-08-13 16:14:44 +07:00
import { ApiHistory } from "../entities/ApiHistory" ;
2026-05-21 18:08:20 +07:00
import { OrgChild1 } from "../entities/OrgChild1" ;
import { OrgChild2 } from "../entities/OrgChild2" ;
import { OrgChild3 } from "../entities/OrgChild3" ;
import { OrgChild4 } from "../entities/OrgChild4" ;
2025-08-14 11:17:13 +07:00
import { SystemCode } from "./../interfaces/api-type" ;
@Route ( "api/v1/org/api-service" )
2025-08-07 13:05:58 +07:00
@Tags ( "ApiKey" )
2025-08-08 09:26:48 +07:00
@Security ( "webServiceAuth" )
2025-08-07 13:05:58 +07:00
@Response (
HttpStatusCode . INTERNAL_SERVER_ERROR ,
"เกิดข้อผิดพลาด ไม่สามารถแสดงรายการได้ กรุณาลองใหม่ในภายหลัง" ,
)
export class ApiWebServiceController extends Controller {
private apiNameRepository = AppDataSource . getRepository ( ApiName ) ;
2025-08-08 17:14:29 +07:00
private orgRevisionRepository = AppDataSource . getRepository ( OrgRevision ) ;
2025-08-13 16:14:44 +07:00
private apiHistoryRepository = AppDataSource . getRepository ( ApiHistory ) ;
2026-05-21 11:44:28 +07:00
private currentRevisionId : string = "" ;
2025-08-13 16:14:44 +07:00
2026-05-21 11:07:15 +07:00
// การแทนที่ฟิลด์ ID ด้วยฟิลด์ Name สำหรับ Profile entity
private readonly PROFILE_FIELD_REPLACEMENTS : Record <
string ,
{ propertyName : string ; joinRelation : string ; joinField : string }
> = {
posLevelName : {
propertyName : "posLevelId" ,
joinRelation : "posLevel" ,
joinField : "posLevelName" ,
} ,
posTypeName : {
propertyName : "posTypeId" ,
joinRelation : "posType" ,
joinField : "posTypeName" ,
} ,
registrationProvinceName : {
propertyName : "registrationProvinceId" ,
joinRelation : "registrationProvince" ,
joinField : "name" ,
} ,
registrationDistrictName : {
propertyName : "registrationDistrictId" ,
joinRelation : "registrationDistrict" ,
joinField : "name" ,
} ,
registrationSubDistrictName : {
propertyName : "registrationSubDistrictId" ,
joinRelation : "registrationSubDistrict" ,
joinField : "name" ,
} ,
currentProvinceName : {
propertyName : "currentProvinceId" ,
joinRelation : "currentProvince" ,
joinField : "name" ,
} ,
currentDistrictName : {
propertyName : "currentDistrictId" ,
joinRelation : "currentDistrict" ,
joinField : "name" ,
} ,
currentSubDistrictName : {
propertyName : "currentSubDistrictId" ,
joinRelation : "currentSubDistrict" ,
joinField : "name" ,
} ,
} ;
2026-05-21 13:44:03 +07:00
// การแทนที่ฟิลด์ ID ด้วยฟิลด์ Name สำหรับ Position entity
private readonly POSITION_FIELD_REPLACEMENTS : Record <
string ,
{ propertyName : string ; joinRelation : string ; joinField : string }
> = {
posTypeName : {
propertyName : "posTypeId" ,
joinRelation : "posType" ,
joinField : "posTypeName" ,
} ,
posLevelName : {
propertyName : "posLevelId" ,
joinRelation : "posLevel" ,
joinField : "posLevelName" ,
} ,
posExecutiveName : {
propertyName : "posExecutiveId" ,
joinRelation : "posExecutive" ,
joinField : "posExecutiveName" ,
} ,
} ;
2026-05-21 18:08:20 +07:00
// การแทนที่ฟิลด์ ID ด้วยฟิลด์ Name สำหรับ ProfileEmployee entity
private readonly PROFILEEMPLOYEE_FIELD_REPLACEMENTS : Record <
string ,
{ propertyName : string ; joinRelation : string ; joinField : string }
> = {
posTypeName : {
propertyName : "posTypeId" ,
joinRelation : "posType" ,
joinField : "posTypeName" ,
} ,
posLevelName : {
propertyName : "posLevelId" ,
joinRelation : "posLevel" ,
joinField : "posLevelName" ,
} ,
} ;
2026-05-21 11:44:28 +07:00
/ * *
* build posMaster permission condition
* @summary ส ร ้ า ง เ ง ื ่ อ น ไ ข ก า ร ก ร อ ง ข ้ อ ม ู ล ต า ม ส ิ ท ธ ิ ์ ก า ร เ ข ้ า ถ ึ ง
* /
private buildPosMasterPermissionCondition (
accessType : string | undefined ,
dnaIds : {
dnaRootId? : string | null ;
dnaChild1Id? : string | null ;
dnaChild2Id? : string | null ;
dnaChild3Id? : string | null ;
dnaChild4Id? : string | null ;
} ,
2026-05-21 13:44:03 +07:00
tableAlias : string = "posMaster" ,
2026-05-21 11:44:28 +07:00
) : string {
// ALL - no filtering
if ( accessType === "ALL" ) {
return "1=1" ;
}
// No access type specified but has DNA IDs - default to NORMAL behavior
const conditions : string [ ] = [ ] ;
if ( accessType === "ROOT" && dnaIds . dnaRootId ) {
// All organizations under this root
conditions . push (
2026-05-21 13:44:03 +07:00
` ${ tableAlias } .orgRootId IN (SELECT id FROM orgRoot WHERE orgRevisionId = " ${ this . currentRevisionId } " AND ancestorDNA LIKE " ${ dnaIds . dnaRootId } %") ` ,
2026-05-21 11:44:28 +07:00
) ;
} else if ( accessType === "CHILD" || accessType === "NORMAL" ) {
// Build conditions based on which DNA level is specified
if ( dnaIds . dnaChild4Id ) {
conditions . push (
2026-05-21 13:44:03 +07:00
` ${ tableAlias } .orgChild4Id IN (SELECT id FROM orgChild4 WHERE orgRevisionId = " ${ this . currentRevisionId } " AND ancestorDNA = " ${ dnaIds . dnaChild4Id } ") ` ,
2026-05-21 11:44:28 +07:00
) ;
} else if ( dnaIds . dnaChild3Id ) {
conditions . push (
2026-05-21 13:44:03 +07:00
` ${ tableAlias } .orgChild3Id IN (SELECT id FROM orgChild3 WHERE orgRevisionId = " ${ this . currentRevisionId } " AND ancestorDNA = " ${ dnaIds . dnaChild3Id } ") ` ,
2026-05-21 11:44:28 +07:00
) ;
// For CHILD type, include all descendants
if ( accessType === "CHILD" ) {
conditions . push (
2026-05-21 13:44:03 +07:00
` ( ${ tableAlias } .orgChild3Id IN (SELECT id FROM orgChild3 WHERE orgRevisionId = " ${ this . currentRevisionId } " AND ancestorDNA LIKE " ${ dnaIds . dnaChild3Id } %") OR ${ tableAlias } .orgChild4Id IS NOT NULL) ` ,
2026-05-21 11:44:28 +07:00
) ;
}
} else if ( dnaIds . dnaChild2Id ) {
conditions . push (
2026-05-21 13:44:03 +07:00
` ${ tableAlias } .orgChild2Id IN (SELECT id FROM orgChild2 WHERE orgRevisionId = " ${ this . currentRevisionId } " AND ancestorDNA = " ${ dnaIds . dnaChild2Id } ") ` ,
2026-05-21 11:44:28 +07:00
) ;
if ( accessType === "CHILD" ) {
conditions . push (
2026-05-21 13:44:03 +07:00
` ( ${ tableAlias } .orgChild2Id IN (SELECT id FROM orgChild2 WHERE orgRevisionId = " ${ this . currentRevisionId } " AND ancestorDNA LIKE " ${ dnaIds . dnaChild2Id } %") OR ${ tableAlias } .orgChild3Id IS NOT NULL) ` ,
2026-05-21 11:44:28 +07:00
) ;
}
} else if ( dnaIds . dnaChild1Id ) {
conditions . push (
2026-05-21 13:44:03 +07:00
` ${ tableAlias } .orgChild1Id IN (SELECT id FROM orgChild1 WHERE orgRevisionId = " ${ this . currentRevisionId } " AND ancestorDNA = " ${ dnaIds . dnaChild1Id } ") ` ,
2026-05-21 11:44:28 +07:00
) ;
if ( accessType === "CHILD" ) {
conditions . push (
2026-05-21 13:44:03 +07:00
` ( ${ tableAlias } .orgChild1Id IN (SELECT id FROM orgChild1 WHERE orgRevisionId = " ${ this . currentRevisionId } " AND ancestorDNA LIKE " ${ dnaIds . dnaChild1Id } %") OR ${ tableAlias } .orgChild2Id IS NOT NULL) ` ,
2026-05-21 11:44:28 +07:00
) ;
}
} else if ( dnaIds . dnaRootId ) {
conditions . push (
2026-05-21 13:44:03 +07:00
` ${ tableAlias } .orgRootId IN (SELECT id FROM orgRoot WHERE orgRevisionId = " ${ this . currentRevisionId } " AND ancestorDNA = " ${ dnaIds . dnaRootId } ") ` ,
2026-05-21 11:44:28 +07:00
) ;
if ( accessType === "CHILD" ) {
conditions . push (
2026-05-21 13:44:03 +07:00
` ( ${ tableAlias } .orgRootId IN (SELECT id FROM orgRoot WHERE orgRevisionId = " ${ this . currentRevisionId } " AND ancestorDNA LIKE " ${ dnaIds . dnaRootId } %") OR ${ tableAlias } .orgChild1Id IS NOT NULL) ` ,
2026-05-21 11:44:28 +07:00
) ;
}
}
}
return conditions . length > 0 ? ` ( ${ conditions . join ( " OR " ) } ) ` : "1=1" ;
}
2026-05-22 12:35:21 +07:00
/ * *
* rename ancestorDNA to id
* @summary เ ป ล ี ่ ย น ช ื ่ อ ฟ ิ ล ด ์ ancestorDNA เ ป ็ น id
* /
private renameAncestorDnaToId ( obj : any ) : any {
if ( ! obj || typeof obj !== "object" ) {
return obj ;
}
const result = { . . . obj } ;
if ( result . ancestorDNA !== undefined ) {
result . id = result . ancestorDNA ;
delete result . ancestorDNA ;
}
return result ;
}
2025-08-07 13:05:58 +07:00
/ * *
* list fields by systems
* @summary ร า ย ก า ร fields ต า ม systems
* /
@Get ( "/:system/:code" )
async listAttribute (
2025-08-08 09:26:48 +07:00
@Request ( ) request : RequestWithUserWebService ,
2025-08-14 11:17:13 +07:00
@Path ( "system" )
system : SystemCode ,
2025-08-07 13:05:58 +07:00
@Path ( "code" ) code : string ,
@Query ( "page" ) page : number = 1 ,
@Query ( "pageSize" ) pageSize : number = 100 ,
) : Promise < HttpSuccess | HttpError > {
2025-08-08 17:14:29 +07:00
const apiName = await this . apiNameRepository . findOne ( {
where : { code } ,
select : [ "id" , "code" , "methodApi" , "system" , "isActive" ] ,
relations : [ "apiAttributes" ] ,
order : {
apiAttributes : {
ordering : "ASC" ,
2025-08-07 13:05:58 +07:00
} ,
2025-08-08 17:14:29 +07:00
} ,
} ) ;
if ( ! apiName || apiName . system != system || ! apiName . isActive || apiName . methodApi != "GET" ) {
throw new HttpError ( HttpStatusCode . NOT_FOUND , "ไม่พบ API ที่ระบุ" ) ;
}
await isPermissionRequest ( request , apiName . id ) ;
const offset = ( page - 1 ) * pageSize ;
2026-05-21 11:07:15 +07:00
let propertyKey = apiName . apiAttributes . map ( ( attr ) = > ` ${ attr . tbName } . ${ attr . propertyKey } ` ) ;
2026-05-21 18:08:20 +07:00
const selectedFieldsByTable : Record < string , Set < string > > = { } ;
apiName . apiAttributes . forEach ( ( attr ) = > {
if ( ! selectedFieldsByTable [ attr . tbName ] ) {
selectedFieldsByTable [ attr . tbName ] = new Set < string > ( ) ;
}
selectedFieldsByTable [ attr . tbName ] . add ( attr . propertyKey ) ;
} ) ;
2025-08-07 13:05:58 +07:00
2026-05-22 12:35:21 +07:00
// สำหรับ Organization: ให้รวม ancestorDNA เสมอ เพื่อแสดงเป็น id
if ( system === "organization" ) {
// สำหรับ OrgRoot
const ancestorDnaField = "OrgRoot.ancestorDNA" ;
if ( ! propertyKey . includes ( ancestorDnaField ) ) {
propertyKey . push ( ancestorDnaField ) ;
}
if ( ! selectedFieldsByTable [ "OrgRoot" ] ) {
selectedFieldsByTable [ "OrgRoot" ] = new Set < string > ( ) ;
}
selectedFieldsByTable [ "OrgRoot" ] . add ( "ancestorDNA" ) ;
// สำหรับ OrgChild1, OrgChild2, OrgChild3, OrgChild4
const childTables = [ "OrgChild1" , "OrgChild2" , "OrgChild3" , "OrgChild4" ] ;
childTables . forEach ( ( table ) = > {
if ( ! selectedFieldsByTable [ table ] ) {
selectedFieldsByTable [ table ] = new Set < string > ( ) ;
}
selectedFieldsByTable [ table ] . add ( "ancestorDNA" ) ;
} ) ;
}
2025-08-14 11:17:13 +07:00
let tbMain : string = "" ;
2025-08-13 16:47:01 +07:00
let condition : string = "1=1" ;
2025-08-08 17:14:29 +07:00
if ( system == "registry" ) {
2025-08-14 11:17:13 +07:00
tbMain = "Profile" ;
2025-08-13 16:47:01 +07:00
} else if ( system == "registry_emp" ) {
2025-08-14 11:17:13 +07:00
tbMain = "ProfileEmployee" ;
2025-08-13 16:47:01 +07:00
condition = ` ProfileEmployee.employeeClass = "PERM" ` ;
} else if ( system == "registry_temp" ) {
2025-08-14 11:17:13 +07:00
tbMain = "ProfileEmployee" ;
2025-08-13 16:47:01 +07:00
condition = ` ProfileEmployee.employeeClass = "TEMP" ` ;
2025-08-14 12:20:59 +07:00
} else if ( system == "organization" ) {
2025-08-14 11:17:13 +07:00
tbMain = "OrgRoot" ;
2025-08-08 17:14:29 +07:00
const revision = await this . orgRevisionRepository . findOne ( {
2025-08-13 16:47:01 +07:00
select : [ "id" ] ,
where : { orgRevisionIsCurrent : true , orgRevisionIsDraft : false } ,
} ) ;
condition = ` OrgRoot.orgRevisionId = " ${ revision ? . id } " ` ;
2025-08-14 12:20:59 +07:00
} else if ( system == "position" ) {
tbMain = "PosMaster" ;
const revision = await this . orgRevisionRepository . findOne ( {
select : [ "id" ] ,
where : { orgRevisionIsCurrent : true , orgRevisionIsDraft : false } ,
} ) ;
condition = ` PosMaster.orgRevisionId = " ${ revision ? . id } " ` ;
2025-08-08 17:14:29 +07:00
}
2025-08-13 11:45:34 +07:00
2026-05-21 18:08:20 +07:00
let posMasterCondition : string = "1=1" ;
2026-05-21 13:44:03 +07:00
let posMasterAlias : string = "" ;
2026-05-21 11:44:28 +07:00
2026-05-21 13:44:03 +07:00
// Special handling for Profile and ProfileEmployee systems with permission filtering
2026-05-21 11:44:28 +07:00
if ( system == "registry" ) {
// Get current revision
const revision = await this . orgRevisionRepository . findOne ( {
select : [ "id" ] ,
where : { orgRevisionIsCurrent : true , orgRevisionIsDraft : false } ,
} ) ;
// Store for use in permission building
this . currentRevisionId = revision ? . id || "" ;
2026-05-21 13:44:03 +07:00
posMasterAlias = "posMaster" ;
2026-05-21 11:44:28 +07:00
// Build permission condition
2026-05-21 13:44:03 +07:00
posMasterCondition = this . buildPosMasterPermissionCondition (
request . user . accessType ,
{
dnaRootId : request.user.dnaRootId ,
dnaChild1Id : request.user.dnaChild1Id ,
dnaChild2Id : request.user.dnaChild2Id ,
dnaChild3Id : request.user.dnaChild3Id ,
dnaChild4Id : request.user.dnaChild4Id ,
} ,
posMasterAlias ,
) ;
} else if ( system == "registry_emp" ) {
// Get current revision
const revision = await this . orgRevisionRepository . findOne ( {
select : [ "id" ] ,
where : { orgRevisionIsCurrent : true , orgRevisionIsDraft : false } ,
} ) ;
// Store for use in permission building
this . currentRevisionId = revision ? . id || "" ;
posMasterAlias = "employeePosMaster" ;
// Build permission condition
posMasterCondition = this . buildPosMasterPermissionCondition (
request . user . accessType ,
{
dnaRootId : request.user.dnaRootId ,
dnaChild1Id : request.user.dnaChild1Id ,
dnaChild2Id : request.user.dnaChild2Id ,
dnaChild3Id : request.user.dnaChild3Id ,
dnaChild4Id : request.user.dnaChild4Id ,
} ,
posMasterAlias ,
) ;
} else if ( system == "registry_temp" ) {
// Get current revision
const revision = await this . orgRevisionRepository . findOne ( {
select : [ "id" ] ,
where : { orgRevisionIsCurrent : true , orgRevisionIsDraft : false } ,
} ) ;
// Store for use in permission building
this . currentRevisionId = revision ? . id || "" ;
posMasterAlias = "employeeTempPosMaster" ;
// Build permission condition
posMasterCondition = this . buildPosMasterPermissionCondition (
request . user . accessType ,
{
dnaRootId : request.user.dnaRootId ,
dnaChild1Id : request.user.dnaChild1Id ,
dnaChild2Id : request.user.dnaChild2Id ,
dnaChild3Id : request.user.dnaChild3Id ,
dnaChild4Id : request.user.dnaChild4Id ,
} ,
posMasterAlias ,
) ;
} else if ( system == "position" ) {
// Get current revision
const revision = await this . orgRevisionRepository . findOne ( {
select : [ "id" ] ,
where : { orgRevisionIsCurrent : true , orgRevisionIsDraft : false } ,
2026-05-21 11:44:28 +07:00
} ) ;
2026-05-21 13:44:03 +07:00
// Store for use in permission building
this . currentRevisionId = revision ? . id || "" ;
posMasterAlias = "PosMaster" ; // Note: Uses PascalCase to match tbMain alias
// Build permission condition
posMasterCondition = this . buildPosMasterPermissionCondition (
request . user . accessType ,
{
dnaRootId : request.user.dnaRootId ,
dnaChild1Id : request.user.dnaChild1Id ,
dnaChild2Id : request.user.dnaChild2Id ,
dnaChild3Id : request.user.dnaChild3Id ,
dnaChild4Id : request.user.dnaChild4Id ,
} ,
posMasterAlias ,
) ;
2026-05-21 11:44:28 +07:00
}
2025-08-14 11:17:13 +07:00
const repo = AppDataSource . getRepository ( tbMain ) ;
2025-08-08 17:14:29 +07:00
const metadata = repo . metadata ;
2025-08-13 16:47:01 +07:00
2025-08-08 17:14:29 +07:00
const relationMap : Record < string , string > = { } ;
metadata . relations . forEach ( ( rel ) = > {
relationMap [ rel . inverseEntityMetadata . name ] = rel . propertyName ;
} ) ;
2025-08-13 16:47:01 +07:00
2025-08-08 17:14:29 +07:00
// ดึงเฉพาะตารางรอง (ถ้าเลือกไว้)
2025-08-13 16:47:01 +07:00
let propertyOtherKey : any [ ] = [ ] ;
2025-08-08 17:14:29 +07:00
propertyOtherKey = [
2025-08-14 11:17:13 +07:00
. . . new Set ( propertyKey . map ( ( x ) = > x . split ( "." ) [ 0 ] ) . filter ( ( tb ) = > tb !== tbMain ) ) ,
2025-08-13 16:47:01 +07:00
] ;
2026-05-21 18:08:20 +07:00
// Organization hierarchy is assembled in a separate step; keep main query focused on OrgRoot only.
if ( tbMain === "OrgRoot" ) {
const orgChildTables = new Set ( [ "OrgChild1" , "OrgChild2" , "OrgChild3" , "OrgChild4" ] ) ;
propertyKey = propertyKey . filter ( ( key ) = > ! orgChildTables . has ( key . split ( "." ) [ 0 ] ) ) ;
propertyOtherKey = propertyOtherKey . filter ( ( tb ) = > ! orgChildTables . has ( tb ) ) ;
}
2026-05-21 11:07:15 +07:00
// สำหรับ Profile: ตรวจสอบฟิลด์ที่ต้องการ join และแปลง propertyKey
const profileFieldJoins : Record < string , string > = { } ; // alias -> relationName
if ( tbMain === "Profile" ) {
propertyKey = propertyKey . map ( ( key ) = > {
const [ table , field ] = key . split ( "." ) ;
if ( table === "Profile" ) {
const replacement = this . PROFILE_FIELD_REPLACEMENTS [ field ] ;
if ( replacement ) {
const alias = ` ${ table } _ ${ replacement . joinRelation } ` ;
profileFieldJoins [ alias ] = replacement . joinRelation ;
return ` ${ alias } . ${ replacement . joinField } ` ;
}
}
return key ;
} ) ;
}
2026-05-21 13:44:03 +07:00
// สำหรับ Position: ตรวจสอบฟิลด์ที่ต้องการ join และแปลง propertyKey
const positionFieldJoins : Record < string , string > = { } ; // alias -> relationName
2026-05-21 18:08:20 +07:00
if ( tbMain === "Position" || tbMain === "PosMaster" ) {
2026-05-21 13:44:03 +07:00
propertyKey = propertyKey . map ( ( key ) = > {
const [ table , field ] = key . split ( "." ) ;
if ( table === "Position" ) {
const replacement = this . POSITION_FIELD_REPLACEMENTS [ field ] ;
if ( replacement ) {
const alias = ` ${ table } _ ${ replacement . joinRelation } ` ;
positionFieldJoins [ alias ] = replacement . joinRelation ;
return ` ${ alias } . ${ replacement . joinField } ` ;
}
}
return key ;
} ) ;
}
2026-05-21 18:08:20 +07:00
// สำหรับ ProfileEmployee: ตรวจสอบฟิลด์ที่ต้องการ join และแปลง propertyKey
const profileEmployeeFieldJoins : Record < string , string > = { } ; // alias -> relationName
if ( tbMain === "ProfileEmployee" ) {
propertyKey = propertyKey . map ( ( key ) = > {
const [ table , field ] = key . split ( "." ) ;
if ( table === "ProfileEmployee" ) {
const replacement = this . PROFILEEMPLOYEE_FIELD_REPLACEMENTS [ field ] ;
if ( replacement ) {
const alias = ` ${ table } _ ${ replacement . joinRelation } ` ;
profileEmployeeFieldJoins [ alias ] = replacement . joinRelation ;
return ` ${ alias } . ${ replacement . joinField } ` ;
}
}
return key ;
} ) ;
}
2025-08-14 11:17:13 +07:00
const queryBuilder = repo . createQueryBuilder ( tbMain ) ;
2025-08-07 13:05:58 +07:00
2025-08-08 17:14:29 +07:00
// join กับตารารอง
if ( propertyOtherKey . length > 0 ) {
propertyOtherKey . forEach ( ( tb ) = > {
2026-05-21 18:08:20 +07:00
// Skip Profile join for PosMaster - it's handled separately below
if ( tbMain === "PosMaster" && tb === "Profile" ) {
return ;
}
// Skip Position join for PosMaster - it's handled separately below
if ( tbMain === "PosMaster" && tb === "Position" ) {
return ;
}
2025-08-08 17:14:29 +07:00
const relationName = relationMap [ tb ] ;
if ( relationName ) {
2026-05-21 18:08:20 +07:00
queryBuilder . leftJoin ( ` ${ tbMain } . ${ relationName } ` , tb ) ;
} else {
// Remove fields from this table from propertyKey
propertyKey = propertyKey . filter ( ( key ) = > ! key . startsWith ( ` ${ tb } . ` ) ) ;
2025-08-08 17:14:29 +07:00
}
} ) ;
}
2025-08-13 16:47:01 +07:00
2026-05-21 18:08:20 +07:00
// Check if propertyKey is empty after filtering
if ( propertyKey . length === 0 ) {
throw new HttpError (
HttpStatusCode . BAD_REQUEST ,
"ไม่พบฟิลด์ที่ต้องการแสดงผล กรุณาตรวจสอบการตั้งค่า API (ไม่สามารถ join ตารางลูกได้)" ,
) ;
}
2026-05-21 11:07:15 +07:00
// join สำหรับฟิลด์ Profile ที่ต้องการดึงค่าจากตารางอื่น
if ( tbMain === "Profile" && Object . keys ( profileFieldJoins ) . length > 0 ) {
Object . entries ( profileFieldJoins ) . forEach ( ( [ alias , relationName ] ) = > {
queryBuilder . leftJoin ( ` ${ tbMain } . ${ relationName } ` , alias ) ;
} ) ;
}
2026-05-21 13:44:03 +07:00
// join สำหรับฟิลด์ Position ที่ต้องการดึงค่าจากตารางอื่น
2026-05-21 18:08:20 +07:00
if (
( tbMain === "Position" || tbMain === "PosMaster" ) &&
Object . keys ( positionFieldJoins ) . length > 0
) {
if ( tbMain === "PosMaster" ) {
const posMasterPositionRelation = relationMap [ "Position" ] ;
if ( ! posMasterPositionRelation ) {
throw new HttpError (
HttpStatusCode . BAD_REQUEST ,
"ไม่พบความสัมพันธ์ระหว่าง PosMaster กับ Position กรุณาตรวจสอบการตั้งค่า API" ,
) ;
}
// Join PosMaster -> Position once using actual relation name from metadata
queryBuilder . leftJoin ( ` PosMaster. ${ posMasterPositionRelation } ` , "Position" ) ;
}
2026-05-21 13:44:03 +07:00
Object . entries ( positionFieldJoins ) . forEach ( ( [ alias , relationName ] ) = > {
2026-05-21 18:08:20 +07:00
if ( tbMain === "PosMaster" ) {
queryBuilder . leftJoin ( ` Position. ${ relationName } ` , alias ) ;
} else {
queryBuilder . leftJoin ( ` ${ tbMain } . ${ relationName } ` , alias ) ;
}
} ) ;
}
// join สำหรับฟิลด์ ProfileEmployee ที่ต้องการดึงค่าจากตารางอื่น
if ( tbMain === "ProfileEmployee" && Object . keys ( profileEmployeeFieldJoins ) . length > 0 ) {
Object . entries ( profileEmployeeFieldJoins ) . forEach ( ( [ alias , relationName ] ) = > {
2026-05-21 13:44:03 +07:00
queryBuilder . leftJoin ( ` ${ tbMain } . ${ relationName } ` , alias ) ;
} ) ;
}
// join สำหรับ PosMaster เมื่อต้องการดึงค่าจาก Profile (ข้อมูลคนครอง)
const posMasterProfileFields : string [ ] = [ ] ;
if ( tbMain === "PosMaster" ) {
2026-05-21 18:08:20 +07:00
// Collect Profile fields from both formats: "Profile.xxx" and "PosMaster.Profile.xxx"
const extractedProfileFields = propertyKey
. filter ( ( key ) = > key . startsWith ( "Profile." ) || key . startsWith ( "PosMaster.Profile." ) )
. map ( ( key ) = > key . replace ( /^PosMaster\.Profile\./ , "Profile." ) ) ;
posMasterProfileFields . push ( . . . new Set ( extractedProfileFields ) ) ;
// Remove Profile fields then add back normalized "Profile.xxx" form
propertyKey = propertyKey . filter (
( key ) = > ! key . startsWith ( "Profile." ) && ! key . startsWith ( "PosMaster.Profile." ) ,
) ;
propertyKey . push ( . . . posMasterProfileFields ) ;
2026-05-21 13:44:03 +07:00
}
// join PosMaster กับ Profile เมื่อมีการขอ Profile fields
if ( tbMain === "PosMaster" && posMasterProfileFields . length > 0 ) {
2026-05-21 18:08:20 +07:00
// Always join via current_holder (not next_holder) because PosMaster has two relations
// to Profile and relationMap["Profile"] would resolve to next_holder (last defined in entity)
2026-05-21 13:44:03 +07:00
queryBuilder . leftJoin ( "PosMaster.current_holder" , "Profile" ) ;
}
// join กับ posMaster/employeePosMaster/employeeTempPosMaster เพื่อกรองตามสิทธิ์การเข้าถึง
if ( ( tbMain === "Profile" || tbMain === "ProfileEmployee" ) && posMasterCondition !== "1=1" ) {
if ( tbMain === "Profile" ) {
queryBuilder . leftJoin ( "Profile.current_holders" , "posMaster" ) ;
} else if ( tbMain === "ProfileEmployee" ) {
// Use the correct relation based on posMasterAlias
if ( posMasterAlias === "employeeTempPosMaster" ) {
queryBuilder . leftJoin ( "ProfileEmployee.current_holderTemps" , "employeeTempPosMaster" ) ;
} else {
queryBuilder . leftJoin ( "ProfileEmployee.current_holders" , "employeePosMaster" ) ;
}
}
2026-05-21 11:44:28 +07:00
}
2025-08-13 16:14:44 +07:00
// // เพิ่ม Main.id เพราะจะใช้ pk ในการแมบและนับจำนวน
// if (!propertyKey.includes(`${Main}.id`)) {
// propertyKey.push(`${Main}.id`);
// }
2026-05-21 18:08:20 +07:00
// add PK - ensure propertyKey is never empty
2025-08-13 16:47:01 +07:00
let pk : string = "" ;
2025-08-13 16:14:44 +07:00
const primaryColumns = metadata . primaryColumns ;
2025-08-13 16:47:01 +07:00
primaryColumns . forEach ( ( col ) = > {
2025-08-13 16:14:44 +07:00
pk = col . propertyName ;
2025-08-14 11:17:13 +07:00
if ( ! propertyKey . includes ( ` ${ tbMain } . ${ pk } ` ) ) {
propertyKey . push ( ` ${ tbMain } . ${ pk } ` ) ;
2025-08-13 16:14:44 +07:00
}
} ) ;
2025-08-13 11:45:34 +07:00
2026-05-21 18:08:20 +07:00
let items : any [ ] = [ ] ;
let total = 0 ;
if ( tbMain === "OrgRoot" ) {
// Organization API should always return full hierarchy regardless of page/pageSize.
[ items , total ] = await queryBuilder
. select ( propertyKey )
. where ( condition )
. andWhere ( posMasterCondition )
. orderBy ( propertyKey [ 0 ] || ` ${ tbMain } . ${ pk } ` , "ASC" )
. getManyAndCount ( ) ;
} else {
[ items , total ] = await queryBuilder
. select ( propertyKey )
. where ( condition )
. andWhere ( posMasterCondition )
. orderBy ( propertyKey [ 0 ] || ` ${ tbMain } . ${ pk } ` , "ASC" )
. skip ( offset )
. take ( pageSize )
. getManyAndCount ( ) ;
}
2025-08-08 17:14:29 +07:00
2025-08-13 11:45:34 +07:00
// ลบ Main.id
2025-08-13 16:14:44 +07:00
// const results = items.map(({ id, ...x }) => x);
// const results = items.map(({ pk, ...x }) => x);
// const results = items.map(item => {
// primaryColumns.forEach(col => delete item[col.propertyName]);
// return item;
// });
2025-08-13 16:47:01 +07:00
// split object id ออกก่อน return
const data = items . map ( ( item ) = > {
2026-05-21 11:07:15 +07:00
const { [ pk ] : removedPk , . . . rest } = item ;
// สำหรับ Profile: แปลงฟิลด์ที่มาจาก join กลับเป็นชื่อเดิม
if ( tbMain === "Profile" ) {
const flattened : any = { . . . rest } ;
Object . entries ( this . PROFILE_FIELD_REPLACEMENTS ) . forEach ( ( [ nameField , config ] ) = > {
const alias = ` ${ tbMain } _ ${ config . joinRelation } ` ;
if ( rest [ alias ] && rest [ alias ] [ config . joinField ] !== undefined ) {
flattened [ nameField ] = rest [ alias ] [ config . joinField ] ;
delete flattened [ alias ] ;
}
2026-05-22 12:35:21 +07:00
// Also handle nested relation objects (e.g., "posLevel": { "posLevelName": "..." })
if (
rest [ config . joinRelation ] &&
rest [ config . joinRelation ] [ config . joinField ] !== undefined
) {
flattened [ nameField ] = rest [ config . joinRelation ] [ config . joinField ] ;
delete flattened [ config . joinRelation ] ;
}
2026-05-21 11:07:15 +07:00
} ) ;
return flattened ;
}
2026-05-21 13:44:03 +07:00
// สำหรับ Position: แปลงฟิลด์ที่มาจาก join กลับเป็นชื่อเดิม
2026-05-21 18:08:20 +07:00
if ( tbMain === "Position" || tbMain === "PosMaster" ) {
2026-05-21 13:44:03 +07:00
const flattened : any = { . . . rest } ;
Object . entries ( this . POSITION_FIELD_REPLACEMENTS ) . forEach ( ( [ nameField , config ] ) = > {
2026-05-21 18:08:20 +07:00
// Remove the original ID field
delete flattened [ config . propertyName ] ;
// Add the name field from joined table
const alias = ` Position_ ${ config . joinRelation } ` ;
if ( rest [ alias ] && rest [ alias ] [ config . joinField ] !== undefined ) {
flattened [ nameField ] = rest [ alias ] [ config . joinField ] ;
}
// Remove the joined table object
delete flattened [ alias ] ;
} ) ;
// Remove Position object if exists
if ( flattened [ "Position" ] ) {
delete flattened [ "Position" ] ;
}
return flattened ;
}
// สำหรับ ProfileEmployee: แปลงฟิลด์ที่มาจาก join กลับเป็นชื่อเดิม
if ( tbMain === "ProfileEmployee" ) {
const flattened : any = { . . . rest } ;
Object . entries ( this . PROFILEEMPLOYEE_FIELD_REPLACEMENTS ) . forEach ( ( [ nameField , config ] ) = > {
2026-05-21 13:44:03 +07:00
// Remove the original ID field
delete flattened [ config . propertyName ] ;
// Add the name field from joined table
const alias = ` ${ tbMain } _ ${ config . joinRelation } ` ;
if ( rest [ alias ] && rest [ alias ] [ config . joinField ] !== undefined ) {
flattened [ nameField ] = rest [ alias ] [ config . joinField ] ;
}
// Remove the joined table object
delete flattened [ alias ] ;
2026-05-22 12:35:21 +07:00
// Also handle nested relation objects (e.g., "posType": { "posTypeName": "..." })
if (
rest [ config . joinRelation ] &&
rest [ config . joinRelation ] [ config . joinField ] !== undefined
) {
flattened [ nameField ] = rest [ config . joinRelation ] [ config . joinField ] ;
delete flattened [ config . joinRelation ] ;
}
2026-05-21 13:44:03 +07:00
} ) ;
return flattened ;
}
// สำหรับ PosMaster: แปลงฟิลด์ Profile ที่มาจาก join กลับเป็นฟิลด์ระดับบน
if ( tbMain === "PosMaster" && posMasterProfileFields . length > 0 ) {
const flattened : any = { . . . rest } ;
2026-05-21 18:08:20 +07:00
const profileFieldNames = posMasterProfileFields
. filter ( ( field ) = > field . startsWith ( "Profile." ) )
. map ( ( field ) = > field . replace ( "Profile." , "" ) ) ;
// Extract only requested Profile fields and add top-level aliases
2026-05-21 13:44:03 +07:00
if ( rest [ "Profile" ] ) {
2026-05-21 18:08:20 +07:00
profileFieldNames . forEach ( ( fieldName ) = > {
if ( rest [ "Profile" ] [ fieldName ] !== undefined ) {
flattened [ ` profile_ ${ fieldName } ` ] = rest [ "Profile" ] [ fieldName ] ;
}
} ) ;
2026-05-21 13:44:03 +07:00
// Remove the nested Profile object
delete flattened [ "Profile" ] ;
}
return flattened ;
}
2026-05-21 18:08:20 +07:00
// สำหรับ OrgRoot: เก็บ primary key ไว้ใช้ group ข้อมูล แล้วแยก children ภายหลัง
if ( tbMain === "OrgRoot" ) {
return { __rootPk : removedPk , . . . rest } ;
}
2026-05-21 11:07:15 +07:00
return rest ;
2025-08-13 16:47:01 +07:00
} ) ;
2026-05-21 18:08:20 +07:00
let responseData : any [ ] = data ;
let responseTotal = total ;
// สำหรับ Organization: รวมข้อมูลให้เหลือ 1 root ต่อ 1 object และจัด children ตาม hierarchy
if ( tbMain === "OrgRoot" ) {
const rootVisibleFields = Array . from ( selectedFieldsByTable [ "OrgRoot" ] || [ ] ) ;
const child1VisibleFields = Array . from ( selectedFieldsByTable [ "OrgChild1" ] || [ ] ) ;
const child2VisibleFields = Array . from ( selectedFieldsByTable [ "OrgChild2" ] || [ ] ) ;
const child3VisibleFields = Array . from ( selectedFieldsByTable [ "OrgChild3" ] || [ ] ) ;
const child4VisibleFields = Array . from ( selectedFieldsByTable [ "OrgChild4" ] || [ ] ) ;
const pickVisibleFields = ( obj : any , fields : string [ ] ) = > {
const out : any = { } ;
fields . forEach ( ( field ) = > {
if ( obj [ field ] !== undefined ) {
2026-05-22 12:35:21 +07:00
// ถ้าเป็น ancestorDNA ให้เปลี่ยนชื่อเป็น id
if ( field === "ancestorDNA" ) {
out . id = obj [ field ] ;
} else {
out [ field ] = obj [ field ] ;
}
2026-05-21 18:08:20 +07:00
}
} ) ;
return out ;
} ;
const rootMap = new Map < string , any > ( ) ;
data . forEach ( ( row : any ) = > {
if ( ! row . __rootPk || rootMap . has ( row . __rootPk ) ) {
return ;
}
const rootNode = {
. . . pickVisibleFields ( row , rootVisibleFields ) ,
children : [ ] ,
} ;
rootMap . set ( row . __rootPk , rootNode ) ;
} ) ;
const rootIds = Array . from ( rootMap . keys ( ) ) ;
if ( rootIds . length > 0 ) {
const buildSelect = ( alias : string , required : string [ ] , visible : string [ ] ) = >
Array . from ( new Set ( [ . . . required , . . . visible ] ) ) . map ( ( field ) = > ` ${ alias } . ${ field } ` ) ;
const [ child1Rows , child2Rows , child3Rows , child4Rows ] = await Promise . all ( [
AppDataSource . getRepository ( OrgChild1 )
. createQueryBuilder ( "OrgChild1" )
2026-05-22 12:35:21 +07:00
. select ( buildSelect ( "OrgChild1" , [ "id" , "orgRootId" , "ancestorDNA" ] , child1VisibleFields ) )
2026-05-21 18:08:20 +07:00
. where ( "OrgChild1.orgRootId IN (:...rootIds)" , { rootIds } )
. orderBy ( "OrgChild1.id" , "ASC" )
. getMany ( ) ,
AppDataSource . getRepository ( OrgChild2 )
. createQueryBuilder ( "OrgChild2" )
. select (
2026-05-22 12:35:21 +07:00
buildSelect ( "OrgChild2" , [ "id" , "orgRootId" , "orgChild1Id" , "ancestorDNA" ] , child2VisibleFields ) ,
2026-05-21 18:08:20 +07:00
)
. where ( "OrgChild2.orgRootId IN (:...rootIds)" , { rootIds } )
. orderBy ( "OrgChild2.id" , "ASC" )
. getMany ( ) ,
AppDataSource . getRepository ( OrgChild3 )
. createQueryBuilder ( "OrgChild3" )
. select (
buildSelect (
"OrgChild3" ,
2026-05-22 12:35:21 +07:00
[ "id" , "orgRootId" , "orgChild1Id" , "orgChild2Id" , "ancestorDNA" ] ,
2026-05-21 18:08:20 +07:00
child3VisibleFields ,
) ,
)
. where ( "OrgChild3.orgRootId IN (:...rootIds)" , { rootIds } )
. orderBy ( "OrgChild3.id" , "ASC" )
. getMany ( ) ,
AppDataSource . getRepository ( OrgChild4 )
. createQueryBuilder ( "OrgChild4" )
. select (
buildSelect (
"OrgChild4" ,
2026-05-22 12:35:21 +07:00
[ "id" , "orgRootId" , "orgChild1Id" , "orgChild2Id" , "orgChild3Id" , "ancestorDNA" ] ,
2026-05-21 18:08:20 +07:00
child4VisibleFields ,
) ,
)
. where ( "OrgChild4.orgRootId IN (:...rootIds)" , { rootIds } )
. orderBy ( "OrgChild4.id" , "ASC" )
. getMany ( ) ,
] ) ;
const child1Map = new Map < string , any > ( ) ;
const child2Map = new Map < string , any > ( ) ;
const child3Map = new Map < string , any > ( ) ;
child1Rows . forEach ( ( row ) = > {
const node = {
. . . pickVisibleFields ( row , child1VisibleFields ) ,
children : [ ] ,
} ;
child1Map . set ( row . id , node ) ;
const rootNode = rootMap . get ( row . orgRootId ) ;
if ( rootNode ) {
rootNode . children . push ( node ) ;
}
} ) ;
child2Rows . forEach ( ( row ) = > {
const node = {
. . . pickVisibleFields ( row , child2VisibleFields ) ,
children : [ ] ,
} ;
child2Map . set ( row . id , node ) ;
const parent = child1Map . get ( row . orgChild1Id ) ;
if ( parent ) {
parent . children . push ( node ) ;
}
} ) ;
child3Rows . forEach ( ( row ) = > {
const node = {
. . . pickVisibleFields ( row , child3VisibleFields ) ,
children : [ ] ,
} ;
child3Map . set ( row . id , node ) ;
const parent = child2Map . get ( row . orgChild2Id ) ;
if ( parent ) {
parent . children . push ( node ) ;
}
} ) ;
child4Rows . forEach ( ( row ) = > {
const node = {
. . . pickVisibleFields ( row , child4VisibleFields ) ,
} ;
const parent = child3Map . get ( row . orgChild3Id ) ;
if ( parent ) {
if ( ! Array . isArray ( parent . children ) ) {
parent . children = [ ] ;
}
parent . children . push ( node ) ;
}
} ) ;
}
responseData = Array . from ( rootMap . values ( ) ) ;
2026-05-22 12:35:21 +07:00
// สำหรับ Organization: เปลี่ยนชื่อ ancestorDNA เป็น id
responseData = responseData . map ( ( root : any ) = > {
const renamed = this . renameAncestorDnaToId ( root ) ;
if ( Array . isArray ( renamed . children ) ) {
renamed . children = renamed . children . map ( ( child1 : any ) = > {
const renamedChild1 = this . renameAncestorDnaToId ( child1 ) ;
if ( Array . isArray ( renamedChild1 . children ) ) {
renamedChild1 . children = renamedChild1 . children . map ( ( child2 : any ) = > {
const renamedChild2 = this . renameAncestorDnaToId ( child2 ) ;
if ( Array . isArray ( renamedChild2 . children ) ) {
renamedChild2 . children = renamedChild2 . children . map ( ( child3 : any ) = > {
const renamedChild3 = this . renameAncestorDnaToId ( child3 ) ;
if ( Array . isArray ( renamedChild3 . children ) ) {
renamedChild3 . children = renamedChild3 . children . map ( ( child4 : any ) = >
this . renameAncestorDnaToId ( child4 ) ,
) ;
}
return renamedChild3 ;
} ) ;
}
return renamedChild2 ;
} ) ;
}
return renamedChild1 ;
} ) ;
}
return renamed ;
} ) ;
2026-05-21 18:08:20 +07:00
responseTotal = responseData . length ;
}
2025-08-13 16:47:01 +07:00
// save api history after query success
2025-08-13 16:14:44 +07:00
const history = {
headerApi : JSON.stringify ( {
host : request.headers.host ,
"x-api-key" : request . headers [ "x-api-key" ] ,
connection : request.headers.connection ,
accept : request.headers.accept ,
} ) ,
tokenApi : Array.isArray ( request . headers [ "x-api-key" ] )
? request . headers [ "x-api-key" ] [ 0 ] || ""
: request . headers [ "x-api-key" ] || "" ,
requestApi : ` ${ request . method } ${ request . protocol } :// ${ request . headers . host } ${ request . originalUrl || request . url } ` ,
responseApi : "OK" ,
ipApi : request.ip ,
codeApi : code ,
apiKeyId : request.user.id ,
apiNameId : apiName.id ,
createdFullName : request.user.name ,
lastUpdateFullName : request.user.name ,
} ;
await this . apiHistoryRepository . save ( history ) ;
2025-08-14 12:20:59 +07:00
2025-08-19 12:07:36 +07:00
// const results = data.map((item) => {
// const flattenedItem: any = {};
// // Extract nested object properties to top level
// Object.keys(item).forEach((key) => {
// const value = item[key];
// if (value && typeof value === "object") {
// // if (Array.isArray(value) && value.length === 1) {
// // // If array has single item, extract it as object
// // Object.assign(flattenedItem, value[0]);
// // } else
// if (!Array.isArray(value)) {
// // Merge nested object properties to top level
// Object.assign(flattenedItem, value);
// } else {
// // Keep arrays with multiple items or empty arrays as is
// flattenedItem[key] = value;
// }
// } else {
// // Keep primitive values as is
// flattenedItem[key] = value;
// }
// });
// return flattenedItem;
// });
2026-05-21 18:08:20 +07:00
return new HttpSuccess ( { data : responseData , total : responseTotal } ) ;
2025-08-07 13:05:58 +07:00
}
}