import { AppDataSource } from "../database/data-source"; import { Profile } from "./../entities/Profile"; 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"; export interface LeaveFilter { 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; } export class ProfileLeaveService { private profileEmployeeRepo: Repository; private profileRepo: Repository; private orgRootRepository: Repository; private child1Repository: Repository; private child2Repository: Repository; private child3Repository: Repository; private child4Repository: Repository; constructor() { this.profileEmployeeRepo = AppDataSource.getRepository(ProfileEmployee); this.profileRepo = AppDataSource.getRepository(Profile); 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); } /** สร้าง query สำหรับการค้นหาตามฟิลด์ต่างๆ */ buildSearchQuery(searchField?: string, type: string = "profile"): string { switch (searchField) { case "citizenId": return `${type}.citizenId LIKE :keyword`; case "position": return `${type}.position LIKE :keyword`; case "posNo": return ` (CONCAT(profileSalary.posNoAbb, profileSalary.posNo) LIKE :keyword) OR (CONCAT(profileSalary.posNoAbb, " ", profileSalary.posNo) LIKE :keyword) OR (profileSalary.posNo LIKE :keyword) `; default: return `CONCAT(${type}.prefix, ${type}.firstName, ' ', ${type}.lastName) LIKE :keyword`; } } /** สร้างเงื่อนไขการค้นหาตาม node และ nodeId */ async buildNodeCondition( node?: number, nodeId?: string, isAll?: boolean, ): Promise { let condition = "1=1"; let nodeAll = ""; const params: Record = {}; if (!node || !nodeId) { return { condition, params }; } // สร้าง nodeAll condition - เพิ่มการตรวจสอบ IS NULL if (isAll === false && node < 4) { const nextLevels = ["orgChild1", "orgChild2", "orgChild3", "orgChild4"]; nodeAll = ` AND (profileSalary.${nextLevels[node]} IS NULL)`; } 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; } } } catch (error) { console.error("Error building node condition:", error); } return { condition: condition + nodeAll, params }; } /** สร้างเงื่อนไขการค้นหาตาม permission */ async buildPermissionCondition( request: RequestWithUser, 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); } return { condition: condition + nodeAll, params }; } /** แปลงข้อมูลลูกจ้างก่อน response */ 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, }; } /** ค้นหาลูกจ้างที่พ้นจากราชการ */ async getLeaveEmployees( request: RequestWithUser, filter: LeaveFilter, ): 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), this.buildPermissionCondition(request, isAll, "SYS_REGISTRY_EMP"), ]); const searchQuery = this.buildSearchQuery(searchField, "profileEmployee"); // สร้าง main query - เปลี่ยนจาก leftJoinAndSelect เป็น leftJoin สำหรับ profileSalary const queryBuilder = this.profileEmployeeRepo .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 }, ); }), ) .andWhere("profileEmployee.employeeClass LIKE :type", { type: "PERM" }) .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) { queryBuilder.andWhere( "CONCAT(posType.posTypeShortName, ' ', posLevel.posLevelName) LIKE :keyword2", { keyword2: `${posLevel}` }, ); } if (isProbation !== undefined && isProbation !== null) { queryBuilder.andWhere(`profileEmployee.isProbation = ${isProbation}`); } if (retireType) { queryBuilder.andWhere("profileEmployee.leaveType = :retireType", { retireType }); } // เพิ่ม 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) { queryBuilder.andWhere( "CONCAT(posType.posTypeShortName, ' ', posLevel.posLevelName) LIKE :keyword2", { keyword2: `${posLevel}` }, ); } if (isProbation !== undefined && isProbation !== null) { queryBuilder.andWhere(`profile.isProbation = ${isProbation}`); } if (retireType) { queryBuilder.andWhere("profile.leaveType = :retireType", { retireType }); } // เพิ่ม 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(); // print query for debug // console.log("SQL Query:", queryBuilder.getSql()); // แปลงข้อมูลแบบ parallel const data = await Promise.all( records.map((record) => Promise.resolve(this.transformOfficerData(record))), ); return { data, total }; } }