434 lines
16 KiB
TypeScript
434 lines
16 KiB
TypeScript
|
|
import { AppDataSource } from "../database/data-source";
|
||
|
|
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 LeaveEmployeeFilter {
|
||
|
|
page: number;
|
||
|
|
pageSize: number;
|
||
|
|
searchField?: "firstName" | "lastName" | "fullName" | "citizenId" | "position" | "posNo";
|
||
|
|
searchKeyword?: string;
|
||
|
|
posType?: string;
|
||
|
|
posLevel?: string;
|
||
|
|
isProbation?: boolean;
|
||
|
|
node?: number;
|
||
|
|
nodeId?: string;
|
||
|
|
isAll?: boolean;
|
||
|
|
retireType?: string;
|
||
|
|
sortBy?: string;
|
||
|
|
sort: "ASC" | "DESC";
|
||
|
|
}
|
||
|
|
|
||
|
|
export interface OrganizationCondition {
|
||
|
|
condition: string;
|
||
|
|
params: Record<string, any>;
|
||
|
|
}
|
||
|
|
|
||
|
|
export class ProfileLeaveService {
|
||
|
|
private profileRepo: Repository<ProfileEmployee>;
|
||
|
|
private orgRootRepository: Repository<OrgRoot>;
|
||
|
|
private child1Repository: Repository<OrgChild1>;
|
||
|
|
private child2Repository: Repository<OrgChild2>;
|
||
|
|
private child3Repository: Repository<OrgChild3>;
|
||
|
|
private child4Repository: Repository<OrgChild4>;
|
||
|
|
|
||
|
|
constructor() {
|
||
|
|
this.profileRepo = AppDataSource.getRepository(ProfileEmployee);
|
||
|
|
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): string {
|
||
|
|
switch (searchField) {
|
||
|
|
case "citizenId":
|
||
|
|
return "profileEmployee.citizenId LIKE :keyword";
|
||
|
|
case "position":
|
||
|
|
return "profileEmployee.position LIKE :keyword";
|
||
|
|
case "posNo":
|
||
|
|
return `
|
||
|
|
(profileSalary.posNoAbb IS NOT NULL AND CONCAT(profileSalary.posNoAbb, profileSalary.posNo) LIKE :keyword)
|
||
|
|
OR (profileSalary.posNoAbb IS NOT NULL AND CONCAT(profileSalary.posNoAbb, " ", profileSalary.posNo) LIKE :keyword)
|
||
|
|
OR (profileSalary.posNo IS NOT NULL AND profileSalary.posNo LIKE :keyword)
|
||
|
|
`;
|
||
|
|
default:
|
||
|
|
return "CONCAT(profileEmployee.prefix, profileEmployee.firstName, ' ', profileEmployee.lastName) LIKE :keyword";
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* สร้างเงื่อนไขการค้นหาตาม node และ nodeId
|
||
|
|
*/
|
||
|
|
async buildNodeCondition(
|
||
|
|
node?: number,
|
||
|
|
nodeId?: string,
|
||
|
|
isAll?: boolean,
|
||
|
|
): Promise<OrganizationCondition> {
|
||
|
|
let condition = "1=1";
|
||
|
|
let nodeAll = "";
|
||
|
|
const params: Record<string, any> = {};
|
||
|
|
|
||
|
|
if (!node || !nodeId) {
|
||
|
|
return { condition, params };
|
||
|
|
}
|
||
|
|
|
||
|
|
// สร้าง nodeAll condition - เพิ่มการตรวจสอบ IS NULL
|
||
|
|
if (isAll === false && node < 4) {
|
||
|
|
const nextLevels = ["orgChild1", "orgChild2", "orgChild3", "orgChild4"];
|
||
|
|
nodeAll = ` AND (profileSalary.${nextLevels[node]} IS NULL OR profileSalary.id IS NULL)`;
|
||
|
|
}
|
||
|
|
|
||
|
|
try {
|
||
|
|
switch (node) {
|
||
|
|
case 0: {
|
||
|
|
const orgRoot = await this.orgRootRepository.findOne({ where: { id: nodeId } });
|
||
|
|
if (orgRoot) {
|
||
|
|
condition = "(profileSalary.orgRoot = :orgRoot OR profileSalary.id IS NULL)";
|
||
|
|
params.orgRoot = orgRoot.orgRootName;
|
||
|
|
}
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
case 1: {
|
||
|
|
const orgChild1 = await this.child1Repository.findOne({ where: { id: nodeId } });
|
||
|
|
if (orgChild1) {
|
||
|
|
condition = "(profileSalary.orgChild1 = :orgChild1 OR profileSalary.id IS NULL)";
|
||
|
|
params.orgChild1 = orgChild1.orgChild1Name;
|
||
|
|
}
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
case 2: {
|
||
|
|
const orgChild2 = await this.child2Repository.findOne({ where: { id: nodeId } });
|
||
|
|
if (orgChild2) {
|
||
|
|
condition = "(profileSalary.orgChild2 = :orgChild2 OR profileSalary.id IS NULL)";
|
||
|
|
params.orgChild2 = orgChild2.orgChild2Name;
|
||
|
|
}
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
case 3: {
|
||
|
|
const orgChild3 = await this.child3Repository.findOne({ where: { id: nodeId } });
|
||
|
|
if (orgChild3) {
|
||
|
|
condition = "(profileSalary.orgChild3 = :orgChild3 OR profileSalary.id IS NULL)";
|
||
|
|
params.orgChild3 = orgChild3.orgChild3Name;
|
||
|
|
}
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
case 4: {
|
||
|
|
const orgChild4 = await this.child4Repository.findOne({ where: { id: nodeId } });
|
||
|
|
if (orgChild4) {
|
||
|
|
condition = "(profileSalary.orgChild4 = :orgChild4 OR profileSalary.id IS NULL)";
|
||
|
|
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,
|
||
|
|
): Promise<OrganizationCondition> {
|
||
|
|
const _data = await new permission().PermissionOrgList(request, "SYS_REGISTRY_EMP");
|
||
|
|
let condition = "1=1";
|
||
|
|
let nodeAll = "";
|
||
|
|
const params: Record<string, any> = {};
|
||
|
|
|
||
|
|
try {
|
||
|
|
if (_data.root) {
|
||
|
|
const orgRootPms = await this.orgRootRepository.findOne({ where: { id: _data.root } });
|
||
|
|
if (orgRootPms) {
|
||
|
|
condition = "(profileSalary.orgRoot = :orgRootPms OR profileSalary.id IS NULL)";
|
||
|
|
params.orgRootPms = orgRootPms.orgRootName;
|
||
|
|
}
|
||
|
|
if (isAll === false)
|
||
|
|
nodeAll = " AND (profileSalary.orgChild1 IS NULL OR profileSalary.id IS NULL)";
|
||
|
|
} else if (_data.child1) {
|
||
|
|
const orgChild1Pms = await this.child1Repository.findOne({ where: { id: _data.child1 } });
|
||
|
|
if (orgChild1Pms) {
|
||
|
|
condition = "(profileSalary.orgChild1 = :orgChild1Pms OR profileSalary.id IS NULL)";
|
||
|
|
params.orgChild1Pms = orgChild1Pms.orgChild1Name;
|
||
|
|
}
|
||
|
|
if (isAll === false)
|
||
|
|
nodeAll = " AND (profileSalary.orgChild2 IS NULL OR profileSalary.id IS NULL)";
|
||
|
|
} else if (_data.child2) {
|
||
|
|
const orgChild2Pms = await this.child2Repository.findOne({ where: { id: _data.child2 } });
|
||
|
|
if (orgChild2Pms) {
|
||
|
|
condition = "(profileSalary.orgChild2 = :orgChild2Pms OR profileSalary.id IS NULL)";
|
||
|
|
params.orgChild2Pms = orgChild2Pms.orgChild2Name;
|
||
|
|
}
|
||
|
|
if (isAll === false)
|
||
|
|
nodeAll = " AND (profileSalary.orgChild3 IS NULL OR profileSalary.id IS NULL)";
|
||
|
|
} else if (_data.child3) {
|
||
|
|
const orgChild3Pms = await this.child3Repository.findOne({ where: { id: _data.child3 } });
|
||
|
|
if (orgChild3Pms) {
|
||
|
|
condition = "(profileSalary.orgChild3 = :orgChild3Pms OR profileSalary.id IS NULL)";
|
||
|
|
params.orgChild3Pms = orgChild3Pms.orgChild3Name;
|
||
|
|
}
|
||
|
|
if (isAll === false)
|
||
|
|
nodeAll = " AND (profileSalary.orgChild4 IS NULL OR profileSalary.id IS NULL)";
|
||
|
|
} else if (_data.child4) {
|
||
|
|
const orgChild4Pms = await this.child4Repository.findOne({ where: { id: _data.child4 } });
|
||
|
|
if (orgChild4Pms) {
|
||
|
|
condition = "(profileSalary.orgChild4 = :orgChild4Pms OR profileSalary.id IS NULL)";
|
||
|
|
params.orgChild4Pms = orgChild4Pms.orgChild4Name;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
} catch (error) {
|
||
|
|
console.error("Error building permission condition:", error);
|
||
|
|
}
|
||
|
|
|
||
|
|
return { condition: condition + nodeAll, params };
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* แปลงข้อมูลพนักงานเป็น format ที่ต้องการ
|
||
|
|
*/
|
||
|
|
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: LeaveEmployeeFilter,
|
||
|
|
): 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),
|
||
|
|
]);
|
||
|
|
|
||
|
|
const searchQuery = this.buildSearchQuery(searchField);
|
||
|
|
|
||
|
|
// สร้าง main query - เปลี่ยนจาก leftJoinAndSelect เป็น leftJoin สำหรับ profileSalary
|
||
|
|
const queryBuilder = this.profileRepo
|
||
|
|
.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" });
|
||
|
|
|
||
|
|
// เพิ่มเงื่อนไขการค้นหา
|
||
|
|
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 });
|
||
|
|
}
|
||
|
|
|
||
|
|
if (searchKeyword) {
|
||
|
|
queryBuilder.andWhere(searchQuery, { keyword: `%${searchKeyword}%` });
|
||
|
|
}
|
||
|
|
|
||
|
|
// เพิ่ม 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();
|
||
|
|
|
||
|
|
// แปลงข้อมูลแบบ parallel
|
||
|
|
const data = await Promise.all(
|
||
|
|
records.map((record) => Promise.resolve(this.transformEmployeeData(record))),
|
||
|
|
);
|
||
|
|
|
||
|
|
return { data, total };
|
||
|
|
}
|
||
|
|
}
|