Merge branch 'adiDev' into develop
All checks were successful
Build & Deploy on Dev / build (push) Successful in 55s
All checks were successful
Build & Deploy on Dev / build (push) Successful in 55s
This commit is contained in:
commit
4c9ed3d317
3 changed files with 1225 additions and 1155 deletions
|
|
@ -6300,7 +6300,7 @@ 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");
|
||||
let _data = await new permission().PermissionOrgList(request, "SYS_REGISTRY_RETIRE_OFFICER");
|
||||
|
||||
const { data, total } = await this.profileLeaveService.getLeaveOfficer(request, {
|
||||
page,
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -1,12 +1,13 @@
|
|||
import { AppDataSource } from "../database/data-source";
|
||||
import { Profile } from "./../entities/Profile";
|
||||
import { ProfileEmployee } from "../entities/ProfileEmployee";
|
||||
import { ProfileSalary } from "./../entities/ProfileSalary";
|
||||
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 { Brackets, In, Repository } from "typeorm";
|
||||
import Extension from "../interfaces/extension";
|
||||
import { RequestWithUser } from "../middlewares/user";
|
||||
|
||||
|
|
@ -62,6 +63,7 @@ interface OrgParentName {
|
|||
export class ProfileLeaveService {
|
||||
private profileEmployeeRepo: Repository<ProfileEmployee>;
|
||||
private profileRepo: Repository<Profile>;
|
||||
private profileSalaryRepo: Repository<ProfileSalary>;
|
||||
private orgRootRepository: Repository<OrgRoot>;
|
||||
private child1Repository: Repository<OrgChild1>;
|
||||
private child2Repository: Repository<OrgChild2>;
|
||||
|
|
@ -72,6 +74,7 @@ export class ProfileLeaveService {
|
|||
constructor() {
|
||||
this.profileEmployeeRepo = AppDataSource.getRepository(ProfileEmployee);
|
||||
this.profileRepo = AppDataSource.getRepository(Profile);
|
||||
this.profileSalaryRepo = AppDataSource.getRepository(ProfileSalary);
|
||||
this.orgRootRepository = AppDataSource.getRepository(OrgRoot);
|
||||
this.child1Repository = AppDataSource.getRepository(OrgChild1);
|
||||
this.child2Repository = AppDataSource.getRepository(OrgChild2);
|
||||
|
|
@ -207,19 +210,16 @@ export class ProfileLeaveService {
|
|||
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;
|
||||
}
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
for (let index = 0; index <= node; index++) {
|
||||
const config = this.nodeConfigs[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,
|
||||
|
|
@ -234,53 +234,31 @@ export class ProfileLeaveService {
|
|||
child3: string | null;
|
||||
child4: string | null;
|
||||
}): Promise<OrgParentName> {
|
||||
const orgNames: OrgParentName = {
|
||||
orgRootName: null,
|
||||
orgChild1Name: null,
|
||||
orgChild2Name: null,
|
||||
orgChild3Name: null,
|
||||
orgChild4Name: null,
|
||||
const [rootName, child1, child2, child3, child4] = await Promise.all([
|
||||
orgIds.root
|
||||
? this.orgRootRepository.findOne({ where: { id: orgIds.root }, select: ["orgRootName"] })
|
||||
: Promise.resolve(null),
|
||||
orgIds.child1
|
||||
? this.child1Repository.findOne({ where: { id: orgIds.child1 }, select: ["orgChild1Name"] })
|
||||
: Promise.resolve(null),
|
||||
orgIds.child2
|
||||
? this.child2Repository.findOne({ where: { id: orgIds.child2 }, select: ["orgChild2Name"] })
|
||||
: Promise.resolve(null),
|
||||
orgIds.child3
|
||||
? this.child3Repository.findOne({ where: { id: orgIds.child3 }, select: ["orgChild3Name"] })
|
||||
: Promise.resolve(null),
|
||||
orgIds.child4
|
||||
? this.child4Repository.findOne({ where: { id: orgIds.child4 }, select: ["orgChild4Name"] })
|
||||
: Promise.resolve(null),
|
||||
]);
|
||||
|
||||
return {
|
||||
orgRootName: rootName?.orgRootName ?? null,
|
||||
orgChild1Name: child1?.orgChild1Name ?? null,
|
||||
orgChild2Name: child2?.orgChild2Name ?? null,
|
||||
orgChild3Name: child3?.orgChild3Name ?? null,
|
||||
orgChild4Name: child4?.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 */
|
||||
|
|
@ -317,16 +295,15 @@ export class ProfileLeaveService {
|
|||
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;
|
||||
}
|
||||
}),
|
||||
);
|
||||
for (let index = 0; index < this.nodeConfigs.length; index++) {
|
||||
const config = this.nodeConfigs[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,
|
||||
|
|
@ -478,97 +455,146 @@ export class ProfileLeaveService {
|
|||
_data,
|
||||
} = filter;
|
||||
|
||||
const t0 = Date.now();
|
||||
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(
|
||||
// สร้าง base WHERE conditions แชร์ระหว่าง count/id/data query
|
||||
const baseWhere = (qb: any) => {
|
||||
qb.where(
|
||||
new Brackets((qb2) => {
|
||||
qb2.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 && searchKeyword != "" ? searchQuery : "1=1", {
|
||||
keyword: `%${searchKeyword}%`,
|
||||
});
|
||||
}),
|
||||
);
|
||||
.andWhere("profileEmployee.employeeClass LIKE :type", { type: "PERM" })
|
||||
.andWhere(
|
||||
new Brackets((qb2) => {
|
||||
qb2.orWhere(searchKeyword && 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) {
|
||||
queryBuilder.andWhere(`profileEmployee.isProbation = ${isProbation}`);
|
||||
}
|
||||
|
||||
if (retireType) {
|
||||
queryBuilder.andWhere("profileEmployee.leaveType = :retireType", { retireType });
|
||||
}
|
||||
|
||||
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" && _data.privilege !== "PARENT") {
|
||||
queryBuilder.andWhere(permissionCondition.condition, permissionCondition.params);
|
||||
if (posType) {
|
||||
qb.andWhere("posType.posTypeName LIKE :keyword1", { keyword1: `${posType}` });
|
||||
}
|
||||
if (posLevel) {
|
||||
qb.andWhere(
|
||||
"CONCAT(posType.posTypeShortName, ' ', posLevel.posLevelName) LIKE :keyword2",
|
||||
{ keyword2: `${posLevel}` },
|
||||
);
|
||||
}
|
||||
if (isProbation !== undefined && isProbation !== null) {
|
||||
qb.andWhere("profileEmployee.isProbation = :isProbation", { isProbation });
|
||||
}
|
||||
if (retireType) {
|
||||
qb.andWhere("profileEmployee.leaveType = :retireType", { retireType });
|
||||
}
|
||||
};
|
||||
|
||||
// Compute permission/node conditions เพียงครั้งเดียว
|
||||
const conditions: { condition: string; params: Record<string, any> }[] = [];
|
||||
if (_data.privilege !== "OWNER" && _data.privilege !== "PARENT") {
|
||||
conditions.push(await this.buildPermissionCondition(_data, isAll));
|
||||
}
|
||||
if (node !== null && node !== undefined && nodeId) {
|
||||
conditions.push(await this.buildNodeCondition(node, nodeId, isAll));
|
||||
}
|
||||
const applyConditions = (qb: any) => {
|
||||
for (const cond of conditions) {
|
||||
qb.andWhere(cond.condition, cond.params);
|
||||
}
|
||||
};
|
||||
|
||||
// console.log(`[ProfileLeaveService] getLeaveEmployees conditions took ${Date.now() - t0}ms`);
|
||||
|
||||
// สร้าง salary EXISTS filter (ใช้ซ้ำทั้ง step1, step2)
|
||||
const applySalaryFilter = (qb: any) => {
|
||||
if (conditions.length > 0) {
|
||||
let existsCond = "profileSalary.positionName != :notRetire";
|
||||
const existsParams: Record<string, any> = { notRetire: "เกษียณอายุราชการ" };
|
||||
for (const cond of conditions) {
|
||||
existsCond += ` AND ${cond.condition}`;
|
||||
Object.assign(existsParams, cond.params);
|
||||
}
|
||||
qb.andWhere(
|
||||
`EXISTS (SELECT 1 FROM profileSalary WHERE profileEmployeeId = profileEmployee.id AND ${existsCond} AND profileSalary.\`order\` = (SELECT MAX(ps.\`order\`) FROM profileSalary ps WHERE ps.profileEmployeeId = profileEmployee.id AND ps.positionName != :notRetire2))`,
|
||||
{ ...existsParams, notRetire2: "เกษียณอายุราชการ" }
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
// Step 1: Count query
|
||||
const countQb = this.profileEmployeeRepo
|
||||
.createQueryBuilder("profileEmployee")
|
||||
.leftJoinAndSelect("profileEmployee.posLevel", "posLevel")
|
||||
.leftJoinAndSelect("profileEmployee.posType", "posType");
|
||||
baseWhere(countQb);
|
||||
applySalaryFilter(countQb);
|
||||
const total = await countQb.getCount();
|
||||
|
||||
// console.log(`[ProfileLeaveService] getLeaveEmployees count took ${Date.now() - t0}ms, total=${total}`);
|
||||
|
||||
// Step 2: ดึงเฉพาะ profileEmployee IDs ที่ผ่านเงื่อนไข
|
||||
const idQb = this.profileEmployeeRepo
|
||||
.createQueryBuilder("profileEmployee")
|
||||
.select(["profileEmployee.id"])
|
||||
.leftJoin("profileEmployee.posLevel", "posLevel")
|
||||
.leftJoin("profileEmployee.posType", "posType");
|
||||
baseWhere(idQb);
|
||||
applySalaryFilter(idQb);
|
||||
idQb.orderBy(sortBy, sort).skip((page - 1) * pageSize).take(pageSize);
|
||||
const rawIds = await idQb.getRawMany();
|
||||
const employeeIds = rawIds.map((r) => r.profileEmployee_id);
|
||||
|
||||
// console.log(`[ProfileLeaveService] getLeaveEmployees ids took ${Date.now() - t0}ms, ids=${employeeIds.length}`);
|
||||
|
||||
if (employeeIds.length === 0) {
|
||||
return { data: [], total };
|
||||
}
|
||||
|
||||
// เพิ่ม sorting และ pagination
|
||||
queryBuilder
|
||||
.orderBy(sortBy, sort)
|
||||
.skip((page - 1) * pageSize)
|
||||
.take(pageSize);
|
||||
// Step 3: Load full data โดยไม่ JOIN salary
|
||||
const records = await this.profileEmployeeRepo.find({
|
||||
where: { id: In(employeeIds) },
|
||||
relations: ["posLevel", "posType", "profileEmployeeEmployment"],
|
||||
order: { [sortBy.split(".")[1]]: sort } as any,
|
||||
});
|
||||
|
||||
const [records, total] = await queryBuilder.getManyAndCount();
|
||||
// Step 4: Load salary เฉพาะ row ที่มี order สูงสุดต่อ profileEmployeeId (INNER JOIN + GROUP BY)
|
||||
const salaries = await this.profileSalaryRepo
|
||||
.createQueryBuilder("ps")
|
||||
.innerJoin(
|
||||
(subQuery) =>
|
||||
subQuery
|
||||
.select("ps2.profileEmployeeId", "pid")
|
||||
.addSelect("MAX(ps2.order)", "maxOrd")
|
||||
.from(ProfileSalary, "ps2")
|
||||
.where("ps2.profileEmployeeId IN (:...employeeIds)", { employeeIds })
|
||||
.andWhere("ps2.positionName != :notRetire", { notRetire: "เกษียณอายุราชการ" })
|
||||
.groupBy("ps2.profileEmployeeId"),
|
||||
"latest",
|
||||
"latest.pid = ps.profileEmployeeId AND ps.order = latest.maxOrd"
|
||||
)
|
||||
.getMany();
|
||||
|
||||
// print query for debug
|
||||
// console.log("SQL Query:", queryBuilder.getSql());
|
||||
// สร้าง map: profileEmployeeId → salary ที่มี order สูงสุด
|
||||
const salaryMap = new Map<string, ProfileSalary>();
|
||||
for (const s of salaries) {
|
||||
salaryMap.set(s.profileEmployeeId, s);
|
||||
}
|
||||
|
||||
const data = await Promise.all(
|
||||
records.map((record) => Promise.resolve(this.transformEmployeeData(record))),
|
||||
);
|
||||
// แปลงข้อมูลพร้อม salary
|
||||
const data = records.map((record) => {
|
||||
const salary = salaryMap.get(record.id);
|
||||
if (salary) {
|
||||
(record as any).profileSalary = [salary];
|
||||
}
|
||||
return this.transformEmployeeData(record);
|
||||
});
|
||||
|
||||
// console.log(`[ProfileLeaveService] getLeaveEmployees total took ${Date.now() - t0}ms, total=${total}`);
|
||||
return { data, total };
|
||||
}
|
||||
|
||||
|
|
@ -649,94 +675,143 @@ export class ProfileLeaveService {
|
|||
_data,
|
||||
} = filter;
|
||||
|
||||
const t0 = Date.now();
|
||||
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(
|
||||
// สร้าง base WHERE conditions แชร์ระหว่าง count/id/data query
|
||||
const baseWhere = (qb: any) => {
|
||||
qb.where(
|
||||
new Brackets((qb2) => {
|
||||
qb2.where("profile.isLeave = :isLeave", { isLeave: true }).orWhere(
|
||||
"profile.isRetirement = :isRetirement",
|
||||
{ isRetirement: true },
|
||||
);
|
||||
}),
|
||||
)
|
||||
.andWhere(
|
||||
new Brackets((qb) => {
|
||||
qb.orWhere(searchKeyword && searchKeyword != "" ? searchQuery : "1=1", {
|
||||
).andWhere(
|
||||
new Brackets((qb2) => {
|
||||
qb2.orWhere(searchKeyword && searchKeyword != "" ? searchQuery : "1=1", {
|
||||
keyword: `%${searchKeyword}%`,
|
||||
});
|
||||
}),
|
||||
);
|
||||
|
||||
if (posType) {
|
||||
queryBuilder.andWhere("posType.posTypeName LIKE :keyword1", { keyword1: `${posType}` });
|
||||
}
|
||||
|
||||
if (posLevel) {
|
||||
queryBuilder.andWhere("posLevel.posLevelName LIKE :keyword2", { keyword2: `${posLevel}` });
|
||||
}
|
||||
|
||||
if (isProbation) {
|
||||
queryBuilder.andWhere(`profile.isProbation = ${isProbation}`);
|
||||
}
|
||||
|
||||
if (retireType) {
|
||||
queryBuilder.andWhere("profile.leaveType = :retireType", { retireType });
|
||||
}
|
||||
|
||||
// เพิ่ม permission และ node conditions
|
||||
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" && _data.privilege !== "PARENT") {
|
||||
queryBuilder.andWhere(permissionCondition.condition, permissionCondition.params);
|
||||
if (posType) {
|
||||
qb.andWhere("posType.posTypeName LIKE :keyword1", { keyword1: `${posType}` });
|
||||
}
|
||||
if (posLevel) {
|
||||
qb.andWhere("posLevel.posLevelName LIKE :keyword2", { keyword2: `${posLevel}` });
|
||||
}
|
||||
if (isProbation !== undefined && isProbation !== null) {
|
||||
qb.andWhere("profile.isProbation = :isProbation", { isProbation });
|
||||
}
|
||||
if (retireType) {
|
||||
qb.andWhere("profile.leaveType = :retireType", { retireType });
|
||||
}
|
||||
};
|
||||
|
||||
// Compute permission/node conditions เพียงครั้งเดียว
|
||||
const conditions: { condition: string; params: Record<string, any> }[] = [];
|
||||
if (_data.privilege !== "OWNER" && _data.privilege !== "PARENT") {
|
||||
conditions.push(await this.buildPermissionCondition(_data, isAll));
|
||||
}
|
||||
if (node !== null && node !== undefined && nodeId) {
|
||||
conditions.push(await this.buildNodeCondition(node, nodeId, isAll));
|
||||
}
|
||||
const applyConditions = (qb: any) => {
|
||||
for (const cond of conditions) {
|
||||
qb.andWhere(cond.condition, cond.params);
|
||||
}
|
||||
};
|
||||
|
||||
// console.log(`[ProfileLeaveService] getLeaveOfficer conditions took ${Date.now() - t0}ms`);
|
||||
|
||||
// สร้าง salary EXISTS filter (ใช้ซ้ำทั้ง step1, step2)
|
||||
const applySalaryFilter = (qb: any) => {
|
||||
if (conditions.length > 0) {
|
||||
let existsCond = "profileSalary.positionName != :notRetire";
|
||||
const existsParams: Record<string, any> = { notRetire: "เกษียณอายุราชการ" };
|
||||
for (const cond of conditions) {
|
||||
existsCond += ` AND ${cond.condition}`;
|
||||
Object.assign(existsParams, cond.params);
|
||||
}
|
||||
qb.andWhere(
|
||||
`EXISTS (SELECT 1 FROM profileSalary WHERE profileId = profile.id AND ${existsCond} AND profileSalary.\`order\` = (SELECT MAX(ps.\`order\`) FROM profileSalary ps WHERE ps.profileId = profile.id AND ps.positionName != :notRetire2))`,
|
||||
{ ...existsParams, notRetire2: "เกษียณอายุราชการ" }
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
// Step 1: Count query
|
||||
const countQb = this.profileRepo
|
||||
.createQueryBuilder("profile")
|
||||
.leftJoinAndSelect("profile.posLevel", "posLevel")
|
||||
.leftJoinAndSelect("profile.posType", "posType");
|
||||
baseWhere(countQb);
|
||||
applySalaryFilter(countQb);
|
||||
const total = await countQb.getCount();
|
||||
|
||||
// console.log(`[ProfileLeaveService] getLeaveOfficer count took ${Date.now() - t0}ms, total=${total}`);
|
||||
|
||||
// Step 2: ดึงเฉพาะ profile IDs ที่ผ่านเงื่อนไข
|
||||
const idQb = this.profileRepo
|
||||
.createQueryBuilder("profile")
|
||||
.select(["profile.id"])
|
||||
.leftJoin("profile.posLevel", "posLevel")
|
||||
.leftJoin("profile.posType", "posType");
|
||||
baseWhere(idQb);
|
||||
applySalaryFilter(idQb);
|
||||
idQb.orderBy(sortBy, sort).skip((page - 1) * pageSize).take(pageSize);
|
||||
const rawIds = await idQb.getRawMany();
|
||||
const profileIds = rawIds.map((r) => r.profile_id);
|
||||
|
||||
// console.log(`[ProfileLeaveService] getLeaveOfficer ids took ${Date.now() - t0}ms, ids=${profileIds.length}`);
|
||||
|
||||
if (profileIds.length === 0) {
|
||||
return { data: [], total };
|
||||
}
|
||||
|
||||
// เพิ่ม sorting และ pagination
|
||||
queryBuilder
|
||||
.orderBy(sortBy, sort)
|
||||
.skip((page - 1) * pageSize)
|
||||
.take(pageSize);
|
||||
// Step 3: Load full data โดยไม่ JOIN salary
|
||||
const records = await this.profileRepo.find({
|
||||
where: { id: In(profileIds) },
|
||||
relations: ["posLevel", "posType"],
|
||||
order: { [sortBy.split(".")[1]]: sort } as any,
|
||||
});
|
||||
// console.log(`[ProfileLeaveService] getLeaveOfficer step3 (load profiles) took ${Date.now() - t0}ms`);
|
||||
|
||||
const [records, total] = await queryBuilder.getManyAndCount();
|
||||
// Step 4: Load salary เฉพาะ row ที่มี order สูงสุดต่อ profileId (INNER JOIN + GROUP BY)
|
||||
const salaries = await this.profileSalaryRepo
|
||||
.createQueryBuilder("ps")
|
||||
.innerJoin(
|
||||
(subQuery) =>
|
||||
subQuery
|
||||
.select("ps2.profileId", "pid")
|
||||
.addSelect("MAX(ps2.order)", "maxOrd")
|
||||
.from(ProfileSalary, "ps2")
|
||||
.where("ps2.profileId IN (:...profileIds)", { profileIds })
|
||||
.andWhere("ps2.positionName != :notRetire", { notRetire: "เกษียณอายุราชการ" })
|
||||
.groupBy("ps2.profileId"),
|
||||
"latest",
|
||||
"latest.pid = ps.profileId AND ps.order = latest.maxOrd"
|
||||
)
|
||||
.getMany();
|
||||
// console.log(`[ProfileLeaveService] getLeaveOfficer step4 (load salaries) took ${Date.now() - t0}ms, salary rows=${salaries.length}`);
|
||||
|
||||
// print query for debug
|
||||
// console.log("SQL Query:", queryBuilder.getSql());
|
||||
// สร้าง map: profileId → salary ที่มี order สูงสุด
|
||||
const salaryMap = new Map<string, ProfileSalary>();
|
||||
for (const s of salaries) {
|
||||
salaryMap.set(s.profileId, s);
|
||||
}
|
||||
|
||||
const data = await Promise.all(
|
||||
records.map((record) => Promise.resolve(this.transformOfficerData(record))),
|
||||
);
|
||||
// แปลงข้อมูลพร้อม salary
|
||||
const data = records.map((record) => {
|
||||
const salary = salaryMap.get(record.id);
|
||||
if (salary) {
|
||||
(record as any).profileSalary = [salary];
|
||||
}
|
||||
return this.transformOfficerData(record);
|
||||
});
|
||||
|
||||
// console.log(`[ProfileLeaveService] getLeaveOfficer total took ${Date.now() - t0}ms, total=${total}`);
|
||||
return { data, total };
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue