From cd9c1721c1ccfdfe20270d316c99634c63d51793 Mon Sep 17 00:00:00 2001 From: waruneeauy Date: Sat, 4 Oct 2025 13:54:17 +0700 Subject: [PATCH] fix sort registry and filter node of registry retire --- src/controllers/ProfileController.ts | 6 +- src/controllers/ProfileEmployeeController.ts | 5 +- src/services/ProfileLeaveService.ts | 452 ++++++++++++------- 3 files changed, 301 insertions(+), 162 deletions(-) diff --git a/src/controllers/ProfileController.ts b/src/controllers/ProfileController.ts index c441d9cb..fe65b695 100644 --- a/src/controllers/ProfileController.ts +++ b/src/controllers/ProfileController.ts @@ -5742,6 +5742,8 @@ export class ProfileController extends Controller { @Query() sortBy: string = "profile.dateLeave", @Query() sort: "ASC" | "DESC" = "ASC", ) { + let _data = await new permission().PermissionOrgList(request, "SYS_REGISTRY_OFFICER"); + const { data, total } = await this.profileLeaveService.getLeaveOfficer(request, { page, pageSize, @@ -5756,6 +5758,7 @@ export class ProfileController extends Controller { retireType, sortBy, sort, + _data, }); // let _data = await new permission().PermissionOrgList(request, "SYS_REGISTRY_OFFICER"); @@ -6403,7 +6406,6 @@ export class ProfileController extends Controller { total: 1, }, }) - // ...existing code... async listProfile( @Request() request: RequestWithUser, @Query("page") page: number = 1, @@ -6587,7 +6589,7 @@ export class ProfileController extends Controller { }), ) .addSelect("CASE WHEN current_holders.posMasterNo IS NULL THEN 1 ELSE 0 END", "sort_order") - .orderBy("sort_order", sort) + .orderBy(sortBy ? sortBy : "sort_order", sort) .addOrderBy("orgRoot.orgRootOrder", sort) .addOrderBy("orgChild1.orgChild1Order", sort) .addOrderBy("orgChild2.orgChild2Order", sort) diff --git a/src/controllers/ProfileEmployeeController.ts b/src/controllers/ProfileEmployeeController.ts index ecd0d9c6..362c9419 100644 --- a/src/controllers/ProfileEmployeeController.ts +++ b/src/controllers/ProfileEmployeeController.ts @@ -2674,6 +2674,8 @@ export class ProfileEmployeeController extends Controller { @Query() sortBy: string = "profileEmployee.dateLeave", @Query() sort: "ASC" | "DESC" = "DESC", ) { + let _data = await new permission().PermissionOrgList(request, "SYS_REGISTRY_EMP"); + const { data, total } = await this.profileLeaveService.getLeaveEmployees(request, { page, pageSize, @@ -2688,6 +2690,7 @@ export class ProfileEmployeeController extends Controller { retireType, sortBy, sort, + _data, }); return new HttpSuccess({ data, total }); @@ -2915,7 +2918,7 @@ export class ProfileEmployeeController extends Controller { nodeId: nodeId, }) .addSelect("CASE WHEN current_holders.posMasterNo IS NULL THEN 1 ELSE 0 END", "sort_order") - .orderBy("sort_order", sort) + .orderBy(sortBy ? sortBy : "sort_order", sort) .addOrderBy("orgRoot.orgRootOrder", sort) .addOrderBy("orgChild1.orgChild1Order", sort) .addOrderBy("orgChild2.orgChild2Order", sort) diff --git a/src/services/ProfileLeaveService.ts b/src/services/ProfileLeaveService.ts index 42905093..0db2ff19 100644 --- a/src/services/ProfileLeaveService.ts +++ b/src/services/ProfileLeaveService.ts @@ -8,10 +8,9 @@ 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"; -export interface LeaveFilter { +interface LeaveFilter { page: number; pageSize: number; searchField?: "firstName" | "lastName" | "fullName" | "citizenId" | "position" | "posNo"; @@ -25,13 +24,41 @@ export interface LeaveFilter { retireType?: string; sortBy?: string; sort: "ASC" | "DESC"; + _data: DataPermission; } -export interface OrganizationCondition { +interface DataPermission { + root: string | null; + child1: string | null; + child2: string | null; + child3: string | null; + child4: string | null; + privilege: string; +} + +interface OrganizationCondition { condition: string; params: Record; } +interface NodeConfig { + repository: Repository; + nameField: string; + condition: string; + isAllTrue: string; + paramKey: string; + parentIdField: string; +} +interface NodeParams { + [key: string]: string | null | undefined; +} +interface OrgParentName { + orgRootName: string | null; + orgChild1Name: string | null; + orgChild2Name: string | null; + orgChild3Name: string | null; + orgChild4Name: string | null; +} export class ProfileLeaveService { private profileEmployeeRepo: Repository; private profileRepo: Repository; @@ -40,6 +67,7 @@ export class ProfileLeaveService { private child2Repository: Repository; private child3Repository: Repository; private child4Repository: Repository; + private readonly nodeConfigs: NodeConfig[]; constructor() { this.profileEmployeeRepo = AppDataSource.getRepository(ProfileEmployee); @@ -49,6 +77,49 @@ export class ProfileLeaveService { this.child2Repository = AppDataSource.getRepository(OrgChild2); this.child3Repository = AppDataSource.getRepository(OrgChild3); this.child4Repository = AppDataSource.getRepository(OrgChild4); + + this.nodeConfigs = [ + { + repository: this.orgRootRepository, + nameField: "orgRootName", + condition: "profileSalary.orgRoot = :orgRoot", + isAllTrue: "profileSalary.orgChild1 IS NULL", + paramKey: "orgRoot", + parentIdField: "", + }, + { + repository: this.child1Repository, + nameField: "orgChild1Name", + condition: "profileSalary.orgChild1 = :orgChild1", + isAllTrue: "profileSalary.orgChild2 IS NULL", + paramKey: "orgChild1", + parentIdField: "orgRootId", + }, + { + repository: this.child2Repository, + nameField: "orgChild2Name", + condition: "profileSalary.orgChild2 = :orgChild2", + isAllTrue: "profileSalary.orgChild3 IS NULL", + paramKey: "orgChild2", + parentIdField: "orgChild1Id", + }, + { + repository: this.child3Repository, + nameField: "orgChild3Name", + condition: "profileSalary.orgChild3 = :orgChild3", + isAllTrue: "profileSalary.orgChild4 IS NULL", + paramKey: "orgChild3", + parentIdField: "orgChild2Id", + }, + { + repository: this.child4Repository, + nameField: "orgChild4Name", + condition: "profileSalary.orgChild4 = :orgChild4", + isAllTrue: "", + paramKey: "orgChild4", + parentIdField: "orgChild3Id", + }, + ]; } /** สร้าง query สำหรับการค้นหาตามฟิลด์ต่างๆ */ @@ -69,132 +140,198 @@ export class ProfileLeaveService { } } - /** สร้างเงื่อนไขการค้นหาตาม node และ nodeId */ - async buildNodeCondition( - node?: number, - nodeId?: string, - isAll?: boolean, - ): Promise { - let condition = "1=1"; - let nodeAll = ""; - const params: Record = {}; + async findOrgNodeParentAll(node: number, nodeId: string): Promise { + const orgNames: OrgParentName = { + orgRootName: null, + orgChild1Name: null, + orgChild2Name: null, + orgChild3Name: null, + orgChild4Name: null, + }; - if (!node || !nodeId) { - return { condition, params }; + if (!nodeId || node < 0 || node >= this.nodeConfigs.length) { + return orgNames; } - // สร้าง nodeAll condition - เพิ่มการตรวจสอบ IS NULL - if (isAll === false && node < 4) { - const nextLevels = ["orgChild1", "orgChild2", "orgChild3", "orgChild4"]; - nodeAll = ` AND (profileSalary.${nextLevels[node]} IS NULL)`; - } + let currentNode = node; + let currentNodeId = nodeId; - try { - switch (node) { - case 0: { - const orgRoot = await this.orgRootRepository.findOne({ where: { id: nodeId } }); - if (orgRoot) { - condition = "(profileSalary.orgRoot = :orgRoot)"; - params.orgRoot = orgRoot.orgRootName; - } - break; - } - case 1: { - const orgChild1 = await this.child1Repository.findOne({ where: { id: nodeId } }); - if (orgChild1) { - condition = "(profileSalary.orgChild1 = :orgChild1)"; - params.orgChild1 = orgChild1.orgChild1Name; - } - break; - } - case 2: { - const orgChild2 = await this.child2Repository.findOne({ where: { id: nodeId } }); - if (orgChild2) { - condition = "(profileSalary.orgChild2 = :orgChild2)"; - params.orgChild2 = orgChild2.orgChild2Name; - } - break; - } - case 3: { - const orgChild3 = await this.child3Repository.findOne({ where: { id: nodeId } }); - if (orgChild3) { - condition = "(profileSalary.orgChild3 = :orgChild3)"; - params.orgChild3 = orgChild3.orgChild3Name; - } - break; - } - case 4: { - const orgChild4 = await this.child4Repository.findOne({ where: { id: nodeId } }); - if (orgChild4) { - condition = "(profileSalary.orgChild4 = :orgChild4)"; - params.orgChild4 = orgChild4.orgChild4Name; - } - break; - } + while (currentNode >= 0) { + const config = this.nodeConfigs[currentNode]; + + // Build select fields dynamically, excluding empty parentIdField + const selectFields = [config.nameField, "id"]; + if (config.parentIdField && config.parentIdField.trim() !== "") { + selectFields.push(config.parentIdField); + } + + const orgData = await config.repository.findOne({ + where: { id: currentNodeId }, + select: selectFields, + }); + + if (!orgData) { + break; + } + + const orgName = orgData[config.nameField] || null; + if (orgName) { + orgNames[config.nameField as keyof OrgParentName] = orgName; + } + + // Check if parentIdField exists and is not empty before accessing it + if (config.parentIdField && config.parentIdField.trim() !== "") { + currentNodeId = orgData[config.parentIdField]; + currentNode -= 1; + } else { + // If no parent field (root level), break the loop + break; } - } catch (error) { - console.error("Error building node condition:", error); } - return { condition: condition + nodeAll, params }; + return orgNames; } - /** สร้างเงื่อนไขการค้นหาตาม permission */ - async buildPermissionCondition( - request: RequestWithUser, + /** สร้างเงื่อนไขการค้นหาตาม node และ nodeId และเช็คกับ permission */ + async buildNodeCondition( + node: number, + nodeId: string, isAll?: boolean, - permissionType: string = "SYS_REGISTRY_OFFICER", ): Promise { - const _data = await new permission().PermissionOrgList(request, permissionType); - let condition = "1=1"; - let nodeAll = ""; - const params: Record = {}; - - 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); + // Early return สำหรับ edge cases + if (!nodeId || node < 0 || node >= this.nodeConfigs.length) { + return { condition: "1=1", params: {} }; } - return { condition: condition + nodeAll, params }; + let nodeCondition = ""; + let params: NodeParams = {}; + + const orgLists = await this.findOrgNodeParentAll(node, nodeId); + console.log("Org Hierarchy for Node Condition:", orgLists); + await Promise.all( + this.nodeConfigs.map(async (config, index) => { + if (index <= node) { + const orgName = orgLists[config.nameField as keyof OrgParentName] || null; + if (orgName) { + nodeCondition += index > 0 ? ` AND ${config.condition}` : config.condition; + nodeCondition += isAll === false && config.isAllTrue ? ` AND ${config.isAllTrue}` : ""; + params[config.paramKey] = orgName; + } + } + }), + ); + + return { + condition: nodeCondition, + params, + }; + } + + async getOrgNameFromId(orgIds: { + root: string | null; + child1: string | null; + child2: string | null; + child3: string | null; + child4: string | null; + }): Promise { + const orgNames: OrgParentName = { + orgRootName: null, + orgChild1Name: null, + orgChild2Name: null, + orgChild3Name: null, + orgChild4Name: null, + }; + if (orgIds.root) { + const rootName = await this.orgRootRepository.findOne({ + where: { id: orgIds.root }, + select: ["orgRootName"], + }); + orgNames.orgRootName = rootName ? rootName.orgRootName : null; + } + if (orgIds.child1) { + const child1 = await this.child1Repository.findOne({ + where: { id: orgIds.child1 }, + select: ["orgChild1Name"], + }); + orgNames.orgChild1Name = child1 ? child1.orgChild1Name : null; + } + + if (orgIds.child2) { + const child2 = await this.child2Repository.findOne({ + where: { id: orgIds.child2 }, + select: ["orgChild2Name"], + }); + orgNames.orgChild2Name = child2 ? child2.orgChild2Name : null; + } + + if (orgIds.child3) { + const child3 = await this.child3Repository.findOne({ + where: { id: orgIds.child3 }, + select: ["orgChild3Name"], + }); + orgNames.orgChild3Name = child3 ? child3.orgChild3Name : null; + } + + if (orgIds.child4) { + const child4 = await this.child4Repository.findOne({ + where: { id: orgIds.child4 }, + select: ["orgChild4Name"], + }); + orgNames.orgChild4Name = child4 ? child4.orgChild4Name : null; + } + + return orgNames; + } + + /** สร้างเงื่อนไขการค้นหาตาม node และ nodeId และเช็คกับ permission */ + async buildPermissionCondition( + _data: DataPermission, + isAll?: boolean, + ): Promise { + // Early return สำหรับ OWNER privilege + if (_data.privilege === "OWNER") { + return { condition: "1=1", params: {} }; + } + + // const nodeFields = ["root", "child1", "child2", "child3", "child4"] as const; + let nodeCondition = ""; + let params: NodeParams = {}; + + const orgLists = await this.getOrgNameFromId({ + root: _data.root, + child1: _data.child1, + child2: _data.child2, + child3: _data.child3, + child4: _data.child4, + }); + // console.log("Org Hierarchy for Permission Condition:", orgLists); + + // check orgLists has at least one non-null value + if ( + !orgLists.orgRootName && + !orgLists.orgChild1Name && + !orgLists.orgChild2Name && + !orgLists.orgChild3Name && + !orgLists.orgChild4Name + ) { + return { condition: "1=0", params: {} }; // no access + } + + await Promise.all( + this.nodeConfigs.map(async (config, index) => { + const orgName = orgLists[config.nameField as keyof OrgParentName] || null; + if (orgName) { + nodeCondition += index > 0 ? ` AND ${config.condition}` : config.condition; + nodeCondition += isAll === false && config.isAllTrue ? ` AND ${config.isAllTrue}` : ""; + params[config.paramKey] = orgName; + } + }), + ); + + return { + condition: nodeCondition, + params, + }; } /** แปลงข้อมูลลูกจ้างก่อน response */ @@ -338,14 +475,9 @@ export class ProfileLeaveService { retireType, sortBy = "profileEmployee.dateLeave", sort, + _data, } = filter; - // สร้าง query conditions แบบ parallel - const [nodeCondition, permissionCondition] = await Promise.all([ - this.buildNodeCondition(node, nodeId, isAll), - this.buildPermissionCondition(request, isAll, "SYS_REGISTRY_EMP"), - ]); - const searchQuery = this.buildSearchQuery(searchField, "profileEmployee"); // สร้าง main query - เปลี่ยนจาก leftJoinAndSelect เป็น leftJoin สำหรับ profileSalary @@ -381,14 +513,9 @@ export class ProfileLeaveService { .andWhere("profileEmployee.employeeClass LIKE :type", { type: "PERM" }) .andWhere( new Brackets((qb) => { - qb.orWhere( - searchKeyword != undefined && searchKeyword != null && searchKeyword != "" - ? searchQuery - : "1=1", - { - keyword: `%${searchKeyword}%`, - }, - ); + qb.orWhere(searchKeyword && searchKeyword != "" ? searchQuery : "1=1", { + keyword: `%${searchKeyword}%`, + }); }), ); @@ -404,7 +531,7 @@ export class ProfileLeaveService { ); } - if (isProbation !== undefined && isProbation !== null) { + if (isProbation) { queryBuilder.andWhere(`profileEmployee.isProbation = ${isProbation}`); } @@ -412,13 +539,21 @@ export class ProfileLeaveService { queryBuilder.andWhere("profileEmployee.leaveType = :retireType", { retireType }); } - // เพิ่ม permission และ node conditions - queryBuilder - .andWhere(permissionCondition.condition, permissionCondition.params) - .andWhere(nodeCondition.condition, nodeCondition.params); + if (node !== null && node !== undefined && nodeId) { + const [nodeCondition, permissionCondition] = await Promise.all([ + this.buildNodeCondition(node, nodeId, isAll), + this.buildPermissionCondition(_data, isAll), + ]); + // console.log("Permission Condition:", permissionCondition); + // console.log("Node Condition:", nodeCondition); + + queryBuilder.andWhere(nodeCondition.condition, nodeCondition.params); + + if (_data.privilege !== "OWNER") { + queryBuilder.andWhere(permissionCondition.condition, permissionCondition.params); + } + } - console.log("Permission Condition:", permissionCondition); - console.log("Node Condition:", nodeCondition); // เพิ่ม sorting และ pagination queryBuilder .orderBy(sortBy, sort) @@ -430,7 +565,6 @@ export class ProfileLeaveService { // print query for debug // console.log("SQL Query:", queryBuilder.getSql()); - // แปลงข้อมูลแบบ parallel const data = await Promise.all( records.map((record) => Promise.resolve(this.transformEmployeeData(record))), ); @@ -441,7 +575,7 @@ export class ProfileLeaveService { /** * แปลงข้อมูลลูกจ้างก่อน response */ - transformOfficerData(employee: Profile): any { + transformOfficerData(employee: Profile) { // ตรวจสอบว่า profileSalary มีข้อมูลหรือไม่ const salary = employee.profileSalary && employee.profileSalary.length > 0 @@ -512,14 +646,9 @@ export class ProfileLeaveService { retireType, sortBy = "profile.dateLeave", sort, + _data, } = 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 @@ -554,18 +683,12 @@ export class ProfileLeaveService { ) .andWhere( new Brackets((qb) => { - qb.orWhere( - searchKeyword != undefined && searchKeyword != null && searchKeyword != "" - ? searchQuery - : "1=1", - { - keyword: `%${searchKeyword}%`, - }, - ); + qb.orWhere(searchKeyword && searchKeyword != "" ? searchQuery : "1=1", { + keyword: `%${searchKeyword}%`, + }); }), ); - // เพิ่มเงื่อนไขการค้นหา if (posType) { queryBuilder.andWhere("posType.posTypeName LIKE :keyword1", { keyword1: `${posType}` }); } @@ -574,7 +697,7 @@ export class ProfileLeaveService { queryBuilder.andWhere("posLevel.posLevelName LIKE :keyword2", { keyword2: `${posLevel}` }); } - if (isProbation !== undefined && isProbation !== null) { + if (isProbation) { queryBuilder.andWhere(`profile.isProbation = ${isProbation}`); } @@ -583,9 +706,21 @@ export class ProfileLeaveService { } // เพิ่ม permission และ node conditions - queryBuilder - .andWhere(permissionCondition.condition, permissionCondition.params) - .andWhere(nodeCondition.condition, nodeCondition.params); + if (node !== null && node !== undefined && nodeId) { + // สร้าง query conditions แบบ parallel + const [nodeCondition, permissionCondition] = await Promise.all([ + this.buildNodeCondition(node, nodeId, isAll), + this.buildPermissionCondition(_data, isAll), + ]); + console.log("Permission Condition:", permissionCondition); + console.log("Node Condition:", nodeCondition); + + queryBuilder.andWhere(nodeCondition.condition, nodeCondition.params); + + if (_data.privilege !== "OWNER") { + queryBuilder.andWhere(permissionCondition.condition, permissionCondition.params); + } + } // เพิ่ม sorting และ pagination queryBuilder @@ -598,7 +733,6 @@ export class ProfileLeaveService { // print query for debug // console.log("SQL Query:", queryBuilder.getSql()); - // แปลงข้อมูลแบบ parallel const data = await Promise.all( records.map((record) => Promise.resolve(this.transformOfficerData(record))), );