Compare commits
No commits in common. "dev" and "develop" have entirely different histories.
23 changed files with 579 additions and 2100 deletions
|
|
@ -1,154 +0,0 @@
|
||||||
-- =====================================================
|
|
||||||
-- Update position fields in profile table
|
|
||||||
-- อัพเดทฟิลด์ตำแหน่งในตาราง profile
|
|
||||||
--
|
|
||||||
-- Fields:
|
|
||||||
-- - positionField (สายงาน)
|
|
||||||
-- - posExecutive (ตำแหน่งทางการบริหาร)
|
|
||||||
-- - positionArea (ด้าน/สาขา)
|
|
||||||
-- - positionExecutiveField (ด้านทางการบริหาร)
|
|
||||||
-- - posMasterNo (เลขที่ตำแหน่ง) - format: orgShortName + space + number
|
|
||||||
-- - org (สังกัด)
|
|
||||||
--
|
|
||||||
-- Run each query separately to verify results
|
|
||||||
-- =====================================================
|
|
||||||
USE hrms_organization;
|
|
||||||
-- 1. Update positionField (สายงาน)
|
|
||||||
UPDATE profile p
|
|
||||||
INNER JOIN posMaster pm ON pm.current_holderId = p.id
|
|
||||||
INNER JOIN orgRevision oRev ON pm.orgRevisionId = oRev.id AND oRev.orgRevisionIsCurrent = 1 AND oRev.orgRevisionIsDraft = 0
|
|
||||||
INNER JOIN position pos ON pos.posMasterId = pm.id AND pos.positionIsSelected = 1
|
|
||||||
SET p.positionField = pos.positionField
|
|
||||||
WHERE p.positionField IS NULL;
|
|
||||||
|
|
||||||
-- 2. Update posExecutive (ตำแหน่งทางการบริหาร)
|
|
||||||
UPDATE profile p
|
|
||||||
INNER JOIN posMaster pm ON pm.current_holderId = p.id
|
|
||||||
INNER JOIN orgRevision oRev ON pm.orgRevisionId = oRev.id AND oRev.orgRevisionIsCurrent = 1 AND oRev.orgRevisionIsDraft = 0
|
|
||||||
INNER JOIN position pos ON pos.posMasterId = pm.id AND pos.positionIsSelected = 1
|
|
||||||
INNER JOIN posExecutive pe ON pos.posExecutiveId = pe.id
|
|
||||||
SET p.posExecutive = pe.posExecutiveName
|
|
||||||
WHERE p.posExecutive IS NULL;
|
|
||||||
|
|
||||||
-- 3. Update positionArea (ด้าน/สาขา)
|
|
||||||
UPDATE profile p
|
|
||||||
INNER JOIN posMaster pm ON pm.current_holderId = p.id
|
|
||||||
INNER JOIN orgRevision oRev ON pm.orgRevisionId = oRev.id AND oRev.orgRevisionIsCurrent = 1 AND oRev.orgRevisionIsDraft = 0
|
|
||||||
INNER JOIN position pos ON pos.posMasterId = pm.id AND pos.positionIsSelected = 1
|
|
||||||
SET p.positionArea = pos.positionArea
|
|
||||||
WHERE p.positionArea IS NULL;
|
|
||||||
|
|
||||||
-- 4. Update positionExecutiveField (ด้านทางการบริหาร)
|
|
||||||
UPDATE profile p
|
|
||||||
INNER JOIN posMaster pm ON pm.current_holderId = p.id
|
|
||||||
INNER JOIN orgRevision oRev ON pm.orgRevisionId = oRev.id AND oRev.orgRevisionIsCurrent = 1 AND oRev.orgRevisionIsDraft = 0
|
|
||||||
INNER JOIN position pos ON pos.posMasterId = pm.id AND pos.positionIsSelected = 1
|
|
||||||
SET p.positionExecutiveField = pos.positionExecutiveField
|
|
||||||
WHERE p.positionExecutiveField IS NULL;
|
|
||||||
|
|
||||||
-- 5. Update posMasterNo (เลขที่ตำแหน่ง) - format: orgShortName + space + number
|
|
||||||
UPDATE profile p
|
|
||||||
INNER JOIN posMaster pm ON pm.current_holderId = p.id
|
|
||||||
INNER JOIN orgRevision oRev ON pm.orgRevisionId = oRev.id AND oRev.orgRevisionIsCurrent = 1 AND oRev.orgRevisionIsDraft = 0
|
|
||||||
LEFT JOIN orgRoot r ON pm.orgRootId = r.id
|
|
||||||
LEFT JOIN orgChild1 c1 ON pm.orgChild1Id = c1.id
|
|
||||||
LEFT JOIN orgChild2 c2 ON pm.orgChild2Id = c2.id
|
|
||||||
LEFT JOIN orgChild3 c3 ON pm.orgChild3Id = c3.id
|
|
||||||
LEFT JOIN orgChild4 c4 ON pm.orgChild4Id = c4.id
|
|
||||||
SET p.posMasterNo = TRIM(CONCAT(
|
|
||||||
CASE
|
|
||||||
WHEN pm.orgChild1Id IS NULL THEN r.orgRootShortName
|
|
||||||
WHEN pm.orgChild2Id IS NULL THEN c1.orgChild1ShortName
|
|
||||||
WHEN pm.orgChild3Id IS NULL THEN c2.orgChild2ShortName
|
|
||||||
WHEN pm.orgChild4Id IS NULL THEN c3.orgChild3ShortName
|
|
||||||
ELSE c4.orgChild4ShortName
|
|
||||||
END,
|
|
||||||
' ',
|
|
||||||
CONCAT_WS('', pm.posMasterNoPrefix, pm.posMasterNo, pm.posMasterNoSuffix)
|
|
||||||
))
|
|
||||||
WHERE p.posMasterNo IS NULL;
|
|
||||||
|
|
||||||
-- 6. Update org (สังกัด) - combine all org levels
|
|
||||||
UPDATE profile p
|
|
||||||
INNER JOIN posMaster pm ON pm.current_holderId = p.id
|
|
||||||
INNER JOIN orgRevision oRev ON pm.orgRevisionId = oRev.id AND oRev.orgRevisionIsCurrent = 1 AND oRev.orgRevisionIsDraft = 0
|
|
||||||
LEFT JOIN orgRoot r ON pm.orgRootId = r.id
|
|
||||||
LEFT JOIN orgChild1 c1 ON pm.orgChild1Id = c1.id
|
|
||||||
LEFT JOIN orgChild2 c2 ON pm.orgChild2Id = c2.id
|
|
||||||
LEFT JOIN orgChild3 c3 ON pm.orgChild3Id = c3.id
|
|
||||||
LEFT JOIN orgChild4 c4 ON pm.orgChild4Id = c4.id
|
|
||||||
SET p.org = TRIM(CONCAT_WS(
|
|
||||||
CHAR(10),
|
|
||||||
c4.orgChild4Name,
|
|
||||||
c3.orgChild3Name,
|
|
||||||
c2.orgChild2Name,
|
|
||||||
c1.orgChild1Name,
|
|
||||||
r.orgRootName
|
|
||||||
))
|
|
||||||
WHERE p.org IS NULL;
|
|
||||||
|
|
||||||
-- =====================================================
|
|
||||||
-- เช็คผลลัพธ์ (Check results)
|
|
||||||
-- =====================================================
|
|
||||||
|
|
||||||
-- เช็คจำนวนที่ update ได้
|
|
||||||
SELECT
|
|
||||||
COUNT(CASE WHEN positionField IS NOT NULL THEN 1 END) AS has_positionField,
|
|
||||||
COUNT(CASE WHEN posExecutive IS NOT NULL THEN 1 END) AS has_posExecutive,
|
|
||||||
COUNT(CASE WHEN positionArea IS NOT NULL THEN 1 END) AS has_positionArea,
|
|
||||||
COUNT(CASE WHEN positionExecutiveField IS NOT NULL THEN 1 END) AS has_positionExecutiveField,
|
|
||||||
COUNT(CASE WHEN posMasterNo IS NOT NULL THEN 1 END) AS has_posMasterNo,
|
|
||||||
COUNT(CASE WHEN org IS NOT NULL THEN 1 END) AS has_org
|
|
||||||
FROM profile;
|
|
||||||
|
|
||||||
-- =====================================================
|
|
||||||
-- SELECT query สำหรับทดสอบก่อนรัน (Test before run)
|
|
||||||
-- =====================================================
|
|
||||||
|
|
||||||
SELECT
|
|
||||||
p.id,
|
|
||||||
p.firstName,
|
|
||||||
p.lastName,
|
|
||||||
p.citizenId,
|
|
||||||
|
|
||||||
p.positionField as old_positionField,
|
|
||||||
p.posExecutive as old_posExecutive,
|
|
||||||
p.positionArea as old_positionArea,
|
|
||||||
p.positionExecutiveField as old_positionExecutiveField,
|
|
||||||
p.posMasterNo as old_posMasterNo,
|
|
||||||
p.org as old_org,
|
|
||||||
|
|
||||||
pos.positionField as new_positionField,
|
|
||||||
pe.posExecutiveName as new_posExecutive,
|
|
||||||
pos.positionArea as new_positionArea,
|
|
||||||
pos.positionExecutiveField as new_positionExecutiveField,
|
|
||||||
|
|
||||||
TRIM(CONCAT(
|
|
||||||
CASE
|
|
||||||
WHEN pm.orgChild1Id IS NULL THEN r.orgRootShortName
|
|
||||||
WHEN pm.orgChild2Id IS NULL THEN c1.orgChild1ShortName
|
|
||||||
WHEN pm.orgChild3Id IS NULL THEN c2.orgChild2ShortName
|
|
||||||
WHEN pm.orgChild4Id IS NULL THEN c3.orgChild3ShortName
|
|
||||||
ELSE c4.orgChild4ShortName
|
|
||||||
END,
|
|
||||||
' ',
|
|
||||||
pm.posMasterNo
|
|
||||||
)) as new_posMasterNo,
|
|
||||||
|
|
||||||
TRIM(CONCAT_WS(CHAR(10), c4.orgChild4Name, c3.orgChild3Name, c2.orgChild2Name, c1.orgChild1Name, r.orgRootName)) as new_org
|
|
||||||
|
|
||||||
FROM profile p
|
|
||||||
INNER JOIN posMaster pm ON pm.current_holderId = p.id
|
|
||||||
INNER JOIN orgRevision oRev ON pm.orgRevisionId = oRev.id AND oRev.orgRevisionIsCurrent = 1 AND oRev.orgRevisionIsDraft = 0
|
|
||||||
INNER JOIN position pos ON pos.posMasterId = pm.id AND pos.positionIsSelected = 1
|
|
||||||
LEFT JOIN posExecutive pe ON pos.posExecutiveId = pe.id
|
|
||||||
LEFT JOIN orgRoot r ON pm.orgRootId = r.id
|
|
||||||
LEFT JOIN orgChild1 c1 ON pm.orgChild1Id = c1.id
|
|
||||||
LEFT JOIN orgChild2 c2 ON pm.orgChild2Id = c2.id
|
|
||||||
LEFT JOIN orgChild3 c3 ON pm.orgChild3Id = c3.id
|
|
||||||
LEFT JOIN orgChild4 c4 ON pm.orgChild4Id = c4.id
|
|
||||||
|
|
||||||
-- ใส่ WHERE ทดสอบ 1 คน (Test 1 person)
|
|
||||||
WHERE p.id = 'ใส่ profile_id ที่ต้องการทดสอบ'
|
|
||||||
-- หรือทดสอบ 10 คน (Test 10 persons)
|
|
||||||
-- LIMIT 10;
|
|
||||||
|
|
@ -48,7 +48,6 @@ import {
|
||||||
import { Position } from "../entities/Position";
|
import { Position } from "../entities/Position";
|
||||||
import { PosMaster } from "../entities/PosMaster";
|
import { PosMaster } from "../entities/PosMaster";
|
||||||
import { EmployeePosition } from "../entities/EmployeePosition";
|
import { EmployeePosition } from "../entities/EmployeePosition";
|
||||||
import { getPosMasterNo, getOrgFullName } from "../utils/org-formatting";
|
|
||||||
import { EmployeePosMaster } from "../entities/EmployeePosMaster";
|
import { EmployeePosMaster } from "../entities/EmployeePosMaster";
|
||||||
import { ProfileDiscipline } from "../entities/ProfileDiscipline";
|
import { ProfileDiscipline } from "../entities/ProfileDiscipline";
|
||||||
import { ProfileDisciplineHistory } from "../entities/ProfileDisciplineHistory";
|
import { ProfileDisciplineHistory } from "../entities/ProfileDisciplineHistory";
|
||||||
|
|
@ -3661,7 +3660,6 @@ export class CommandController extends Controller {
|
||||||
|
|
||||||
const posMaster = await this.posMasterRepository.findOne({
|
const posMaster = await this.posMasterRepository.findOne({
|
||||||
where: { id: item.posmasterId },
|
where: { id: item.posmasterId },
|
||||||
relations: ["orgRoot", "orgChild1", "orgChild2", "orgChild3", "orgChild4"],
|
|
||||||
});
|
});
|
||||||
if (posMaster == null)
|
if (posMaster == null)
|
||||||
throw new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบข้อมูลตำแหน่งนี้");
|
throw new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบข้อมูลตำแหน่งนี้");
|
||||||
|
|
@ -3717,22 +3715,14 @@ export class CommandController extends Controller {
|
||||||
id: item.positionId,
|
id: item.positionId,
|
||||||
posMasterId: item.posmasterId,
|
posMasterId: item.posmasterId,
|
||||||
},
|
},
|
||||||
relations: ["posExecutive"],
|
|
||||||
});
|
});
|
||||||
// ถ้าไม่ใช่ตำแหน่งนั่งทับ (isSit = false) ถึงจะอัพเดทตำแหน่งในทะเบียนประวัติ
|
// ถ้าไม่ใช่ตำแหน่งนั่งทับ (isSit = false) ถึงจะอัพเดทตำแหน่งในทะเบียนประวัติ
|
||||||
if (positionNew != null) {
|
if (positionNew != null) {
|
||||||
positionNew.positionIsSelected = true;
|
positionNew.positionIsSelected = true;
|
||||||
// อัพเดท org และ posMasterNo ตลอดไม่ต้องดัก isSit
|
|
||||||
profile.posMasterNo = getPosMasterNo(posMaster);
|
|
||||||
profile.org = getOrgFullName(posMaster);
|
|
||||||
if(!posMaster.isSit){
|
if(!posMaster.isSit){
|
||||||
profile.posLevelId = positionNew.posLevelId;
|
profile.posLevelId = positionNew.posLevelId;
|
||||||
profile.posTypeId = positionNew.posTypeId;
|
profile.posTypeId = positionNew.posTypeId;
|
||||||
profile.position = positionNew.positionName;
|
profile.position = positionNew.positionName;
|
||||||
profile.positionField = positionNew.positionField ?? null;
|
|
||||||
profile.posExecutive = positionNew.posExecutive?.posExecutiveName ?? null;
|
|
||||||
profile.positionArea = positionNew.positionArea ?? null;
|
|
||||||
profile.positionExecutiveField = positionNew.positionExecutiveField ?? null;
|
|
||||||
}
|
}
|
||||||
profile.amount = item.amount ?? null;
|
profile.amount = item.amount ?? null;
|
||||||
profile.amountSpecial = item.amountSpecial ?? null;
|
profile.amountSpecial = item.amountSpecial ?? null;
|
||||||
|
|
@ -6886,7 +6876,7 @@ export class CommandController extends Controller {
|
||||||
where: {
|
where: {
|
||||||
id: item.bodyPosition.posmasterId,
|
id: item.bodyPosition.posmasterId,
|
||||||
},
|
},
|
||||||
relations: { orgRevision: true, orgRoot: true, orgChild1: true, orgChild2: true, orgChild3: true, orgChild4: true }
|
relations: { orgRevision: true }
|
||||||
});
|
});
|
||||||
|
|
||||||
// เช็คว่า posMaster ที่หามาอยู่ในโครงสร้างปัจจุบันหรือไม่
|
// เช็คว่า posMaster ที่หามาอยู่ในโครงสร้างปัจจุบันหรือไม่
|
||||||
|
|
@ -6903,8 +6893,9 @@ export class CommandController extends Controller {
|
||||||
orgRevisionIsDraft: false
|
orgRevisionIsDraft: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
relations: { orgRevision: true, orgRoot: true, orgChild1: true, orgChild2: true, orgChild3: true, orgChild4: true }
|
relations: { orgRevision: true }
|
||||||
}); }
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (posMaster == null)
|
if (posMaster == null)
|
||||||
throw new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบข้อมูลตำแหน่งนี้");
|
throw new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบข้อมูลตำแหน่งนี้");
|
||||||
|
|
@ -6992,28 +6983,20 @@ export class CommandController extends Controller {
|
||||||
id: item.bodyPosition.positionId,
|
id: item.bodyPosition.positionId,
|
||||||
posMasterId: posMaster.id,
|
posMasterId: posMaster.id,
|
||||||
},
|
},
|
||||||
relations: ["posExecutive"],
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// ถ้าไม่ใช่ตำแหน่งนั่งทับ (isSit = false) ถึงจะอัพเดทตำแหน่งในทะเบียนประวัติ
|
// ถ้าไม่ใช่ตำแหน่งนั่งทับ (isSit = false) ถึงจะอัพเดทตำแหน่งในทะเบียนประวัติ
|
||||||
if (positionNew != null) {
|
if (positionNew != null) {
|
||||||
positionNew.positionIsSelected = true;
|
positionNew.positionIsSelected = true;
|
||||||
// อัพเดท org และ posMasterNo ตลอดไม่ต้องดัก isSit
|
|
||||||
profile.posMasterNo = getPosMasterNo(posMaster);
|
|
||||||
profile.org = getOrgFullName(posMaster);
|
|
||||||
if(!posMaster.isSit){
|
if(!posMaster.isSit){
|
||||||
profile.posLevelId = positionNew.posLevelId;
|
profile.posLevelId = positionNew.posLevelId;
|
||||||
profile.posTypeId = positionNew.posTypeId;
|
profile.posTypeId = positionNew.posTypeId;
|
||||||
profile.position = positionNew.positionName;
|
profile.position = positionNew.positionName;
|
||||||
profile.positionField = positionNew.positionField ?? null;
|
|
||||||
profile.posExecutive = positionNew.posExecutive?.posExecutiveName ?? null;
|
|
||||||
profile.positionArea = positionNew.positionArea ?? null;
|
|
||||||
profile.positionExecutiveField = positionNew.positionExecutiveField ?? null;
|
|
||||||
// profile.dateStart = new Date();
|
// profile.dateStart = new Date();
|
||||||
}
|
|
||||||
await this.profileRepository.save(profile, { data: req });
|
await this.profileRepository.save(profile, { data: req });
|
||||||
setLogDataDiff(req, { before, after: profile });
|
setLogDataDiff(req, { before, after: profile });
|
||||||
|
}
|
||||||
await this.positionRepository.save(positionNew, { data: req });
|
await this.positionRepository.save(positionNew, { data: req });
|
||||||
}
|
}
|
||||||
// await CreatePosMasterHistoryOfficer(posMaster.id, req);
|
// await CreatePosMasterHistoryOfficer(posMaster.id, req);
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,6 @@ import {
|
||||||
import HttpError from "../interfaces/http-error";
|
import HttpError from "../interfaces/http-error";
|
||||||
import HttpStatusCode from "../interfaces/http-status";
|
import HttpStatusCode from "../interfaces/http-status";
|
||||||
import { addLogSequence } from "../interfaces/utils";
|
import { addLogSequence } from "../interfaces/utils";
|
||||||
import HttpSuccess from "../interfaces/http-success";
|
|
||||||
|
|
||||||
interface CachedToken {
|
interface CachedToken {
|
||||||
token: string;
|
token: string;
|
||||||
|
|
@ -89,8 +88,7 @@ export class ExRetirementController extends Controller {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
// return res.data;
|
return res.data;
|
||||||
return new HttpSuccess(res.data.data);
|
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
if (error.response?.status === 500 && retryCount < maxRetries - 1) {
|
if (error.response?.status === 500 && retryCount < maxRetries - 1) {
|
||||||
TokenCache.delete(`${clientId}:${clientSecret}`);
|
TokenCache.delete(`${clientId}:${clientSecret}`);
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { Controller, Post, Route, Security, Tags, Request, UploadedFile, Path } from "tsoa";
|
import { Controller, Post, Route, Security, Tags, Request, UploadedFile } from "tsoa";
|
||||||
import { AppDataSource } from "../database/data-source";
|
import { AppDataSource } from "../database/data-source";
|
||||||
import { In, IsNull, LessThanOrEqual, Not, Between } from "typeorm";
|
import { In, IsNull, LessThanOrEqual, Not, Between } from "typeorm";
|
||||||
import HttpSuccess from "../interfaces/http-success";
|
import HttpSuccess from "../interfaces/http-success";
|
||||||
|
|
@ -105,7 +105,6 @@ import { positionOfficer } from "../entities/mis/positionOfficer";
|
||||||
import { ProvinceMaster } from "../entities/ProvinceMaster";
|
import { ProvinceMaster } from "../entities/ProvinceMaster";
|
||||||
import { SubDistrictMaster } from "../entities/SubDistrictMaster";
|
import { SubDistrictMaster } from "../entities/SubDistrictMaster";
|
||||||
import { DistrictMaster } from "../entities/DistrictMaster";
|
import { DistrictMaster } from "../entities/DistrictMaster";
|
||||||
import { RequestWithUser } from "../middlewares/user";
|
|
||||||
@Route("api/v1/org/upload")
|
@Route("api/v1/org/upload")
|
||||||
@Tags("UPLOAD")
|
@Tags("UPLOAD")
|
||||||
@Security("bearerAuth")
|
@Security("bearerAuth")
|
||||||
|
|
@ -6816,502 +6815,4 @@ export class ImportDataController extends Controller {
|
||||||
// await repo.save(entities);
|
// await repo.save(entities);
|
||||||
// return entities;
|
// return entities;
|
||||||
// }
|
// }
|
||||||
|
|
||||||
/**
|
|
||||||
* @summary Import ข้อมูลประวัติตำแหน่งเงินเดือนของข้าราชการเข้าตาราง ProfileSalaryTemp
|
|
||||||
* @param profileId Id โปรไฟล์ข้าราชการ
|
|
||||||
* @param file Excel file with salary history data
|
|
||||||
*/
|
|
||||||
@Post("office-profileSalaryTemp/{profileId}")
|
|
||||||
@UseInterceptors(FileInterceptor("file"))
|
|
||||||
async UploadProfileSalaryTemp(
|
|
||||||
@Path() profileId: string,
|
|
||||||
@Request() req: RequestWithUser,
|
|
||||||
@UploadedFile() file: Express.Multer.File,
|
|
||||||
) {
|
|
||||||
if (!profileId) {
|
|
||||||
throw new Error("profileId is required");
|
|
||||||
}
|
|
||||||
|
|
||||||
// อ่านไฟล์ Excel ก่อน (นอก transaction)
|
|
||||||
const workbook = xlsx.read(file.buffer, { type: "buffer" });
|
|
||||||
const sheetName = workbook.SheetNames[0];
|
|
||||||
const sheet = workbook.Sheets[sheetName];
|
|
||||||
const getExcel = xlsx.utils.sheet_to_json(sheet, { header: 1 }) as any[][];
|
|
||||||
|
|
||||||
let salaryTemps: ProfileSalaryTemp[] = [];
|
|
||||||
let dateTime = new Date();
|
|
||||||
|
|
||||||
// เริ่มจาก index 1 เพื่อข้าม header row
|
|
||||||
for (let i = 1; i < getExcel.length; i++) {
|
|
||||||
const row = getExcel[i];
|
|
||||||
|
|
||||||
// ข้าม empty rows
|
|
||||||
if (!row || row.length === 0) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ข้ามแถวที่ไม่มีลำดับ (row[0] เป็น null, undefined หรือค่าว่าง)
|
|
||||||
if (!row[0]) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const salaryTemp = new ProfileSalaryTemp();
|
|
||||||
|
|
||||||
// ฟังก์ชันแปลงวันที่จาก Excel รองรับทั้ง string format และ serial number
|
|
||||||
const parseExcelDate = (value: any): Date | null => {
|
|
||||||
if (!value) return null;
|
|
||||||
|
|
||||||
// กรณี 1: Excel serial number (ตัวเลข)
|
|
||||||
if (typeof value === "number") {
|
|
||||||
// Excel serial number = จำนวนวันตั้งแต่ 1 ม.ค. 1900
|
|
||||||
// แปลงเป็น JavaScript Date (epoch 1970)
|
|
||||||
let jsDate = new Date(Math.round((value - 25569) * 86400 * 1000));
|
|
||||||
|
|
||||||
// ตรวจสอบและแปลงปี พ.ศ. เป็น ค.ศ. (ถ้าปี > 2500)
|
|
||||||
if (jsDate.getFullYear() > 2500) {
|
|
||||||
const newYear = jsDate.getFullYear() - 543;
|
|
||||||
jsDate = new Date(
|
|
||||||
newYear,
|
|
||||||
jsDate.getMonth(),
|
|
||||||
jsDate.getDate(),
|
|
||||||
jsDate.getHours(),
|
|
||||||
jsDate.getMinutes(),
|
|
||||||
jsDate.getSeconds(),
|
|
||||||
jsDate.getMilliseconds()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return jsDate;
|
|
||||||
}
|
|
||||||
|
|
||||||
// กรณี 2: String format (dd/mm/yyyy หรือ d/m/yyyy)
|
|
||||||
const dateStr = value.toString().trim();
|
|
||||||
|
|
||||||
// ตรวจสอบว่าเป็น serial number ที่เป็น string หรือไม่
|
|
||||||
if (/^\d+$/.test(dateStr)) {
|
|
||||||
const serialNum = parseInt(dateStr);
|
|
||||||
let jsDate = new Date(Math.round((serialNum - 25569) * 86400 * 1000));
|
|
||||||
|
|
||||||
// ตรวจสอบและแปลงปี พ.ศ. เป็น ค.ศ. (ถ้าปี > 2500)
|
|
||||||
if (jsDate.getFullYear() > 2500) {
|
|
||||||
const newYear = jsDate.getFullYear() - 543;
|
|
||||||
jsDate = new Date(
|
|
||||||
newYear,
|
|
||||||
jsDate.getMonth(),
|
|
||||||
jsDate.getDate(),
|
|
||||||
jsDate.getHours(),
|
|
||||||
jsDate.getMinutes(),
|
|
||||||
jsDate.getSeconds(),
|
|
||||||
jsDate.getMilliseconds()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return jsDate;
|
|
||||||
}
|
|
||||||
|
|
||||||
// String format ปกติ (dd/mm/yyyy)
|
|
||||||
const dateParts = dateStr.split("/");
|
|
||||||
if (dateParts.length === 3) {
|
|
||||||
// แปลงเป็นตัวเลขแล้วค่อยจัดรูปแบบใหม่ เพื่อรองรับทั้ง 1 หลักและ 2 หลัก
|
|
||||||
const day = parseInt(dateParts[0].trim()).toString().padStart(2, "0");
|
|
||||||
const month = parseInt(dateParts[1].trim()).toString().padStart(2, "0");
|
|
||||||
let year = parseInt(dateParts[2].trim());
|
|
||||||
if (year > 2500) {
|
|
||||||
year -= 543;
|
|
||||||
}
|
|
||||||
const result = new Date(`${year}-${month}-${day}`);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Index 1: วันที่คำสั่งมีผล
|
|
||||||
let commandDateAffect: Date | null = null;
|
|
||||||
if (row[1]) {
|
|
||||||
commandDateAffect = parseExcelDate(row[1]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Index 25: วันที่ลงนาม
|
|
||||||
let commandDateSign: Date | null = null;
|
|
||||||
if (row[25]) {
|
|
||||||
commandDateSign = parseExcelDate(row[25]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Map ข้อมูลจาก Excel ไปยัง ProfileSalaryTemp ตามลำดับ column
|
|
||||||
// ข้อมูลระบบ
|
|
||||||
salaryTemp.profileId = profileId;
|
|
||||||
salaryTemp.profileEmployeeId = null as any;
|
|
||||||
|
|
||||||
// Index 0: ลำดับ
|
|
||||||
salaryTemp.order = row[0] ? parseInt(row[0].toString()) : (null as any);
|
|
||||||
|
|
||||||
// Index 1: วันที่คำสั่งมีผล
|
|
||||||
salaryTemp.commandDateAffect = commandDateAffect as any;
|
|
||||||
|
|
||||||
// Index 2: ตำแหน่งในสายงาน
|
|
||||||
salaryTemp.positionName = row[2] || null;
|
|
||||||
|
|
||||||
// Index 3: ตำแหน่งประเภท
|
|
||||||
salaryTemp.positionType = row[3] || null;
|
|
||||||
|
|
||||||
// Index 4: ระดับ
|
|
||||||
salaryTemp.positionLevel = row[4] || null;
|
|
||||||
|
|
||||||
// Index 5: ระดับซี
|
|
||||||
salaryTemp.positionCee = row[5] || null;
|
|
||||||
|
|
||||||
// Index 6: สายงาน
|
|
||||||
salaryTemp.positionLine = row[6] || null;
|
|
||||||
|
|
||||||
// Index 7: ด้าน/สาขา
|
|
||||||
salaryTemp.positionPathSide = row[7] || null;
|
|
||||||
|
|
||||||
// Index 8: ตำแหน่งทางการบริหาร
|
|
||||||
salaryTemp.positionExecutive = row[8] || null;
|
|
||||||
|
|
||||||
// Index 9: ด้านทางการบริหาร
|
|
||||||
salaryTemp.positionExecutiveField = row[9] || null;
|
|
||||||
|
|
||||||
// Index 10: เงินเดือน
|
|
||||||
salaryTemp.amount = row[10] || 0;
|
|
||||||
|
|
||||||
// Index 11: เงินค่าตอบแทนรายเดือน
|
|
||||||
salaryTemp.mouthSalaryAmount = row[11] || 0;
|
|
||||||
|
|
||||||
// Index 12: เงินประจำตำแหน่ง
|
|
||||||
salaryTemp.positionSalaryAmount = row[12] || 0;
|
|
||||||
|
|
||||||
// Index 13: เงินค่าตอบแทนพิเศษ
|
|
||||||
salaryTemp.amountSpecial = row[13] || 0;
|
|
||||||
|
|
||||||
// Index 14: หน่วยงาน
|
|
||||||
salaryTemp.orgRoot = row[14] || null;
|
|
||||||
|
|
||||||
// Index 15: ส่วนราชการระดับ 1
|
|
||||||
salaryTemp.orgChild1 = row[15] || null;
|
|
||||||
|
|
||||||
// Index 16: ส่วนราชการระดับ 2
|
|
||||||
salaryTemp.orgChild2 = row[16] || null;
|
|
||||||
|
|
||||||
// Index 17: ส่วนราชการระดับ 3
|
|
||||||
salaryTemp.orgChild3 = row[17] || null;
|
|
||||||
|
|
||||||
// Index 18: ส่วนราชการระดับ 4
|
|
||||||
salaryTemp.orgChild4 = row[18] || null;
|
|
||||||
|
|
||||||
// Index 19: ตัวย่อเลขที่ตำแหน่ง
|
|
||||||
salaryTemp.posNoAbb = row[19] || null;
|
|
||||||
|
|
||||||
// Index 20: เลขที่ตำแหน่ง
|
|
||||||
salaryTemp.posNo = row[20] ? row[20].toString() : null;
|
|
||||||
|
|
||||||
// Index 21: หน่วยงานที่ออกคำสั่ง
|
|
||||||
salaryTemp.posNumCodeSit = row[21] || null;
|
|
||||||
|
|
||||||
// Index 22: ตัวย่อหน่วยงานที่ออกคำสั่ง
|
|
||||||
salaryTemp.posNumCodeSitAbb = row[22] || null;
|
|
||||||
|
|
||||||
// Index 23: เลขที่คำสั่ง
|
|
||||||
salaryTemp.commandNo = row[23] || null;
|
|
||||||
|
|
||||||
// Index 24: ปีเลขที่คำสั่ง (แปลงเป็น ค.ศ.)
|
|
||||||
let commandYearValue: number | null = null;
|
|
||||||
if (row[24]) {
|
|
||||||
commandYearValue = parseInt(row[24].toString());
|
|
||||||
// ถ้าปีเป็น พ.ศ. (มากกว่า 2500) ให้แปลงเป็น ค.ศ.
|
|
||||||
if (commandYearValue > 2500) {
|
|
||||||
commandYearValue -= 543;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
salaryTemp.commandYear = commandYearValue as any;
|
|
||||||
|
|
||||||
// Index 25: วันที่ลงนาม (แปลงแล้ว)
|
|
||||||
salaryTemp.commandDateSign = commandDateSign as any;
|
|
||||||
|
|
||||||
// Index 26: ประเภทคำสั่ง
|
|
||||||
salaryTemp.commandName = row[26] || null;
|
|
||||||
|
|
||||||
// Index 27: หมายเหตุ
|
|
||||||
salaryTemp.remark = row[27] || null;
|
|
||||||
|
|
||||||
// Index 28: commandId
|
|
||||||
salaryTemp.commandId = row[28] || null;
|
|
||||||
|
|
||||||
// Index 29: commandCode
|
|
||||||
salaryTemp.commandCode = row[29] || null;
|
|
||||||
|
|
||||||
// ข้อมูลระบบ
|
|
||||||
salaryTemp.isDelete = false;
|
|
||||||
salaryTemp.isEdit = false;
|
|
||||||
salaryTemp.isGovernment = false;
|
|
||||||
salaryTemp.isEntry = false;
|
|
||||||
salaryTemp.createdAt = dateTime;
|
|
||||||
salaryTemp.createdUserId = req.user?.sub || "";
|
|
||||||
salaryTemp.createdFullName = req.user?.name || "System Administrator";
|
|
||||||
salaryTemp.lastUpdatedAt = dateTime;
|
|
||||||
salaryTemp.lastUpdateUserId = req.user?.sub || "";
|
|
||||||
salaryTemp.lastUpdateFullName = req.user?.name || "System Administrator";
|
|
||||||
|
|
||||||
salaryTemps.push(salaryTemp);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ใช้ Transaction เพื่อความปลอดภัย
|
|
||||||
await AppDataSource.transaction(async (transactionalEntityManager) => {
|
|
||||||
// ล้างข้อมูลทั้งหมดในตาราง profileSalaryTemp ของ profileId นั้น
|
|
||||||
await transactionalEntityManager.delete(ProfileSalaryTemp, { profileId });
|
|
||||||
// Insert ข้อมูลใหม่
|
|
||||||
await transactionalEntityManager.save(ProfileSalaryTemp, salaryTemps);
|
|
||||||
});
|
|
||||||
|
|
||||||
return new HttpSuccess({ message: "Import ข้อมูลเรียบร้อย", count: salaryTemps.length });
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @summary Import ข้อมูลประวัติตำแหน่งเงินเดือนของลูกจ้างประจำเข้าตาราง ProfileSalaryTemp
|
|
||||||
* @param profileEmployeeId Id โปรไฟล์ลูกจ้างประจำ
|
|
||||||
* @param file Excel file with salary history data
|
|
||||||
*/
|
|
||||||
@Post("employee-profileSalaryTemp/{profileEmployeeId}")
|
|
||||||
@UseInterceptors(FileInterceptor("file"))
|
|
||||||
async UploadProfileEmployeeSalaryTemp(
|
|
||||||
@Path() profileEmployeeId: string,
|
|
||||||
@Request() req: RequestWithUser,
|
|
||||||
@UploadedFile() file: Express.Multer.File,
|
|
||||||
) {
|
|
||||||
if (!profileEmployeeId) {
|
|
||||||
throw new Error("profileEmployeeId is required");
|
|
||||||
}
|
|
||||||
|
|
||||||
// อ่านไฟล์ Excel ก่อน (นอก transaction)
|
|
||||||
const workbook = xlsx.read(file.buffer, { type: "buffer" });
|
|
||||||
const sheetName = workbook.SheetNames[0];
|
|
||||||
const sheet = workbook.Sheets[sheetName];
|
|
||||||
const getExcel = xlsx.utils.sheet_to_json(sheet, { header: 1 }) as any[][];
|
|
||||||
|
|
||||||
let salaryTemps: ProfileSalaryTemp[] = [];
|
|
||||||
let dateTime = new Date();
|
|
||||||
|
|
||||||
// เริ่มจาก index 1 เพื่อข้าม header row
|
|
||||||
for (let i = 1; i < getExcel.length; i++) {
|
|
||||||
const row = getExcel[i];
|
|
||||||
|
|
||||||
// ข้าม empty rows
|
|
||||||
if (!row || row.length === 0) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ข้ามแถวที่ไม่มีลำดับ (row[0] เป็น null, undefined หรือค่าว่าง)
|
|
||||||
if (!row[0]) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const salaryTemp = new ProfileSalaryTemp();
|
|
||||||
|
|
||||||
// ฟังก์ชันแปลงวันที่จาก Excel รองรับทั้ง string format และ serial number
|
|
||||||
const parseExcelDate = (value: any): Date | null => {
|
|
||||||
if (!value) return null;
|
|
||||||
|
|
||||||
// กรณี 1: Excel serial number (ตัวเลข)
|
|
||||||
if (typeof value === "number") {
|
|
||||||
// Excel serial number = จำนวนวันตั้งแต่ 1 ม.ค. 1900
|
|
||||||
// แปลงเป็น JavaScript Date (epoch 1970)
|
|
||||||
let jsDate = new Date(Math.round((value - 25569) * 86400 * 1000));
|
|
||||||
|
|
||||||
// ตรวจสอบและแปลงปี พ.ศ. เป็น ค.ศ. (ถ้าปี > 2500)
|
|
||||||
if (jsDate.getFullYear() > 2500) {
|
|
||||||
const newYear = jsDate.getFullYear() - 543;
|
|
||||||
jsDate = new Date(
|
|
||||||
newYear,
|
|
||||||
jsDate.getMonth(),
|
|
||||||
jsDate.getDate(),
|
|
||||||
jsDate.getHours(),
|
|
||||||
jsDate.getMinutes(),
|
|
||||||
jsDate.getSeconds(),
|
|
||||||
jsDate.getMilliseconds()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return jsDate;
|
|
||||||
}
|
|
||||||
|
|
||||||
// กรณี 2: String format (dd/mm/yyyy หรือ d/m/yyyy)
|
|
||||||
const dateStr = value.toString().trim();
|
|
||||||
|
|
||||||
// ตรวจสอบว่าเป็น serial number ที่เป็น string หรือไม่
|
|
||||||
if (/^\d+$/.test(dateStr)) {
|
|
||||||
const serialNum = parseInt(dateStr);
|
|
||||||
let jsDate = new Date(Math.round((serialNum - 25569) * 86400 * 1000));
|
|
||||||
|
|
||||||
// ตรวจสอบและแปลงปี พ.ศ. เป็น ค.ศ. (ถ้าปี > 2500)
|
|
||||||
if (jsDate.getFullYear() > 2500) {
|
|
||||||
const newYear = jsDate.getFullYear() - 543;
|
|
||||||
jsDate = new Date(
|
|
||||||
newYear,
|
|
||||||
jsDate.getMonth(),
|
|
||||||
jsDate.getDate(),
|
|
||||||
jsDate.getHours(),
|
|
||||||
jsDate.getMinutes(),
|
|
||||||
jsDate.getSeconds(),
|
|
||||||
jsDate.getMilliseconds()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return jsDate;
|
|
||||||
}
|
|
||||||
|
|
||||||
// String format ปกติ (dd/mm/yyyy)
|
|
||||||
const dateParts = dateStr.split("/");
|
|
||||||
if (dateParts.length === 3) {
|
|
||||||
// แปลงเป็นตัวเลขแล้วค่อยจัดรูปแบบใหม่ เพื่อรองรับทั้ง 1 หลักและ 2 หลัก
|
|
||||||
const day = parseInt(dateParts[0].trim()).toString().padStart(2, "0");
|
|
||||||
const month = parseInt(dateParts[1].trim()).toString().padStart(2, "0");
|
|
||||||
let year = parseInt(dateParts[2].trim());
|
|
||||||
if (year > 2500) {
|
|
||||||
year -= 543;
|
|
||||||
}
|
|
||||||
const result = new Date(`${year}-${month}-${day}`);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Index 1: วันที่คำสั่งมีผล
|
|
||||||
let commandDateAffect: Date | null = null;
|
|
||||||
if (row[1]) {
|
|
||||||
commandDateAffect = parseExcelDate(row[1]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Index 25: วันที่ลงนาม
|
|
||||||
let commandDateSign: Date | null = null;
|
|
||||||
if (row[25]) {
|
|
||||||
commandDateSign = parseExcelDate(row[25]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Map ข้อมูลจาก Excel ไปยัง ProfileSalaryTemp ตามลำดับ column
|
|
||||||
// ข้อมูลระบบ
|
|
||||||
salaryTemp.profileEmployeeId = profileEmployeeId;
|
|
||||||
salaryTemp.profileId = null as any;
|
|
||||||
|
|
||||||
// Index 0: ลำดับ
|
|
||||||
salaryTemp.order = row[0] ? parseInt(row[0].toString()) : (null as any);
|
|
||||||
|
|
||||||
// Index 1: วันที่คำสั่งมีผล
|
|
||||||
salaryTemp.commandDateAffect = commandDateAffect as any;
|
|
||||||
|
|
||||||
// Index 2: ตำแหน่งในสายงาน
|
|
||||||
salaryTemp.positionName = row[2] || null;
|
|
||||||
|
|
||||||
// Index 3: ตำแหน่งประเภท
|
|
||||||
salaryTemp.positionType = row[3] || null;
|
|
||||||
|
|
||||||
// Index 4: ระดับ
|
|
||||||
salaryTemp.positionLevel = row[4] || null;
|
|
||||||
|
|
||||||
// Index 5: ระดับซี
|
|
||||||
salaryTemp.positionCee = row[5] || null;
|
|
||||||
|
|
||||||
// Index 6: สายงาน
|
|
||||||
salaryTemp.positionLine = row[6] || null;
|
|
||||||
|
|
||||||
// Index 7: ด้าน/สาขา
|
|
||||||
salaryTemp.positionPathSide = row[7] || null;
|
|
||||||
|
|
||||||
// Index 8: ตำแหน่งทางการบริหาร
|
|
||||||
salaryTemp.positionExecutive = row[8] || null;
|
|
||||||
|
|
||||||
// Index 9: ด้านทางการบริหาร
|
|
||||||
salaryTemp.positionExecutiveField = row[9] || null;
|
|
||||||
|
|
||||||
// Index 10: เงินเดือน
|
|
||||||
salaryTemp.amount = row[10] || 0;
|
|
||||||
|
|
||||||
// Index 11: เงินค่าตอบแทนรายเดือน
|
|
||||||
salaryTemp.mouthSalaryAmount = row[11] || 0;
|
|
||||||
|
|
||||||
// Index 12: เงินประจำตำแหน่ง
|
|
||||||
salaryTemp.positionSalaryAmount = row[12] || 0;
|
|
||||||
|
|
||||||
// Index 13: เงินค่าตอบแทนพิเศษ
|
|
||||||
salaryTemp.amountSpecial = row[13] || 0;
|
|
||||||
|
|
||||||
// Index 14: หน่วยงาน
|
|
||||||
salaryTemp.orgRoot = row[14] || null;
|
|
||||||
|
|
||||||
// Index 15: ส่วนราชการระดับ 1
|
|
||||||
salaryTemp.orgChild1 = row[15] || null;
|
|
||||||
|
|
||||||
// Index 16: ส่วนราชการระดับ 2
|
|
||||||
salaryTemp.orgChild2 = row[16] || null;
|
|
||||||
|
|
||||||
// Index 17: ส่วนราชการระดับ 3
|
|
||||||
salaryTemp.orgChild3 = row[17] || null;
|
|
||||||
|
|
||||||
// Index 18: ส่วนราชการระดับ 4
|
|
||||||
salaryTemp.orgChild4 = row[18] || null;
|
|
||||||
|
|
||||||
// Index 19: ตัวย่อเลขที่ตำแหน่ง
|
|
||||||
salaryTemp.posNoAbb = row[19] || null;
|
|
||||||
|
|
||||||
// Index 20: เลขที่ตำแหน่ง
|
|
||||||
salaryTemp.posNo = row[20] ? row[20].toString() : null;
|
|
||||||
|
|
||||||
// Index 21: หน่วยงานที่ออกคำสั่ง
|
|
||||||
salaryTemp.posNumCodeSit = row[21] || null;
|
|
||||||
|
|
||||||
// Index 22: ตัวย่อหน่วยงานที่ออกคำสั่ง
|
|
||||||
salaryTemp.posNumCodeSitAbb = row[22] || null;
|
|
||||||
|
|
||||||
// Index 23: เลขที่คำสั่ง
|
|
||||||
salaryTemp.commandNo = row[23] || null;
|
|
||||||
|
|
||||||
// Index 24: ปีเลขที่คำสั่ง (แปลงเป็น ค.ศ.)
|
|
||||||
let commandYearValue: number | null = null;
|
|
||||||
if (row[24]) {
|
|
||||||
commandYearValue = parseInt(row[24].toString());
|
|
||||||
// ถ้าปีเป็น พ.ศ. (มากกว่า 2500) ให้แปลงเป็น ค.ศ.
|
|
||||||
if (commandYearValue > 2500) {
|
|
||||||
commandYearValue -= 543;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
salaryTemp.commandYear = commandYearValue as any;
|
|
||||||
|
|
||||||
// Index 25: วันที่ลงนาม (แปลงแล้ว)
|
|
||||||
salaryTemp.commandDateSign = commandDateSign as any;
|
|
||||||
|
|
||||||
// Index 26: ประเภทคำสั่ง
|
|
||||||
salaryTemp.commandName = row[26] || null;
|
|
||||||
|
|
||||||
// Index 27: หมายเหตุ
|
|
||||||
salaryTemp.remark = row[27] || null;
|
|
||||||
|
|
||||||
// Index 28: commandId
|
|
||||||
salaryTemp.commandId = row[28] || null;
|
|
||||||
|
|
||||||
// Index 29: commandCode
|
|
||||||
salaryTemp.commandCode = row[29] || null;
|
|
||||||
|
|
||||||
// ข้อมูลระบบ
|
|
||||||
salaryTemp.isDelete = false;
|
|
||||||
salaryTemp.isEdit = false;
|
|
||||||
salaryTemp.isGovernment = false;
|
|
||||||
salaryTemp.isEntry = false;
|
|
||||||
salaryTemp.createdAt = dateTime;
|
|
||||||
salaryTemp.createdUserId = req.user?.sub || "";
|
|
||||||
salaryTemp.createdFullName = req.user?.name || "System Administrator";
|
|
||||||
salaryTemp.lastUpdatedAt = dateTime;
|
|
||||||
salaryTemp.lastUpdateUserId = req.user?.sub || "";
|
|
||||||
salaryTemp.lastUpdateFullName = req.user?.name || "System Administrator";
|
|
||||||
|
|
||||||
salaryTemps.push(salaryTemp);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ใช้ Transaction เพื่อความปลอดภัย
|
|
||||||
await AppDataSource.transaction(async (transactionalEntityManager) => {
|
|
||||||
// ล้างข้อมูลทั้งหมดในตาราง profileSalaryTemp ของ profileEmployeeId นั้น
|
|
||||||
await transactionalEntityManager.delete(ProfileSalaryTemp, { profileEmployeeId });
|
|
||||||
// Insert ข้อมูลใหม่
|
|
||||||
await transactionalEntityManager.save(ProfileSalaryTemp, salaryTemps);
|
|
||||||
});
|
|
||||||
|
|
||||||
return new HttpSuccess({ message: "Import ข้อมูลเรียบร้อย", count: salaryTemps.length });
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -315,81 +315,4 @@ export class KeycloakSyncController extends Controller {
|
||||||
...result,
|
...result,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Sync profiles with missing empType for a specific month (Admin only)
|
|
||||||
*
|
|
||||||
* @summary Find profiles updated in specified month with missing empType in Keycloak and sync them (ADMIN)
|
|
||||||
*
|
|
||||||
* @description
|
|
||||||
* This endpoint will:
|
|
||||||
* - List profiles from Profile table where lastUpdatedAt falls within the specified month
|
|
||||||
* - For each profile, check Keycloak if empType attribute is empty/null
|
|
||||||
* - If empType is empty, sync the profile using existing sync logic
|
|
||||||
* - Return summary of sync results
|
|
||||||
*
|
|
||||||
* Features:
|
|
||||||
* - Dry run mode (dryRun=true) to check without syncing
|
|
||||||
* - Configurable concurrency for parallel processing
|
|
||||||
* - Rate limiting to avoid overwhelming Keycloak
|
|
||||||
* - Detailed error reporting
|
|
||||||
* - Idempotent (can be safely re-run)
|
|
||||||
*
|
|
||||||
* @param {request} request Request body containing month parameter
|
|
||||||
* @param dryRun - If true, only check without syncing (default: false)
|
|
||||||
* @param concurrency - Number of parallel operations (default: 5)
|
|
||||||
* @param rateLimit - Requests per second limit (default: 10)
|
|
||||||
*/
|
|
||||||
@Post("sync-missing-emptype")
|
|
||||||
@Response<HttpError>(HttpStatus.BAD_REQUEST, "Invalid month format")
|
|
||||||
@Response<HttpError>(HttpStatus.INTERNAL_SERVER_ERROR, "Sync operation failed")
|
|
||||||
async syncMissingEmpType(
|
|
||||||
@Body() request: {
|
|
||||||
month: string;
|
|
||||||
profileType?: "PROFILE" | "PROFILE_EMPLOYEE";
|
|
||||||
},
|
|
||||||
@Query() dryRun: boolean = false,
|
|
||||||
@Query() concurrency: number = 5,
|
|
||||||
@Query() rateLimit: number = 10,
|
|
||||||
) {
|
|
||||||
const { month, profileType = "PROFILE" } = request;
|
|
||||||
|
|
||||||
// Validate month format (YYYY-MM)
|
|
||||||
const monthRegex = /^\d{4}-\d{2}$/;
|
|
||||||
if (!monthRegex.test(month)) {
|
|
||||||
throw new HttpError(HttpStatus.BAD_REQUEST, "รูปแบบเดือนไม่ถูกต้อง ต้องเป็น YYYY-MM");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate profileType
|
|
||||||
if (!["PROFILE", "PROFILE_EMPLOYEE"].includes(profileType)) {
|
|
||||||
throw new HttpError(
|
|
||||||
HttpStatus.BAD_REQUEST,
|
|
||||||
"profileType ต้องเป็น PROFILE หรือ PROFILE_EMPLOYEE เท่านั้น",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate concurrency
|
|
||||||
if (concurrency < 1 || concurrency > 20) {
|
|
||||||
throw new HttpError(HttpStatus.BAD_REQUEST, "concurrency ต้องอยู่ระหว่าง 1 ถึง 20");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate rateLimit
|
|
||||||
if (rateLimit < 1 || rateLimit > 50) {
|
|
||||||
throw new HttpError(HttpStatus.BAD_REQUEST, "rateLimit ต้องอยู่ระหว่าง 1 ถึง 50");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Execute sync
|
|
||||||
const result = await this.keycloakAttributeService.syncMissingEmpTypeByMonth({
|
|
||||||
month,
|
|
||||||
profileType,
|
|
||||||
dryRun,
|
|
||||||
concurrency,
|
|
||||||
rateLimit,
|
|
||||||
});
|
|
||||||
|
|
||||||
return new HttpSuccess({
|
|
||||||
message: `Sync ${dryRun ? "check " : ""}เสร็จสิ้น`,
|
|
||||||
...result,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -66,7 +66,7 @@ import {
|
||||||
import { orgStructureCache } from "../utils/OrgStructureCache";
|
import { orgStructureCache } from "../utils/OrgStructureCache";
|
||||||
import { OrgIdMapping, AllOrgMappings, SavePosMasterHistory } from "../interfaces/OrgMapping";
|
import { OrgIdMapping, AllOrgMappings, SavePosMasterHistory } from "../interfaces/OrgMapping";
|
||||||
import { OrgPermissionData, NodeLevel } from "../interfaces/OrgTypes";
|
import { OrgPermissionData, NodeLevel } from "../interfaces/OrgTypes";
|
||||||
import { formatPosMaster, generateLabelName, filterPosMasters, getPosMasterNo, getOrgFullName } from "../utils/org-formatting";
|
import { formatPosMaster, generateLabelName, filterPosMasters } from "../utils/org-formatting";
|
||||||
|
|
||||||
@Route("api/v1/org")
|
@Route("api/v1/org")
|
||||||
@Tags("Organization")
|
@Tags("Organization")
|
||||||
|
|
@ -8933,25 +8933,13 @@ export class OrganizationController extends Controller {
|
||||||
const draftPosMaster = draftPosMasterMap.get(draftPosMasterId) as any;
|
const draftPosMaster = draftPosMasterMap.get(draftPosMasterId) as any;
|
||||||
|
|
||||||
// Collect profile update for the selected position
|
// Collect profile update for the selected position
|
||||||
// อัพเดท org และ posMasterNo ตลอดไม่ต้องดัก isSit
|
|
||||||
if (nextHolderId != null && draftPos.positionIsSelected) {
|
|
||||||
const _null: any = null;
|
|
||||||
profileUpdates.set(nextHolderId, {
|
|
||||||
posMasterNo: draftPosMaster ? (getPosMasterNo(draftPosMaster as PosMaster) ?? _null) : _null,
|
|
||||||
org: draftPosMaster ? (getOrgFullName(draftPosMaster as PosMaster) ?? _null) : _null,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
// ถ้าไม่ใช่ตำแหน่งนั่งทับ (isSit = false) ถึงจะอัพเดทตำแหน่งในทะเบียนประวัติ
|
// ถ้าไม่ใช่ตำแหน่งนั่งทับ (isSit = false) ถึงจะอัพเดทตำแหน่งในทะเบียนประวัติ
|
||||||
if (nextHolderId != null && draftPos.positionIsSelected && !draftPosMaster?.isSit) {
|
if (nextHolderId != null && draftPos.positionIsSelected && !draftPosMaster?.isSit) {
|
||||||
const existing = profileUpdates.get(nextHolderId) || {};
|
profileUpdates.set(nextHolderId, {
|
||||||
existing.position = draftPos.positionName;
|
position: draftPos.positionName,
|
||||||
existing.posTypeId = draftPos.posTypeId;
|
posTypeId: draftPos.posTypeId,
|
||||||
existing.posLevelId = draftPos.posLevelId;
|
posLevelId: draftPos.posLevelId,
|
||||||
existing.positionField = draftPos.positionField ?? null;
|
});
|
||||||
existing.posExecutive = (draftPos as any).posExecutive?.posExecutiveName ?? null;
|
|
||||||
existing.positionArea = draftPos.positionArea ?? null;
|
|
||||||
existing.positionExecutiveField = draftPos.positionExecutiveField ?? null;
|
|
||||||
profileUpdates.set(nextHolderId, existing);
|
|
||||||
if (draftPosMaster && draftPosMaster.ancestorDNA) {
|
if (draftPosMaster && draftPosMaster.ancestorDNA) {
|
||||||
// Find the selected position from draft positions
|
// Find the selected position from draft positions
|
||||||
const selectedPos =
|
const selectedPos =
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,6 @@ import permission from "../interfaces/permission";
|
||||||
import { ProfileEmployee } from "../entities/ProfileEmployee";
|
import { ProfileEmployee } from "../entities/ProfileEmployee";
|
||||||
import { EmployeePosMaster } from "../entities/EmployeePosMaster";
|
import { EmployeePosMaster } from "../entities/EmployeePosMaster";
|
||||||
import { OrgRevision } from "../entities/OrgRevision";
|
import { OrgRevision } from "../entities/OrgRevision";
|
||||||
import { actingPositionService } from "../services/ActingPositionService";
|
|
||||||
const REDIS_HOST = process.env.REDIS_HOST;
|
const REDIS_HOST = process.env.REDIS_HOST;
|
||||||
const REDIS_PORT = process.env.REDIS_PORT;
|
const REDIS_PORT = process.env.REDIS_PORT;
|
||||||
|
|
||||||
|
|
@ -255,64 +254,6 @@ export class PermissionController extends Controller {
|
||||||
return new HttpSuccess(res);
|
return new HttpSuccess(res);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* API permission with acting positions
|
|
||||||
* @summary permission with acting positions (dotnet api)
|
|
||||||
* @param {string} action action
|
|
||||||
* @param {string} system authSysId
|
|
||||||
*/
|
|
||||||
@Get("dotnet-acting/{action}/{system}")
|
|
||||||
public async dotnetActing(
|
|
||||||
@Request() req: RequestWithUser,
|
|
||||||
@Path() action: string,
|
|
||||||
@Path() system: string,
|
|
||||||
) {
|
|
||||||
if (!["CREATE", "DELETE", "GET", "LIST", "UPDATE"].includes(action)) {
|
|
||||||
throw new HttpError(HttpStatus.NOT_FOUND, "Action ไม่ถูกต้อง");
|
|
||||||
}
|
|
||||||
// ดึง privilege ตามปกติ
|
|
||||||
let privilege = await new permission().Permission(req, system.toLocaleUpperCase(), action);
|
|
||||||
|
|
||||||
// ดึงข้อมูล profile และ orgRevision
|
|
||||||
let profile: any = await this.profileRepo.findOne({
|
|
||||||
select: ["id"],
|
|
||||||
where: { keycloak: req.user.sub },
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!profile) {
|
|
||||||
profile = await this.profileEmployeeRepo.findOne({
|
|
||||||
select: ["id"],
|
|
||||||
where: { keycloak: req.user.sub },
|
|
||||||
});
|
|
||||||
if (!profile) {
|
|
||||||
throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูลบุคคลนี้ในระบบ");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const orgRevision = await this.orgRevisionRepository.findOne({
|
|
||||||
select: ["id"],
|
|
||||||
where: {
|
|
||||||
orgRevisionIsDraft: false,
|
|
||||||
orgRevisionIsCurrent: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
// ดึงข้อมูลตำแหน่งที่รักษาการ
|
|
||||||
const actingData = await actingPositionService.getActingPositionsWithPrivilege(
|
|
||||||
profile.id,
|
|
||||||
orgRevision?.id,
|
|
||||||
action,
|
|
||||||
system.toLocaleUpperCase()
|
|
||||||
);
|
|
||||||
|
|
||||||
// ส่งค่ากลับเหมือน dotnet endpoint แต่เพิ่ม isAct และ posMasterActs
|
|
||||||
return new HttpSuccess({
|
|
||||||
privilege,
|
|
||||||
isAct: actingData.isAct,
|
|
||||||
posMasterActs: actingData.posMasterActs,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* API permission (dotnet api)
|
* API permission (dotnet api)
|
||||||
* @summary permission (dotnet api)
|
* @summary permission (dotnet api)
|
||||||
|
|
|
||||||
|
|
@ -39,7 +39,6 @@ import { AuthRole } from "../entities/AuthRole";
|
||||||
import { RequestWithUser } from "../middlewares/user";
|
import { RequestWithUser } from "../middlewares/user";
|
||||||
import permission from "../interfaces/permission";
|
import permission from "../interfaces/permission";
|
||||||
import { resolveNodeLevel, setLogDataDiff } from "../interfaces/utils";
|
import { resolveNodeLevel, setLogDataDiff } from "../interfaces/utils";
|
||||||
import { getPosMasterNo, getOrgFullName } from "../utils/org-formatting";
|
|
||||||
import { PosMasterAssign } from "../entities/PosMasterAssign";
|
import { PosMasterAssign } from "../entities/PosMasterAssign";
|
||||||
import { Assign } from "../entities/Assign";
|
import { Assign } from "../entities/Assign";
|
||||||
import { ProfileEmployee } from "../entities/ProfileEmployee";
|
import { ProfileEmployee } from "../entities/ProfileEmployee";
|
||||||
|
|
@ -1257,15 +1256,7 @@ export class PositionController extends Controller {
|
||||||
) {
|
) {
|
||||||
await new permission().PermissionUpdate(request, "SYS_ORG");
|
await new permission().PermissionUpdate(request, "SYS_ORG");
|
||||||
const posMaster = await this.posMasterRepository.findOne({
|
const posMaster = await this.posMasterRepository.findOne({
|
||||||
relations: [
|
relations: ["positions", "orgRevision"],
|
||||||
"positions",
|
|
||||||
"orgRevision",
|
|
||||||
"orgRoot",
|
|
||||||
"orgChild1",
|
|
||||||
"orgChild2",
|
|
||||||
"orgChild3",
|
|
||||||
"orgChild4",
|
|
||||||
],
|
|
||||||
where: { id: id },
|
where: { id: id },
|
||||||
});
|
});
|
||||||
if (!posMaster) {
|
if (!posMaster) {
|
||||||
|
|
@ -1460,17 +1451,6 @@ export class PositionController extends Controller {
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
// อัพเดท org และ posMasterNo ตลอดไม่ต้องดัก isSit
|
|
||||||
if (posMaster.orgRevision?.orgRevisionIsCurrent == true && posMaster.current_holderId) {
|
|
||||||
const _profile = await this.profileRepository.findOne({
|
|
||||||
where: { id: posMaster.current_holderId },
|
|
||||||
});
|
|
||||||
if (_profile) {
|
|
||||||
_profile.posMasterNo = getPosMasterNo(posMaster);
|
|
||||||
_profile.org = getOrgFullName(posMaster);
|
|
||||||
await this.profileRepository.save(_profile);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// ถ้าไม่ใช่ตำแหน่งนั่งทับ (isSit = false) ถึงจะอัพเดทตำแหน่งในทะเบียนประวัติ
|
// ถ้าไม่ใช่ตำแหน่งนั่งทับ (isSit = false) ถึงจะอัพเดทตำแหน่งในทะเบียนประวัติ
|
||||||
if (posMaster.orgRevision?.orgRevisionIsCurrent == true && !posMaster.isSit) {
|
if (posMaster.orgRevision?.orgRevisionIsCurrent == true && !posMaster.isSit) {
|
||||||
const _position = requestBody.positions.find((p) => p.positionIsSelected == true);
|
const _position = requestBody.positions.find((p) => p.positionIsSelected == true);
|
||||||
|
|
@ -1483,10 +1463,6 @@ export class PositionController extends Controller {
|
||||||
_profile.position = _position.posDictName ?? _null;
|
_profile.position = _position.posDictName ?? _null;
|
||||||
_profile.posTypeId = _position.posTypeId;
|
_profile.posTypeId = _position.posTypeId;
|
||||||
_profile.posLevelId = _position.posLevelId;
|
_profile.posLevelId = _position.posLevelId;
|
||||||
_profile.positionField = _position.posDictField ?? _null;
|
|
||||||
_profile.posExecutive = _position.posExecutiveId ?? _null;
|
|
||||||
_profile.positionArea = _position.posDictArea ?? _null;
|
|
||||||
_profile.positionExecutiveField = _position.posDictExecutiveField ?? _null;
|
|
||||||
await this.profileRepository.save(_profile);
|
await this.profileRepository.save(_profile);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -2411,16 +2387,16 @@ export class PositionController extends Controller {
|
||||||
? "posMaster.orgRootId IN (:...root)"
|
? "posMaster.orgRootId IN (:...root)"
|
||||||
: "posMaster.orgRootId is null"
|
: "posMaster.orgRootId is null"
|
||||||
: "1=1",
|
: "1=1",
|
||||||
{ root: _data.root },
|
{ root: _data.root }
|
||||||
)
|
)
|
||||||
.andWhere(
|
.andWhere(
|
||||||
_data.child1 != undefined && _data.child1 != null
|
_data.child1 != undefined && _data.child1 != null
|
||||||
? _data.child1[0] != null
|
? _data.child1[0] != null
|
||||||
? "posMaster.orgChild1Id IN (:...child1)"
|
? "posMaster.orgChild1Id IN (:...child1)"
|
||||||
: // : `posMaster.orgChild1Id is ${_data.privilege == "PARENT" ? "not null" : "null"}`
|
// : `posMaster.orgChild1Id is ${_data.privilege == "PARENT" ? "not null" : "null"}`
|
||||||
`posMaster.orgChild1Id is null`
|
: `posMaster.orgChild1Id is null`
|
||||||
: "1=1",
|
: "1=1",
|
||||||
{ child1: _data.child1 },
|
{ child1: _data.child1 }
|
||||||
)
|
)
|
||||||
.andWhere(
|
.andWhere(
|
||||||
_data.child2 != undefined && _data.child2 != null
|
_data.child2 != undefined && _data.child2 != null
|
||||||
|
|
@ -2451,14 +2427,13 @@ export class PositionController extends Controller {
|
||||||
{
|
{
|
||||||
child4: _data.child4,
|
child4: _data.child4,
|
||||||
},
|
},
|
||||||
);
|
)
|
||||||
// .andWhere(checkChildConditions)
|
// .andWhere(checkChildConditions)
|
||||||
// .andWhere(typeCondition)
|
// .andWhere(typeCondition)
|
||||||
// .andWhere(revisionCondition);
|
// .andWhere(revisionCondition);
|
||||||
|
|
||||||
if (body.keyword != null && body.keyword != "") {
|
if (body.keyword != null && body.keyword != "") {
|
||||||
query
|
query.orWhere(
|
||||||
.orWhere(
|
|
||||||
new Brackets((qb) => {
|
new Brackets((qb) => {
|
||||||
qb.andWhere(
|
qb.andWhere(
|
||||||
body.keyword != null && body.keyword != ""
|
body.keyword != null && body.keyword != ""
|
||||||
|
|
@ -3818,7 +3793,7 @@ export class PositionController extends Controller {
|
||||||
await new permission().PermissionUpdate(request, "SYS_ORG");
|
await new permission().PermissionUpdate(request, "SYS_ORG");
|
||||||
const dataMaster = await this.posMasterRepository.findOne({
|
const dataMaster = await this.posMasterRepository.findOne({
|
||||||
where: { id: requestBody.posMaster },
|
where: { id: requestBody.posMaster },
|
||||||
relations: ["positions", "orgRoot", "orgChild1", "orgChild2", "orgChild3", "orgChild4"],
|
relations: ["positions"],
|
||||||
});
|
});
|
||||||
if (!dataMaster) {
|
if (!dataMaster) {
|
||||||
throw new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบข้อมูลตำแหน่งนี้");
|
throw new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบข้อมูลตำแหน่งนี้");
|
||||||
|
|
@ -3850,26 +3825,18 @@ export class PositionController extends Controller {
|
||||||
if (_profile) {
|
if (_profile) {
|
||||||
let _position = await this.positionRepository.findOne({
|
let _position = await this.positionRepository.findOne({
|
||||||
where: { id: requestBody.position, posMasterId: requestBody.posMaster },
|
where: { id: requestBody.position, posMasterId: requestBody.posMaster },
|
||||||
relations: ["posExecutive"],
|
|
||||||
});
|
});
|
||||||
if (_position) {
|
if (_position) {
|
||||||
// อัพเดท org และ posMasterNo ตลอดไม่ต้องดัก isSit
|
|
||||||
_profile.posMasterNo = getPosMasterNo(dataMaster);
|
|
||||||
_profile.org = getOrgFullName(dataMaster);
|
|
||||||
// ถ้าไม่ใช่ตำแหน่งนั่งทับ (isSit = false) ถึงจะอัพเดทตำแหน่งในทะเบียนประวัติ
|
// ถ้าไม่ใช่ตำแหน่งนั่งทับ (isSit = false) ถึงจะอัพเดทตำแหน่งในทะเบียนประวัติ
|
||||||
if (!dataMaster.isSit) {
|
if(!dataMaster.isSit){
|
||||||
_profile.position = _position.positionName;
|
_profile.position = _position.positionName;
|
||||||
_profile.posTypeId = _position.posTypeId;
|
_profile.posTypeId = _position.posTypeId;
|
||||||
_profile.posLevelId = _position.posLevelId;
|
_profile.posLevelId = _position.posLevelId;
|
||||||
_profile.positionField = _position.positionField ?? _null;
|
|
||||||
_profile.posExecutive = _position.posExecutive?.posExecutiveName ?? _null;
|
|
||||||
_profile.positionArea = _position.positionArea ?? _null;
|
|
||||||
_profile.positionExecutiveField = _position.positionExecutiveField ?? _null;
|
|
||||||
}
|
|
||||||
await this.profileRepository.save(_profile);
|
await this.profileRepository.save(_profile);
|
||||||
setLogDataDiff(request, { before, after: _profile });
|
setLogDataDiff(request, { before, after: _profile });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
dataMaster.current_holderId = requestBody.profileId;
|
dataMaster.current_holderId = requestBody.profileId;
|
||||||
dataMaster.next_holderId = _null;
|
dataMaster.next_holderId = _null;
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -5202,9 +5169,9 @@ export class PositionController extends Controller {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* API รายการตำแหน่งติดเงื่อนไข
|
* API รายการอัตรากำลัง
|
||||||
*
|
*
|
||||||
* @summary รายการตำแหน่งติดเงื่อนไข
|
* @summary ORG_070 - รายการอัตรากำลัง (ADMIN) #56
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
@Post("master/position-condition")
|
@Post("master/position-condition")
|
||||||
|
|
@ -5215,7 +5182,7 @@ export class PositionController extends Controller {
|
||||||
id: string;
|
id: string;
|
||||||
revisionId: string;
|
revisionId: string;
|
||||||
type: number;
|
type: number;
|
||||||
isAll: boolean; // true คือเลือกเฉพาะตำแหน่งติดเงื่อนไข / false คือเลือกตำแหน่งทั้งหมด
|
isAll: boolean;
|
||||||
page: number;
|
page: number;
|
||||||
pageSize: number;
|
pageSize: number;
|
||||||
keyword?: string;
|
keyword?: string;
|
||||||
|
|
@ -5235,7 +5202,7 @@ export class PositionController extends Controller {
|
||||||
let level: any = resolveNodeLevel(orgDna);
|
let level: any = resolveNodeLevel(orgDna);
|
||||||
|
|
||||||
const cannotViewRootPosMaster =
|
const cannotViewRootPosMaster =
|
||||||
_data.privilege === "PARENT" ||
|
(_data.privilege === "PARENT") ||
|
||||||
(_data.privilege === "BROTHER" && level > 1) ||
|
(_data.privilege === "BROTHER" && level > 1) ||
|
||||||
(_data.privilege === "CHILD" && level > 0) ||
|
(_data.privilege === "CHILD" && level > 0) ||
|
||||||
(_data.privilege === "NORMAL" && level != 0);
|
(_data.privilege === "NORMAL" && level != 0);
|
||||||
|
|
@ -5267,46 +5234,46 @@ export class PositionController extends Controller {
|
||||||
typeCondition = {
|
typeCondition = {
|
||||||
...(cannotViewRootPosMaster ? { orgRootId: null } : { orgRootId: body.id }),
|
...(cannotViewRootPosMaster ? { orgRootId: null } : { orgRootId: body.id }),
|
||||||
};
|
};
|
||||||
// if (!body.isAll) {
|
if (!body.isAll) {
|
||||||
// checkChildConditions = {
|
checkChildConditions = {
|
||||||
// orgChild1Id: IsNull(),
|
orgChild1Id: IsNull(),
|
||||||
// };
|
};
|
||||||
// searchShortName = `CONCAT(orgRoot.orgRootShortName," ",COALESCE(posMaster.posMasterNoPrefix, ""),posMaster.posMasterNo,COALESCE(posMaster.posMasterNoSuffix, "")) like '%${body.keyword}%'`;
|
searchShortName = `CONCAT(orgRoot.orgRootShortName," ",COALESCE(posMaster.posMasterNoPrefix, ""),posMaster.posMasterNo,COALESCE(posMaster.posMasterNoSuffix, "")) like '%${body.keyword}%'`;
|
||||||
// } else {
|
} else {
|
||||||
// }
|
}
|
||||||
} else if (body.type === 1) {
|
} else if (body.type === 1) {
|
||||||
typeCondition = {
|
typeCondition = {
|
||||||
...(cannotViewChild1PosMaster ? { orgChild1Id: null } : { orgChild1Id: body.id }),
|
...(cannotViewChild1PosMaster ? { orgChild1Id: null } : { orgChild1Id: body.id }),
|
||||||
};
|
};
|
||||||
// if (!body.isAll) {
|
if (!body.isAll) {
|
||||||
// checkChildConditions = {
|
checkChildConditions = {
|
||||||
// orgChild2Id: IsNull(),
|
orgChild2Id: IsNull(),
|
||||||
// };
|
};
|
||||||
// searchShortName = `CONCAT(orgChild1.orgChild1ShortName," ",COALESCE(posMaster.posMasterNoPrefix, ""),posMaster.posMasterNo,COALESCE(posMaster.posMasterNoSuffix, "")) like '%${body.keyword}%'`;
|
searchShortName = `CONCAT(orgChild1.orgChild1ShortName," ",COALESCE(posMaster.posMasterNoPrefix, ""),posMaster.posMasterNo,COALESCE(posMaster.posMasterNoSuffix, "")) like '%${body.keyword}%'`;
|
||||||
// } else {
|
} else {
|
||||||
// }
|
}
|
||||||
} else if (body.type === 2) {
|
} else if (body.type === 2) {
|
||||||
typeCondition = {
|
typeCondition = {
|
||||||
...(cannotViewChild2PosMaster ? { orgChild2Id: null } : { orgChild2Id: body.id }),
|
...(cannotViewChild2PosMaster ? { orgChild2Id: null } : { orgChild2Id: body.id }),
|
||||||
};
|
};
|
||||||
// if (!body.isAll) {
|
if (!body.isAll) {
|
||||||
// checkChildConditions = {
|
checkChildConditions = {
|
||||||
// orgChild3Id: IsNull(),
|
orgChild3Id: IsNull(),
|
||||||
// };
|
};
|
||||||
// searchShortName = `CONCAT(orgChild2.orgChild2ShortName," ",COALESCE(posMaster.posMasterNoPrefix, ""),posMaster.posMasterNo,COALESCE(posMaster.posMasterNoSuffix, "")) like '%${body.keyword}%'`;
|
searchShortName = `CONCAT(orgChild2.orgChild2ShortName," ",COALESCE(posMaster.posMasterNoPrefix, ""),posMaster.posMasterNo,COALESCE(posMaster.posMasterNoSuffix, "")) like '%${body.keyword}%'`;
|
||||||
// } else {
|
} else {
|
||||||
// }
|
}
|
||||||
} else if (body.type === 3) {
|
} else if (body.type === 3) {
|
||||||
typeCondition = {
|
typeCondition = {
|
||||||
...(cannotViewChild3PosMaster ? { orgChild3Id: null } : { orgChild3Id: body.id }),
|
...(cannotViewChild3PosMaster ? { orgChild3Id: null } : { orgChild3Id: body.id }),
|
||||||
};
|
};
|
||||||
// if (!body.isAll) {
|
if (!body.isAll) {
|
||||||
// checkChildConditions = {
|
checkChildConditions = {
|
||||||
// orgChild4Id: IsNull(),
|
orgChild4Id: IsNull(),
|
||||||
// };
|
};
|
||||||
// searchShortName = `CONCAT(orgChild3.orgChild3ShortName," ",COALESCE(posMaster.posMasterNoPrefix, ""),posMaster.posMasterNo,COALESCE(posMaster.posMasterNoSuffix, "")) like '%${body.keyword}%'`;
|
searchShortName = `CONCAT(orgChild3.orgChild3ShortName," ",COALESCE(posMaster.posMasterNoPrefix, ""),posMaster.posMasterNo,COALESCE(posMaster.posMasterNoSuffix, "")) like '%${body.keyword}%'`;
|
||||||
// } else {
|
} else {
|
||||||
// }
|
}
|
||||||
} else if (body.type === 4) {
|
} else if (body.type === 4) {
|
||||||
typeCondition = {
|
typeCondition = {
|
||||||
...(cannotViewChild4PosMaster ? { orgChild4Id: null } : { orgChild4Id: body.id }),
|
...(cannotViewChild4PosMaster ? { orgChild4Id: null } : { orgChild4Id: body.id }),
|
||||||
|
|
@ -5379,7 +5346,7 @@ export class PositionController extends Controller {
|
||||||
(masterId.length > 0
|
(masterId.length > 0
|
||||||
? { id: In(masterId) }
|
? { id: In(masterId) }
|
||||||
: { posMasterNo: Like(`%${body.keyword}%`) })),
|
: { posMasterNo: Like(`%${body.keyword}%`) })),
|
||||||
...(!body.isAll && { isCondition: true }),
|
current_holderId: IsNull(),
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
let [posMaster, total] = await AppDataSource.getRepository(PosMaster)
|
let [posMaster, total] = await AppDataSource.getRepository(PosMaster)
|
||||||
|
|
@ -5448,15 +5415,15 @@ export class PositionController extends Controller {
|
||||||
new Brackets((qb) => {
|
new Brackets((qb) => {
|
||||||
qb.andWhere(
|
qb.andWhere(
|
||||||
body.keyword != null && body.keyword != ""
|
body.keyword != null && body.keyword != ""
|
||||||
? `CASE WHEN posMaster.orgChild1 is null THEN ${searchShortName0} WHEN posMaster.orgChild2 is null THEN ${searchShortName1} WHEN posMaster.orgChild3 is null THEN ${searchShortName2} WHEN posMaster.orgChild4 is null THEN ${searchShortName3} ELSE ${searchShortName4} END LIKE '%${body.keyword}%'`
|
? body.isAll == false
|
||||||
|
? searchShortName
|
||||||
|
: `CASE WHEN posMaster.orgChild1 is null THEN ${searchShortName0} WHEN posMaster.orgChild2 is null THEN ${searchShortName1} WHEN posMaster.orgChild3 is null THEN ${searchShortName2} WHEN posMaster.orgChild4 is null THEN ${searchShortName3} ELSE ${searchShortName4} END LIKE '%${body.keyword}%'`
|
||||||
: "1=1",
|
: "1=1",
|
||||||
)
|
)
|
||||||
.andWhere(checkChildConditions)
|
.andWhere(checkChildConditions)
|
||||||
.andWhere(typeCondition)
|
.andWhere(typeCondition)
|
||||||
.andWhere(revisionCondition);
|
.andWhere(revisionCondition)
|
||||||
if (!body.isAll) {
|
.andWhere({ current_holderId: IsNull() });
|
||||||
qb.andWhere({ isCondition: true });
|
|
||||||
}
|
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.orWhere(
|
.orWhere(
|
||||||
|
|
@ -5466,10 +5433,8 @@ export class PositionController extends Controller {
|
||||||
)
|
)
|
||||||
.andWhere(checkChildConditions)
|
.andWhere(checkChildConditions)
|
||||||
.andWhere(typeCondition)
|
.andWhere(typeCondition)
|
||||||
.andWhere(revisionCondition);
|
.andWhere(revisionCondition)
|
||||||
if (!body.isAll) {
|
.andWhere({ current_holderId: IsNull() });
|
||||||
qb.andWhere({ isCondition: true });
|
|
||||||
}
|
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.orderBy("orgRoot.orgRootOrder", "ASC")
|
.orderBy("orgRoot.orgRootOrder", "ASC")
|
||||||
|
|
|
||||||
|
|
@ -1679,84 +1679,35 @@ export class ProfileController extends Controller {
|
||||||
// ประวัติพ้นจากราชการ
|
// ประวัติพ้นจากราชการ
|
||||||
let retires = [];
|
let retires = [];
|
||||||
const currentDate = new Date();
|
const currentDate = new Date();
|
||||||
|
// todo: รอข้อสรุป
|
||||||
|
// const retire_raw = await this.salaryRepo.findOne({
|
||||||
|
// where: {
|
||||||
|
// profileId: id,
|
||||||
|
// commandCode: In(["12", "15", "16"]),
|
||||||
|
// },
|
||||||
|
// order: { order: "desc" },
|
||||||
|
// });
|
||||||
|
|
||||||
// commandCode ที่ถือว่าออกจากราชการ
|
// if (retire_raw) {
|
||||||
const retireCommandCodes = ["12", "15", "16"];
|
// const startDate = retire_raw.commandDateAffect;
|
||||||
|
|
||||||
// ดึงข้อมูล profileSalary ทั้งหมดเพื่อหาประวัติพ้นจากราชการ
|
// // คำนวณจำนวนวันจากวันพ้นสภาพถึงปัจจุบัน
|
||||||
const salaries = await this.salaryRepo.find({
|
// let daysCount = 0;
|
||||||
where: { profileId: id },
|
// if (startDate) {
|
||||||
order: { order: "ASC" },
|
// const start = new Date(startDate);
|
||||||
});
|
// daysCount = Math.ceil((currentDate.getTime() - start.getTime()) / (1000 * 60 * 60 * 24));
|
||||||
|
// }
|
||||||
|
|
||||||
// มีคำสั่งพ้นราชการหรือไม่
|
// const startDateStr = startDate
|
||||||
if (
|
// ? Extension.ToThaiNumber(Extension.ToThaiFullDate2(startDate))
|
||||||
salaries.length > 0 &&
|
// : "-";
|
||||||
salaries.some((s) => s.commandCode && retireCommandCodes.includes(s.commandCode))
|
|
||||||
) {
|
|
||||||
// กรองข้อมูลซ้ำตาม commandDateAffect
|
|
||||||
const uniqueSalaries = salaries.filter(
|
|
||||||
(item, index, self) =>
|
|
||||||
index ===
|
|
||||||
self.findIndex(
|
|
||||||
(t) => t.commandDateAffect?.getTime() === item.commandDateAffect?.getTime(),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
// วนลูปหาคู่ของ "ออกราชการ" และ "กลับเข้าราชการ"
|
// retires.push({
|
||||||
for (let i = 0; i < uniqueSalaries.length; i++) {
|
// date: `${startDateStr}`,
|
||||||
const current = uniqueSalaries[i];
|
// detail: retire_raw.commandName ?? "-",
|
||||||
|
// day: daysCount > 0 ? Extension.ToThaiNumber(daysCount.toLocaleString()) : "-"
|
||||||
// เป็นคำสั่งออกจากราชการหรือไม่
|
// });
|
||||||
if (current.commandCode && retireCommandCodes.includes(current.commandCode)) {
|
// }
|
||||||
const startDate = current.commandDateAffect;
|
|
||||||
let endDate: Date | null = null;
|
|
||||||
let endRecord = null;
|
|
||||||
|
|
||||||
// หาคำสั่งถัดไปที่ไม่ใช่การออกจากราชการ (ถือว่ากลับเข้าราชการ)
|
|
||||||
for (let j = i + 1; j < uniqueSalaries.length; j++) {
|
|
||||||
const next = uniqueSalaries[j];
|
|
||||||
if (next.commandCode && !retireCommandCodes.includes(next.commandCode)) {
|
|
||||||
endDate = next.commandDateAffect;
|
|
||||||
endRecord = next;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ถ้าไม่เจอคำสั่งกลับเข้า ให้ใช้วันปัจจุบัน
|
|
||||||
if (!endDate) {
|
|
||||||
endDate = currentDate;
|
|
||||||
}
|
|
||||||
|
|
||||||
// คำนวณจำนวนวัน
|
|
||||||
let daysCount = 0;
|
|
||||||
if (startDate && endDate) {
|
|
||||||
const start = new Date(startDate);
|
|
||||||
const end = new Date(endDate);
|
|
||||||
daysCount = Math.ceil((end.getTime() - start.getTime()) / (1000 * 60 * 60 * 24));
|
|
||||||
}
|
|
||||||
|
|
||||||
// สร้าง detail จาก commandName + remark
|
|
||||||
const commandName = current.commandName || "";
|
|
||||||
const remark = current.remark || "";
|
|
||||||
const detail = `${commandName} ${remark}`.trim();
|
|
||||||
|
|
||||||
// แปลงวันที่เป็น format ไทย
|
|
||||||
const startDateStr = startDate
|
|
||||||
? Extension.ToThaiNumber(Extension.ToThaiFullDate2(startDate))
|
|
||||||
: "-";
|
|
||||||
const endDateStr = endDate
|
|
||||||
? Extension.ToThaiNumber(Extension.ToThaiFullDate2(endDate))
|
|
||||||
: "-";
|
|
||||||
|
|
||||||
retires.push({
|
|
||||||
date: `${startDateStr} - ${endDateStr}`,
|
|
||||||
detail: detail || "-",
|
|
||||||
day: daysCount > 0 ? Extension.ToThaiNumber(daysCount.toLocaleString()) : "-",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// กรณีไม่มีข้อมูล
|
// กรณีไม่มีข้อมูล
|
||||||
if (retires.length === 0) {
|
if (retires.length === 0) {
|
||||||
|
|
@ -12014,90 +11965,4 @@ export class ProfileController extends Controller {
|
||||||
|
|
||||||
return new HttpSuccess();
|
return new HttpSuccess();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* API ข้อมูลทะเบียนประวัติตาม keycloak สำหรับเช็คอินเข้าใช้งานระบบ
|
|
||||||
*
|
|
||||||
* @summary ข้อมูลทะเบียนประวัติตาม keycloak สำหรับเช็คอินเข้าใช้งานระบบ
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
@Get("keycloak/position-checkin")
|
|
||||||
async getProfileByKeycloakForCheckin(@Request() request: { user: Record<string, any> }) {
|
|
||||||
const userSub = request.user.sub;
|
|
||||||
const relations = [
|
|
||||||
"current_holders",
|
|
||||||
"current_holders.orgRoot",
|
|
||||||
"current_holders.orgChild1",
|
|
||||||
"current_holders.orgChild2",
|
|
||||||
"current_holders.orgChild3",
|
|
||||||
"current_holders.orgChild4",
|
|
||||||
];
|
|
||||||
|
|
||||||
const [officerProfile, orgRevisionPublish] = await Promise.all([
|
|
||||||
this.profileRepo.findOne({
|
|
||||||
where: { keycloak: userSub },
|
|
||||||
relations,
|
|
||||||
}),
|
|
||||||
this.orgRevisionRepo.findOne({
|
|
||||||
where: {
|
|
||||||
orgRevisionIsDraft: false,
|
|
||||||
orgRevisionIsCurrent: true,
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
]);
|
|
||||||
|
|
||||||
if (!orgRevisionPublish) {
|
|
||||||
throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบแบบร่างโครงสร้าง");
|
|
||||||
}
|
|
||||||
|
|
||||||
let profile: any = officerProfile;
|
|
||||||
let profileType: "OFFICER" | "EMPLOYEE" = "OFFICER";
|
|
||||||
|
|
||||||
if (!profile) {
|
|
||||||
profile = await this.profileEmpRepo.findOne({
|
|
||||||
where: { keycloak: userSub },
|
|
||||||
relations,
|
|
||||||
});
|
|
||||||
profileType = "EMPLOYEE";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!profile) {
|
|
||||||
if (request.user.role.includes("SUPER_ADMIN")) {
|
|
||||||
return new HttpSuccess(null);
|
|
||||||
}
|
|
||||||
throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูลบุคคลนี้ในระบบ");
|
|
||||||
}
|
|
||||||
|
|
||||||
const currentHolder =
|
|
||||||
profile.current_holders?.find((x: any) => x.orgRevisionId == orgRevisionPublish.id) ?? null;
|
|
||||||
const root = currentHolder?.orgRoot ?? null;
|
|
||||||
const child1 = currentHolder?.orgChild1 ?? null;
|
|
||||||
const child2 = currentHolder?.orgChild2 ?? null;
|
|
||||||
const child3 = currentHolder?.orgChild3 ?? null;
|
|
||||||
const child4 = currentHolder?.orgChild4 ?? null;
|
|
||||||
|
|
||||||
const _profile: any = {
|
|
||||||
profileId: profile.id,
|
|
||||||
keycloak: profile.keycloak,
|
|
||||||
prefix: profile.prefix,
|
|
||||||
avatar: profile.avatar,
|
|
||||||
profileType,
|
|
||||||
isProbation: profile.isProbation,
|
|
||||||
avatarName: profile.avatarName,
|
|
||||||
firstName: profile.firstName,
|
|
||||||
lastName: profile.lastName,
|
|
||||||
citizenId: profile.citizenId,
|
|
||||||
root: root?.orgRootName ?? null,
|
|
||||||
child1: child1?.orgChild1Name ?? null,
|
|
||||||
child2: child2?.orgChild2Name ?? null,
|
|
||||||
child3: child3?.orgChild3Name ?? null,
|
|
||||||
child4: child4?.orgChild4Name ?? null,
|
|
||||||
privacyCheckin: profile.privacyCheckin,
|
|
||||||
privacyUser: profile.privacyUser,
|
|
||||||
privacyMgt: profile.privacyMgt,
|
|
||||||
...(profileType !== "OFFICER" ? { type: profile.employeeClass } : {}),
|
|
||||||
};
|
|
||||||
|
|
||||||
return new HttpSuccess(_profile);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1950,78 +1950,35 @@ export class ProfileEmployeeController extends Controller {
|
||||||
// ประวัติพ้นจากราชการ
|
// ประวัติพ้นจากราชการ
|
||||||
let retires = [];
|
let retires = [];
|
||||||
const currentDate = new Date();
|
const currentDate = new Date();
|
||||||
|
// todo: รอข้อสรุป
|
||||||
|
// const retire_raw = await this.salaryRepo.findOne({
|
||||||
|
// where: {
|
||||||
|
// profileEmployeeId: id,
|
||||||
|
// commandCode: In(["12", "15", "16"]),
|
||||||
|
// },
|
||||||
|
// order: { order: "desc" },
|
||||||
|
// });
|
||||||
|
|
||||||
// commandCode ที่ถือว่าออกจากราชการ
|
// if (retire_raw) {
|
||||||
const retireCommandCodes = ["12", "15", "16"];
|
// const startDate = retire_raw.commandDateAffect;
|
||||||
|
|
||||||
// ดึงข้อมูล profileSalary ทั้งหมดเพื่อหาประวัติพ้นจากราชการ
|
// // คำนวณจำนวนวันจากวันพ้นสภาพถึงปัจจุบัน
|
||||||
const salaries = await this.salaryRepo.find({
|
// let daysCount = 0;
|
||||||
where: { profileEmployeeId: id },
|
// if (startDate) {
|
||||||
order: { order: "ASC" },
|
// const start = new Date(startDate);
|
||||||
});
|
// daysCount = Math.ceil((currentDate.getTime() - start.getTime()) / (1000 * 60 * 60 * 24));
|
||||||
|
// }
|
||||||
|
|
||||||
// มีคำสั่งพ้นราชการหรือไม่
|
// const startDateStr = startDate
|
||||||
if (salaries.length > 0 && salaries.some((s) => s.commandCode &&
|
// ? Extension.ToThaiNumber(Extension.ToThaiFullDate2(startDate))
|
||||||
retireCommandCodes.includes(s.commandCode))) {
|
// : "-";
|
||||||
// กรองข้อมูลซ้ำตาม commandDateAffect
|
|
||||||
const uniqueSalaries = salaries.filter((item, index, self) =>
|
|
||||||
index === self.findIndex((t) => t.commandDateAffect?.getTime() === item.commandDateAffect?.getTime())
|
|
||||||
);
|
|
||||||
|
|
||||||
// วนลูปหาคู่ของ "ออกราชการ" และ "กลับเข้าราชการ"
|
// retires.push({
|
||||||
for (let i = 0; i < uniqueSalaries.length; i++) {
|
// date: `${startDateStr} - ปัจจุบัน`,
|
||||||
const current = uniqueSalaries[i];
|
// detail: retire_raw.commandName ?? "-",
|
||||||
|
// day: daysCount > 0 ? Extension.ToThaiNumber(daysCount.toLocaleString()) : "-"
|
||||||
// เป็นคำสั่งออกจากราชการหรือไม่
|
// });
|
||||||
if (current.commandCode && retireCommandCodes.includes(current.commandCode)) {
|
// }
|
||||||
const startDate = current.commandDateAffect;
|
|
||||||
let endDate: Date | null = null;
|
|
||||||
let endRecord = null;
|
|
||||||
|
|
||||||
// หาคำสั่งถัดไปที่ไม่ใช่การออกจากราชการ (ถือว่ากลับเข้าราชการ)
|
|
||||||
for (let j = i + 1; j < uniqueSalaries.length; j++) {
|
|
||||||
const next = uniqueSalaries[j];
|
|
||||||
if (next.commandCode && !retireCommandCodes.includes(next.commandCode)) {
|
|
||||||
endDate = next.commandDateAffect;
|
|
||||||
endRecord = next;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ถ้าไม่เจอคำสั่งกลับเข้า ให้ใช้วันปัจจุบัน
|
|
||||||
if (!endDate) {
|
|
||||||
endDate = currentDate;
|
|
||||||
}
|
|
||||||
|
|
||||||
// คำนวณจำนวนวัน
|
|
||||||
let daysCount = 0;
|
|
||||||
if (startDate && endDate) {
|
|
||||||
const start = new Date(startDate);
|
|
||||||
const end = new Date(endDate);
|
|
||||||
daysCount = Math.ceil((end.getTime() - start.getTime()) / (1000 * 60 * 60 * 24));
|
|
||||||
}
|
|
||||||
|
|
||||||
// สร้าง detail จาก commandName + remark
|
|
||||||
const commandName = current.commandName || "";
|
|
||||||
const remark = current.remark || "";
|
|
||||||
const detail = `${commandName} ${remark}`.trim();
|
|
||||||
|
|
||||||
// แปลงวันที่เป็น format ไทย
|
|
||||||
const startDateStr = startDate
|
|
||||||
? Extension.ToThaiNumber(Extension.ToThaiFullDate2(startDate))
|
|
||||||
: "-";
|
|
||||||
const endDateStr = endDate
|
|
||||||
? Extension.ToThaiNumber(Extension.ToThaiFullDate2(endDate))
|
|
||||||
: "-";
|
|
||||||
|
|
||||||
retires.push({
|
|
||||||
date: `${startDateStr} - ${endDateStr}`,
|
|
||||||
detail: detail || "-",
|
|
||||||
day: daysCount > 0 ? Extension.ToThaiNumber(daysCount.toLocaleString()) : "-"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// กรณีไม่มีข้อมูล
|
// กรณีไม่มีข้อมูล
|
||||||
if (retires.length === 0) {
|
if (retires.length === 0) {
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,8 @@ import HttpError from "../interfaces/http-error";
|
||||||
import { RequestWithUser } from "../middlewares/user";
|
import { RequestWithUser } from "../middlewares/user";
|
||||||
import { Profile } from "../entities/Profile";
|
import { Profile } from "../entities/Profile";
|
||||||
import { ProfileGovernment, UpdateProfileGovernment } from "../entities/ProfileGovernment";
|
import { ProfileGovernment, UpdateProfileGovernment } from "../entities/ProfileGovernment";
|
||||||
|
import { Position } from "../entities/Position";
|
||||||
|
import { PosMaster } from "../entities/PosMaster";
|
||||||
import {
|
import {
|
||||||
calculateAge,
|
calculateAge,
|
||||||
calculateGovAge,
|
calculateGovAge,
|
||||||
|
|
@ -13,6 +15,7 @@ import {
|
||||||
setLogDataDiff,
|
setLogDataDiff,
|
||||||
} from "../interfaces/utils";
|
} from "../interfaces/utils";
|
||||||
import permission from "../interfaces/permission";
|
import permission from "../interfaces/permission";
|
||||||
|
import { OrgRevision } from "../entities/OrgRevision";
|
||||||
import { In } from "typeorm";
|
import { In } from "typeorm";
|
||||||
@Route("api/v1/org/profile/government")
|
@Route("api/v1/org/profile/government")
|
||||||
@Tags("ProfileGovernment")
|
@Tags("ProfileGovernment")
|
||||||
|
|
@ -20,6 +23,9 @@ import { In } from "typeorm";
|
||||||
export class ProfileGovernmentHistoryController extends Controller {
|
export class ProfileGovernmentHistoryController extends Controller {
|
||||||
private profileRepo = AppDataSource.getRepository(Profile);
|
private profileRepo = AppDataSource.getRepository(Profile);
|
||||||
private govRepo = AppDataSource.getRepository(ProfileGovernment);
|
private govRepo = AppDataSource.getRepository(ProfileGovernment);
|
||||||
|
private positionRepo = AppDataSource.getRepository(Position);
|
||||||
|
private posMasterRepo = AppDataSource.getRepository(PosMaster);
|
||||||
|
private orgRevisionRepository = AppDataSource.getRepository(OrgRevision);
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @summary ข้อมูลราชการ
|
* @summary ข้อมูลราชการ
|
||||||
|
|
@ -27,6 +33,13 @@ export class ProfileGovernmentHistoryController extends Controller {
|
||||||
*/
|
*/
|
||||||
@Get("user")
|
@Get("user")
|
||||||
public async getGovHistoryUser(@Request() request: { user: Record<string, any> }) {
|
public async getGovHistoryUser(@Request() request: { user: Record<string, any> }) {
|
||||||
|
const orgRevision = await this.orgRevisionRepository.findOne({
|
||||||
|
select: ["id"],
|
||||||
|
where: {
|
||||||
|
orgRevisionIsDraft: false,
|
||||||
|
orgRevisionIsCurrent: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
const profile = await this.profileRepo.findOneBy({ keycloak: request.user.sub });
|
const profile = await this.profileRepo.findOneBy({ keycloak: request.user.sub });
|
||||||
if (!profile) {
|
if (!profile) {
|
||||||
throw new HttpError(HttpStatus.BAD_REQUEST, "ไม่พบ profile ดังกล่าว");
|
throw new HttpError(HttpStatus.BAD_REQUEST, "ไม่พบ profile ดังกล่าว");
|
||||||
|
|
@ -38,19 +51,79 @@ export class ProfileGovernmentHistoryController extends Controller {
|
||||||
posLevel: true,
|
posLevel: true,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
if (!record) throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูล");
|
const posMaster = await this.posMasterRepo.findOne({
|
||||||
|
where: {
|
||||||
|
// orgRevision: {
|
||||||
|
// orgRevisionIsCurrent: true,
|
||||||
|
// orgRevisionIsDraft: false,
|
||||||
|
// },
|
||||||
|
orgRevisionId: orgRevision?.id,
|
||||||
|
current_holderId: profile.id,
|
||||||
|
},
|
||||||
|
order: { createdAt: "DESC" },
|
||||||
|
relations: {
|
||||||
|
orgRoot: true,
|
||||||
|
orgChild1: true,
|
||||||
|
orgChild2: true,
|
||||||
|
orgChild3: true,
|
||||||
|
orgChild4: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const position = await this.positionRepo.findOne({
|
||||||
|
where: {
|
||||||
|
positionIsSelected: true,
|
||||||
|
posMaster: {
|
||||||
|
// orgRevision: {
|
||||||
|
// orgRevisionIsCurrent: true,
|
||||||
|
// orgRevisionIsDraft: false,
|
||||||
|
// },
|
||||||
|
orgRevisionId: orgRevision?.id,
|
||||||
|
current_holderId: profile.id,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
order: { createdAt: "DESC" },
|
||||||
|
relations: {
|
||||||
|
posExecutive: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
// ดึงข้อมูลจาก profile ที่เก็บไว้แล้ว
|
if (!record) throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูล");
|
||||||
|
const fullNameParts = [
|
||||||
|
posMaster == null || posMaster.orgChild4 == null ? null : posMaster.orgChild4.orgChild4Name,
|
||||||
|
posMaster == null || posMaster.orgChild3 == null ? null : posMaster.orgChild3.orgChild3Name,
|
||||||
|
posMaster == null || posMaster.orgChild2 == null ? null : posMaster.orgChild2.orgChild2Name,
|
||||||
|
posMaster == null || posMaster.orgChild1 == null ? null : posMaster.orgChild1.orgChild1Name,
|
||||||
|
posMaster == null || posMaster.orgRoot == null ? null : posMaster.orgRoot.orgRootName,
|
||||||
|
];
|
||||||
|
const org = fullNameParts.filter((part) => part !== undefined && part !== null).join("\n");
|
||||||
|
let orgShortName = "";
|
||||||
|
if (posMaster != null) {
|
||||||
|
if (posMaster.orgChild1Id === null) {
|
||||||
|
orgShortName = posMaster.orgRoot?.orgRootShortName;
|
||||||
|
} else if (posMaster.orgChild2Id === null) {
|
||||||
|
orgShortName = posMaster.orgChild1?.orgChild1ShortName;
|
||||||
|
} else if (posMaster.orgChild3Id === null) {
|
||||||
|
orgShortName = posMaster.orgChild2?.orgChild2ShortName;
|
||||||
|
} else if (posMaster.orgChild4Id === null) {
|
||||||
|
orgShortName = posMaster.orgChild3?.orgChild3ShortName;
|
||||||
|
} else {
|
||||||
|
orgShortName = posMaster.orgChild4?.orgChild4ShortName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//posMaster?.isSit แก้ไขชั่วคราว
|
||||||
const data = {
|
const data = {
|
||||||
org: record.org ?? null, //สังกัด
|
org: org, //สังกัด
|
||||||
positionField: record.positionField ?? null, //สายงาน
|
positionField: position == null || posMaster?.isSit ? null : position.positionField, //สายงาน
|
||||||
position: record.position, //ตำแหน่ง
|
position: record.position, //ตำแหน่ง
|
||||||
posLevel: record.posLevel == null ? null : record.posLevel.posLevelName, //ระดับ
|
posLevel: record.posLevel == null ? null : record.posLevel.posLevelName, //ระดับ
|
||||||
posMasterNo: record.posMasterNo ?? null, //เลขที่ตำแหน่ง
|
posMasterNo: posMaster == null ? null : `${orgShortName} ${posMaster.posMasterNo}`, //เลขที่ตำแหน่ง
|
||||||
posType: record.posType == null ? null : record.posType.posTypeName, //ประเภท
|
posType: record.posType == null ? null : record.posType.posTypeName, //ประเภท
|
||||||
posExecutive: record.posExecutive ?? null, //ตำแหน่งทางการบริหาร
|
posExecutive:
|
||||||
positionArea: record.positionArea ?? null, //ด้าน/สาขา
|
position == null || position.posExecutive == null || posMaster?.isSit
|
||||||
positionExecutiveField: record.positionExecutiveField ?? null, //ด้านทางการบริหาร
|
? null
|
||||||
|
: position.posExecutive.posExecutiveName, //ตำแหน่งทางการบริหาร
|
||||||
|
positionArea: position == null || posMaster?.isSit ? null : position.positionArea, //ด้าน/สาขา
|
||||||
|
positionExecutiveField: position == null || posMaster?.isSit ? null : position.positionExecutiveField, //ด้านทางการบริหาร
|
||||||
dateLeave: record.birthDate == null ? null : calculateRetireDate(record.birthDate),
|
dateLeave: record.birthDate == null ? null : calculateRetireDate(record.birthDate),
|
||||||
dateRetireLaw: record.dateRetireLaw ?? null,
|
dateRetireLaw: record.dateRetireLaw ?? null,
|
||||||
// govAge: record.dateStart == null ? null : calculateAge(record.dateStart),
|
// govAge: record.dateStart == null ? null : calculateAge(record.dateStart),
|
||||||
|
|
@ -78,6 +151,14 @@ export class ProfileGovernmentHistoryController extends Controller {
|
||||||
if (_workflow == false)
|
if (_workflow == false)
|
||||||
await new permission().PermissionOrgUserGet(req, "SYS_REGISTRY_OFFICER", profileId);
|
await new permission().PermissionOrgUserGet(req, "SYS_REGISTRY_OFFICER", profileId);
|
||||||
|
|
||||||
|
const orgRevision = await this.orgRevisionRepository.findOne({
|
||||||
|
select: ["id"],
|
||||||
|
where: {
|
||||||
|
orgRevisionIsDraft: false,
|
||||||
|
orgRevisionIsCurrent: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
// ค้นหา profile ก่อน
|
// ค้นหา profile ก่อน
|
||||||
const record = await this.profileRepo.findOne({
|
const record = await this.profileRepo.findOne({
|
||||||
where: { id: profileId },
|
where: { id: profileId },
|
||||||
|
|
@ -123,10 +204,67 @@ export class ProfileGovernmentHistoryController extends Controller {
|
||||||
|
|
||||||
// ใช้ profileSalary จาก query ที่สอง หรือ [] ถ้าไม่เจอ
|
// ใช้ profileSalary จาก query ที่สอง หรือ [] ถ้าไม่เจอ
|
||||||
record.profileSalary = profileWithSalary?.profileSalary || [];
|
record.profileSalary = profileWithSalary?.profileSalary || [];
|
||||||
|
const posMaster = await this.posMasterRepo.findOne({
|
||||||
|
where: {
|
||||||
|
orgRevisionId: orgRevision?.id,
|
||||||
|
current_holderId: profileId,
|
||||||
|
},
|
||||||
|
order: { createdAt: "DESC" },
|
||||||
|
relations: {
|
||||||
|
orgRoot: true,
|
||||||
|
orgChild1: true,
|
||||||
|
orgChild2: true,
|
||||||
|
orgChild3: true,
|
||||||
|
orgChild4: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const position = await this.positionRepo.findOne({
|
||||||
|
where: {
|
||||||
|
positionIsSelected: true,
|
||||||
|
posMaster: {
|
||||||
|
orgRevisionId: orgRevision?.id,
|
||||||
|
current_holderId: profileId,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
order: { createdAt: "DESC" },
|
||||||
|
relations: {
|
||||||
|
posExecutive: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// if (!record) throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูล");
|
||||||
|
const fullNameParts = [
|
||||||
|
posMaster == null || posMaster.orgChild4 == null ? null : posMaster.orgChild4.orgChild4Name,
|
||||||
|
posMaster == null || posMaster.orgChild3 == null ? null : posMaster.orgChild3.orgChild3Name,
|
||||||
|
posMaster == null || posMaster.orgChild2 == null ? null : posMaster.orgChild2.orgChild2Name,
|
||||||
|
posMaster == null || posMaster.orgChild1 == null ? null : posMaster.orgChild1.orgChild1Name,
|
||||||
|
posMaster == null || posMaster.orgRoot == null ? null : posMaster.orgRoot.orgRootName,
|
||||||
|
];
|
||||||
|
const org = fullNameParts.filter((part) => part !== undefined && part !== null).join("\n");
|
||||||
|
let orgShortName = "";
|
||||||
|
if (posMaster != null) {
|
||||||
|
if (posMaster.orgChild1Id === null) {
|
||||||
|
orgShortName = posMaster.orgRoot?.orgRootShortName ?? "";
|
||||||
|
} else if (posMaster.orgChild2Id === null) {
|
||||||
|
orgShortName = posMaster.orgChild1?.orgChild1ShortName ?? "";
|
||||||
|
} else if (posMaster.orgChild3Id === null) {
|
||||||
|
orgShortName = posMaster.orgChild2?.orgChild2ShortName ?? "";
|
||||||
|
} else if (posMaster.orgChild4Id === null) {
|
||||||
|
orgShortName = posMaster.orgChild3?.orgChild3ShortName ?? "";
|
||||||
|
} else {
|
||||||
|
orgShortName = posMaster.orgChild4?.orgChild4ShortName ?? "";
|
||||||
|
}
|
||||||
|
}
|
||||||
let _OrgLeave: any = [];
|
let _OrgLeave: any = [];
|
||||||
let _profileSalary: any = null;
|
let _profileSalary: any = null;
|
||||||
if (record?.isLeave && record?.profileSalary.length > 0) {
|
if (record?.isLeave && record?.profileSalary.length > 0) {
|
||||||
|
// _OrgLeave = [
|
||||||
|
// record?.profileSalary[0].orgChild4 ? record?.profileSalary[0].orgChild4 : null,
|
||||||
|
// record?.profileSalary[0].orgChild3 ? record?.profileSalary[0].orgChild3 : null,
|
||||||
|
// record?.profileSalary[0].orgChild2 ? record?.profileSalary[0].orgChild2 : null,
|
||||||
|
// record?.profileSalary[0].orgChild1 ? record?.profileSalary[0].orgChild1 : null,
|
||||||
|
// record?.profileSalary[0].orgRoot ? record?.profileSalary[0].orgRoot : null,
|
||||||
|
// ];
|
||||||
if (record.leaveType == "RETIRE") {
|
if (record.leaveType == "RETIRE") {
|
||||||
_profileSalary =
|
_profileSalary =
|
||||||
record?.profileSalary.length > 1
|
record?.profileSalary.length > 1
|
||||||
|
|
@ -150,23 +288,27 @@ export class ProfileGovernmentHistoryController extends Controller {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const orgLeave = _OrgLeave.filter((x: any) => x !== undefined && x !== null).join("\n");
|
const orgLeave = _OrgLeave.filter((x: any) => x !== undefined && x !== null).join("\n");
|
||||||
|
//posMaster?.isSit แก้ไขชั่วคราว
|
||||||
// ดึงข้อมูลจาก profile ที่เก็บไว้แล้ว
|
|
||||||
const data = {
|
const data = {
|
||||||
org: record?.isLeave == false ? (record.org ?? null) : orgLeave, //สังกัด
|
org: record?.isLeave == false ? org : orgLeave, //สังกัด
|
||||||
positionField: record.positionField ?? null, //สายงาน
|
positionField: position == null || posMaster?.isSit ? null : position.positionField, //สายงาน
|
||||||
position: record?.position, //ตำแหน่ง
|
position: record?.position, //ตำแหน่ง
|
||||||
posLevel: record?.posLevel == null ? null : record?.posLevel.posLevelName, //ระดับ
|
posLevel: record?.posLevel == null ? null : record?.posLevel.posLevelName, //ระดับ
|
||||||
posMasterNo:
|
posMasterNo:
|
||||||
record?.isLeave == false
|
record?.isLeave == false
|
||||||
? record.posMasterNo ?? null
|
? posMaster == null
|
||||||
|
? null
|
||||||
|
: `${orgShortName} ${posMaster.posMasterNo}`
|
||||||
: _profileSalary != null
|
: _profileSalary != null
|
||||||
? `${_profileSalary.posNoAbb} ${_profileSalary.posNo}`
|
? `${_profileSalary.posNoAbb} ${_profileSalary.posNo}`
|
||||||
: null, //เลขที่ตำแหน่ง
|
: null, //เลขที่ตำแหน่ง
|
||||||
posType: record?.posType == null ? null : record?.posType.posTypeName, //ประเภท
|
posType: record?.posType == null ? null : record?.posType.posTypeName, //ประเภท
|
||||||
posExecutive: record.posExecutive ?? null, //ตำแหน่งทางการบริหาร
|
posExecutive:
|
||||||
positionArea: record.positionArea ?? null, //ด้าน/สาขา
|
position == null || position.posExecutive == null || posMaster?.isSit
|
||||||
positionExecutiveField: record.positionExecutiveField ?? null, //ด้านทางการบริหาร
|
? null
|
||||||
|
: position.posExecutive.posExecutiveName, //ตำแหน่งทางการบริหาร
|
||||||
|
positionArea: position == null || posMaster?.isSit ? null : position.positionArea, //ด้าน/สาขา
|
||||||
|
positionExecutiveField: position == null || posMaster?.isSit ? null : position.positionExecutiveField, //ด้านทางการบริหาร
|
||||||
dateLeave: record?.birthDate == null ? null : calculateRetireDate(record?.birthDate),
|
dateLeave: record?.birthDate == null ? null : calculateRetireDate(record?.birthDate),
|
||||||
dateRetireLaw: record?.dateRetireLaw ?? null,
|
dateRetireLaw: record?.dateRetireLaw ?? null,
|
||||||
// govAge: record?.dateStart == null ? null : calculateAge(record?.dateStart),
|
// govAge: record?.dateStart == null ? null : calculateAge(record?.dateStart),
|
||||||
|
|
@ -184,6 +326,14 @@ export class ProfileGovernmentHistoryController extends Controller {
|
||||||
|
|
||||||
@Get("admin/{profileId}")
|
@Get("admin/{profileId}")
|
||||||
public async getGovHistoryAdmin(@Path() profileId: string) {
|
public async getGovHistoryAdmin(@Path() profileId: string) {
|
||||||
|
const orgRevision = await this.orgRevisionRepository.findOne({
|
||||||
|
select: ["id"],
|
||||||
|
where: {
|
||||||
|
orgRevisionIsDraft: false,
|
||||||
|
orgRevisionIsCurrent: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
// ค้นหา profile ก่อน
|
// ค้นหา profile ก่อน
|
||||||
const record = await this.profileRepo.findOne({
|
const record = await this.profileRepo.findOne({
|
||||||
where: { id: profileId },
|
where: { id: profileId },
|
||||||
|
|
@ -229,10 +379,67 @@ export class ProfileGovernmentHistoryController extends Controller {
|
||||||
|
|
||||||
// ใช้ profileSalary จาก query ที่สอง หรือ [] ถ้าไม่เจอ
|
// ใช้ profileSalary จาก query ที่สอง หรือ [] ถ้าไม่เจอ
|
||||||
record.profileSalary = profileWithSalary?.profileSalary || [];
|
record.profileSalary = profileWithSalary?.profileSalary || [];
|
||||||
|
const posMaster = await this.posMasterRepo.findOne({
|
||||||
|
where: {
|
||||||
|
orgRevisionId: orgRevision?.id,
|
||||||
|
current_holderId: profileId,
|
||||||
|
},
|
||||||
|
order: { createdAt: "DESC" },
|
||||||
|
relations: {
|
||||||
|
orgRoot: true,
|
||||||
|
orgChild1: true,
|
||||||
|
orgChild2: true,
|
||||||
|
orgChild3: true,
|
||||||
|
orgChild4: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const position = await this.positionRepo.findOne({
|
||||||
|
where: {
|
||||||
|
positionIsSelected: true,
|
||||||
|
posMaster: {
|
||||||
|
orgRevisionId: orgRevision?.id,
|
||||||
|
current_holderId: profileId,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
order: { createdAt: "DESC" },
|
||||||
|
relations: {
|
||||||
|
posExecutive: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// if (!record) throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูล");
|
||||||
|
const fullNameParts = [
|
||||||
|
posMaster == null || posMaster.orgChild4 == null ? null : posMaster.orgChild4.orgChild4Name,
|
||||||
|
posMaster == null || posMaster.orgChild3 == null ? null : posMaster.orgChild3.orgChild3Name,
|
||||||
|
posMaster == null || posMaster.orgChild2 == null ? null : posMaster.orgChild2.orgChild2Name,
|
||||||
|
posMaster == null || posMaster.orgChild1 == null ? null : posMaster.orgChild1.orgChild1Name,
|
||||||
|
posMaster == null || posMaster.orgRoot == null ? null : posMaster.orgRoot.orgRootName,
|
||||||
|
];
|
||||||
|
const org = fullNameParts.filter((part) => part !== undefined && part !== null).join("\n");
|
||||||
|
let orgShortName = "";
|
||||||
|
if (posMaster != null) {
|
||||||
|
if (posMaster.orgChild1Id === null) {
|
||||||
|
orgShortName = posMaster.orgRoot?.orgRootShortName;
|
||||||
|
} else if (posMaster.orgChild2Id === null) {
|
||||||
|
orgShortName = posMaster.orgChild1?.orgChild1ShortName;
|
||||||
|
} else if (posMaster.orgChild3Id === null) {
|
||||||
|
orgShortName = posMaster.orgChild2?.orgChild2ShortName;
|
||||||
|
} else if (posMaster.orgChild4Id === null) {
|
||||||
|
orgShortName = posMaster.orgChild3?.orgChild3ShortName;
|
||||||
|
} else {
|
||||||
|
orgShortName = posMaster.orgChild4?.orgChild4ShortName;
|
||||||
|
}
|
||||||
|
}
|
||||||
let _OrgLeave: any = [];
|
let _OrgLeave: any = [];
|
||||||
let _profileSalary: any = null;
|
let _profileSalary: any = null;
|
||||||
if (record?.isLeave && record?.profileSalary.length > 0) {
|
if (record?.isLeave && record?.profileSalary.length > 0) {
|
||||||
|
// _OrgLeave = [
|
||||||
|
// record?.profileSalary[0].orgChild4 ? record?.profileSalary[0].orgChild4 : null,
|
||||||
|
// record?.profileSalary[0].orgChild3 ? record?.profileSalary[0].orgChild3 : null,
|
||||||
|
// record?.profileSalary[0].orgChild2 ? record?.profileSalary[0].orgChild2 : null,
|
||||||
|
// record?.profileSalary[0].orgChild1 ? record?.profileSalary[0].orgChild1 : null,
|
||||||
|
// record?.profileSalary[0].orgRoot ? record?.profileSalary[0].orgRoot : null,
|
||||||
|
// ];
|
||||||
if (record.leaveType == "RETIRE") {
|
if (record.leaveType == "RETIRE") {
|
||||||
_profileSalary =
|
_profileSalary =
|
||||||
record?.profileSalary.length > 1
|
record?.profileSalary.length > 1
|
||||||
|
|
@ -256,23 +463,27 @@ export class ProfileGovernmentHistoryController extends Controller {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const orgLeave = _OrgLeave.filter((x: any) => x !== undefined && x !== null).join("\n");
|
const orgLeave = _OrgLeave.filter((x: any) => x !== undefined && x !== null).join("\n");
|
||||||
|
//posMaster?.isSit แก้ไขชั่วคราว
|
||||||
// ดึงข้อมูลจาก profile ที่เก็บไว้แล้ว
|
|
||||||
const data = {
|
const data = {
|
||||||
org: record?.isLeave == false ? (record.org ?? null) : orgLeave, //สังกัด
|
org: record?.isLeave == false ? org : orgLeave, //สังกัด
|
||||||
positionField: record.positionField ?? null, //สายงาน
|
positionField: position == null || posMaster?.isSit ? null : position.positionField, //สายงาน
|
||||||
position: record?.position, //ตำแหน่ง
|
position: record?.position, //ตำแหน่ง
|
||||||
posLevel: record?.posLevel == null ? null : record?.posLevel.posLevelName, //ระดับ
|
posLevel: record?.posLevel == null ? null : record?.posLevel.posLevelName, //ระดับ
|
||||||
posMasterNo:
|
posMasterNo:
|
||||||
record?.isLeave == false
|
record?.isLeave == false
|
||||||
? record.posMasterNo ?? null
|
? posMaster == null
|
||||||
|
? null
|
||||||
|
: `${orgShortName} ${posMaster.posMasterNo}`
|
||||||
: _profileSalary != null
|
: _profileSalary != null
|
||||||
? `${_profileSalary.posNoAbb} ${_profileSalary.posNo}`
|
? `${_profileSalary.posNoAbb} ${_profileSalary.posNo}`
|
||||||
: null, //เลขที่ตำแหน่ง
|
: null, //เลขที่ตำแหน่ง
|
||||||
posType: record?.posType == null ? null : record?.posType.posTypeName, //ประเภท
|
posType: record?.posType == null ? null : record?.posType.posTypeName, //ประเภท
|
||||||
posExecutive: record.posExecutive ?? null, //ตำแหน่งทางการบริหาร
|
posExecutive:
|
||||||
positionArea: record.positionArea ?? null, //ด้าน/สาขา
|
position == null || position.posExecutive == null || posMaster?.isSit
|
||||||
positionExecutiveField: record.positionExecutiveField ?? null, //ด้านทางการบริหาร
|
? null
|
||||||
|
: position.posExecutive.posExecutiveName, //ตำแหน่งทางการบริหาร
|
||||||
|
positionArea: position == null || posMaster?.isSit ? null : position.positionArea, //ด้าน/สาขา
|
||||||
|
positionExecutiveField: position == null || posMaster?.isSit ? null : position.positionExecutiveField, //ด้านทางการบริหาร
|
||||||
dateLeave: record?.birthDate == null ? null : calculateRetireDate(record?.birthDate),
|
dateLeave: record?.birthDate == null ? null : calculateRetireDate(record?.birthDate),
|
||||||
dateRetireLaw: record?.dateRetireLaw ?? null,
|
dateRetireLaw: record?.dateRetireLaw ?? null,
|
||||||
// govAge: record?.dateStart == null ? null : calculateAge(record?.dateStart),
|
// govAge: record?.dateStart == null ? null : calculateAge(record?.dateStart),
|
||||||
|
|
@ -371,4 +582,3 @@ export class ProfileGovernmentHistoryController extends Controller {
|
||||||
return new HttpSuccess();
|
return new HttpSuccess();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -23,7 +23,6 @@ import { ProfileEmployee } from "../entities/ProfileEmployee";
|
||||||
import { In, IsNull, LessThan, MoreThan, Not } from "typeorm";
|
import { In, IsNull, LessThan, MoreThan, Not } from "typeorm";
|
||||||
import permission from "../interfaces/permission";
|
import permission from "../interfaces/permission";
|
||||||
import { setLogDataDiff } from "../interfaces/utils";
|
import { setLogDataDiff } from "../interfaces/utils";
|
||||||
import { calculateTenure } from "../utils/tenure";
|
|
||||||
import { TenurePositionOfficer } from "../entities/TenurePositionOfficer";
|
import { TenurePositionOfficer } from "../entities/TenurePositionOfficer";
|
||||||
import { TenureLevelOfficer } from "../entities/TenureLevelOfficer";
|
import { TenureLevelOfficer } from "../entities/TenureLevelOfficer";
|
||||||
import { TenurePositionEmployee } from "../entities/TenurePositionEmployee";
|
import { TenurePositionEmployee } from "../entities/TenurePositionEmployee";
|
||||||
|
|
@ -66,12 +65,10 @@ export class ProfileSalaryController extends Controller {
|
||||||
await this.positionOfficerRepo.clear();
|
await this.positionOfficerRepo.clear();
|
||||||
const profile = await this.profileRepo.find();
|
const profile = await this.profileRepo.find();
|
||||||
const CURRENT_DATE = await AppDataSource.query("SELECT CURRENT_DATE() as today");
|
const CURRENT_DATE = await AppDataSource.query("SELECT CURRENT_DATE() as today");
|
||||||
const baseCurrentDate = CURRENT_DATE[0].today;
|
let _currentDate = CURRENT_DATE[0].today;
|
||||||
for await (const x of profile) {
|
for await (const x of profile) {
|
||||||
// Use leave date if available and valid, otherwise use current date
|
if (x.isLeave) {
|
||||||
let _currentDate = baseCurrentDate;
|
_currentDate = x.leaveDate ? Extension.toDateOnlyString(x.leaveDate) : _currentDate;
|
||||||
if (x.isLeave && x.leaveDate) {
|
|
||||||
_currentDate = Extension.toDateOnlyString(x.leaveDate);
|
|
||||||
}
|
}
|
||||||
const position = await AppDataSource.query("CALL GetProfileSalaryPosition(?, ?)", [
|
const position = await AppDataSource.query("CALL GetProfileSalaryPosition(?, ?)", [
|
||||||
x.id,
|
x.id,
|
||||||
|
|
@ -95,18 +92,21 @@ export class ProfileSalaryController extends Controller {
|
||||||
},
|
},
|
||||||
{ days_diff: 0, positionName: null },
|
{ days_diff: 0, positionName: null },
|
||||||
);
|
);
|
||||||
const { year, month, day } = calculateTenure(calDayDiff.days_diff);
|
|
||||||
const mapData: any = {
|
const mapData: any = {
|
||||||
profileId: x.id,
|
profileId: x.id,
|
||||||
positionName: calDayDiff.positionName,
|
positionName: calDayDiff.positionName,
|
||||||
days_diff: calDayDiff.days_diff,
|
days_diff: calDayDiff.days_diff,
|
||||||
Years: year,
|
// Years: (calDayDiff.days_diff / 365.2524).toFixed(4),
|
||||||
Months: month,
|
// Months: ((calDayDiff.days_diff / 30.4375) % 12).toFixed(4),
|
||||||
Days: day,
|
// Days: (calDayDiff.days_diff % 30.4375).toFixed(4),
|
||||||
|
Years: Math.floor(calDayDiff.days_diff / 365.2524),
|
||||||
|
Months: Math.floor((calDayDiff.days_diff / 30.4375) % 12),
|
||||||
|
Days: Math.floor(calDayDiff.days_diff % 30.4375),
|
||||||
};
|
};
|
||||||
data.push(mapData);
|
// data.push(_mapData);
|
||||||
|
await this.positionOfficerRepo.save(mapData);
|
||||||
}
|
}
|
||||||
await this.positionOfficerRepo.save(data);
|
// await this.positionOfficerRepo.save(data);
|
||||||
|
|
||||||
return new HttpSuccess();
|
return new HttpSuccess();
|
||||||
}
|
}
|
||||||
|
|
@ -115,13 +115,11 @@ export class ProfileSalaryController extends Controller {
|
||||||
let data: any = [];
|
let data: any = [];
|
||||||
await this.positionEmployeeRepo.clear();
|
await this.positionEmployeeRepo.clear();
|
||||||
const CURRENT_DATE = await AppDataSource.query("SELECT CURRENT_DATE() as today");
|
const CURRENT_DATE = await AppDataSource.query("SELECT CURRENT_DATE() as today");
|
||||||
const baseCurrentDate = CURRENT_DATE[0].today;
|
let _currentDate = CURRENT_DATE[0].today;
|
||||||
const profile = await this.profileEmployeeRepo.find();
|
const profile = await this.profileEmployeeRepo.find();
|
||||||
for await (const x of profile) {
|
for await (const x of profile) {
|
||||||
// Use leave date if available and valid, otherwise use current date
|
if (x?.isLeave) {
|
||||||
let _currentDate = baseCurrentDate;
|
_currentDate = x.leaveDate ? Extension.toDateOnlyString(x.leaveDate) : _currentDate;
|
||||||
if (x?.isLeave && x.leaveDate) {
|
|
||||||
_currentDate = Extension.toDateOnlyString(x.leaveDate);
|
|
||||||
}
|
}
|
||||||
const position = await AppDataSource.query("CALL GetProfileEmployeeSalaryPosition(?, ?)", [
|
const position = await AppDataSource.query("CALL GetProfileEmployeeSalaryPosition(?, ?)", [
|
||||||
x.id,
|
x.id,
|
||||||
|
|
@ -145,18 +143,21 @@ export class ProfileSalaryController extends Controller {
|
||||||
},
|
},
|
||||||
{ days_diff: 0, positionName: null },
|
{ days_diff: 0, positionName: null },
|
||||||
);
|
);
|
||||||
const { year, month, day } = calculateTenure(calDayDiff.days_diff);
|
|
||||||
const mapData: any = {
|
const mapData: any = {
|
||||||
profileEmployeeId: x.id,
|
profileEmployeeId: x.id,
|
||||||
positionName: calDayDiff.positionName,
|
positionName: calDayDiff.positionName,
|
||||||
days_diff: calDayDiff.days_diff,
|
days_diff: calDayDiff.days_diff,
|
||||||
Years: year,
|
// Years: (calDayDiff.days_diff / 365.2524).toFixed(4),
|
||||||
Months: month,
|
// Months: ((calDayDiff.days_diff / 30.4375) % 12).toFixed(4),
|
||||||
Days: day,
|
// Days: (calDayDiff.days_diff % 30.4375).toFixed(4),
|
||||||
|
Years: Math.floor(calDayDiff.days_diff / 365.2524),
|
||||||
|
Months: Math.floor((calDayDiff.days_diff / 30.4375) % 12),
|
||||||
|
Days: Math.floor(calDayDiff.days_diff % 30.4375),
|
||||||
};
|
};
|
||||||
data.push(mapData);
|
// data.push(_mapData);
|
||||||
|
await this.positionEmployeeRepo.save(mapData);
|
||||||
}
|
}
|
||||||
await this.positionEmployeeRepo.save(data);
|
// await this.positionEmployeeRepo.save(data);
|
||||||
|
|
||||||
return new HttpSuccess();
|
return new HttpSuccess();
|
||||||
}
|
}
|
||||||
|
|
@ -166,12 +167,10 @@ export class ProfileSalaryController extends Controller {
|
||||||
await this.levelOfficerRepo.clear();
|
await this.levelOfficerRepo.clear();
|
||||||
const profile = await this.profileRepo.find({ relations: ["posLevel", "posType"] });
|
const profile = await this.profileRepo.find({ relations: ["posLevel", "posType"] });
|
||||||
const CURRENT_DATE = await AppDataSource.query("SELECT CURRENT_DATE() as today");
|
const CURRENT_DATE = await AppDataSource.query("SELECT CURRENT_DATE() as today");
|
||||||
const baseCurrentDate = CURRENT_DATE[0].today;
|
let _currentDate = CURRENT_DATE[0].today;
|
||||||
for await (const x of profile) {
|
for await (const x of profile) {
|
||||||
// Use leave date if available and valid, otherwise use current date
|
if (x?.isLeave) {
|
||||||
let _currentDate = baseCurrentDate;
|
_currentDate = x.leaveDate ? Extension.toDateOnlyString(x.leaveDate) : _currentDate;
|
||||||
if (x?.isLeave && x.leaveDate) {
|
|
||||||
_currentDate = Extension.toDateOnlyString(x.leaveDate);
|
|
||||||
}
|
}
|
||||||
const positionLevel = await AppDataSource.query("CALL GetProfileSalaryLevel(?, ?)", [
|
const positionLevel = await AppDataSource.query("CALL GetProfileSalaryLevel(?, ?)", [
|
||||||
x.id,
|
x.id,
|
||||||
|
|
@ -203,20 +202,20 @@ export class ProfileSalaryController extends Controller {
|
||||||
},
|
},
|
||||||
{ days_diff: 0, positionType: null, positionLevel: null, positionCee: null },
|
{ days_diff: 0, positionType: null, positionLevel: null, positionCee: null },
|
||||||
);
|
);
|
||||||
const { year, month, day } = calculateTenure(calDayDiff.days_diff);
|
|
||||||
const mapData: any = {
|
const mapData: any = {
|
||||||
profileId: x.id,
|
profileId: x.id,
|
||||||
positionType: calDayDiff.positionType,
|
positionType: calDayDiff.positionType,
|
||||||
positionLevel: calDayDiff.positionLevel,
|
positionLevel: calDayDiff.positionLevel,
|
||||||
positionCee: calDayDiff.positionCee,
|
positionCee: calDayDiff.positionCee,
|
||||||
days_diff: calDayDiff.days_diff,
|
days_diff: calDayDiff.days_diff,
|
||||||
Years: x.posLevel == null ? 0 : year.toFixed(4),
|
Years: x.posLevel == null ? 0 : (calDayDiff.days_diff / 365.2524).toFixed(4),
|
||||||
Months: x.posLevel == null ? 0 : month.toFixed(4),
|
Months: x.posLevel == null ? 0 : ((calDayDiff.days_diff / 30.4375) % 12).toFixed(4),
|
||||||
Days: x.posLevel == null ? 0 : day.toFixed(4),
|
Days: x.posLevel == null ? 0 : (calDayDiff.days_diff % 30.4375).toFixed(4),
|
||||||
};
|
};
|
||||||
data.push(mapData);
|
// data.push(_mapData);
|
||||||
|
await this.levelOfficerRepo.save(mapData);
|
||||||
}
|
}
|
||||||
await this.levelOfficerRepo.save(data);
|
// await this.levelOfficerRepo.save(data);
|
||||||
|
|
||||||
return new HttpSuccess();
|
return new HttpSuccess();
|
||||||
}
|
}
|
||||||
|
|
@ -226,12 +225,10 @@ export class ProfileSalaryController extends Controller {
|
||||||
await this.levelEmployeeRepo.clear();
|
await this.levelEmployeeRepo.clear();
|
||||||
const profile = await this.profileEmployeeRepo.find({ relations: ["posLevel", "posType"] });
|
const profile = await this.profileEmployeeRepo.find({ relations: ["posLevel", "posType"] });
|
||||||
const CURRENT_DATE = await AppDataSource.query("SELECT CURRENT_DATE() as today");
|
const CURRENT_DATE = await AppDataSource.query("SELECT CURRENT_DATE() as today");
|
||||||
const baseCurrentDate = CURRENT_DATE[0].today;
|
let _currentDate = CURRENT_DATE[0].today;
|
||||||
for await (const x of profile) {
|
for await (const x of profile) {
|
||||||
// Use leave date if available and valid, otherwise use current date
|
if (x?.isLeave) {
|
||||||
let _currentDate = baseCurrentDate;
|
_currentDate = x.leaveDate ? Extension.toDateOnlyString(x.leaveDate) : _currentDate;
|
||||||
if (x?.isLeave && x.leaveDate) {
|
|
||||||
_currentDate = Extension.toDateOnlyString(x.leaveDate);
|
|
||||||
}
|
}
|
||||||
const positionLevel = await AppDataSource.query("CALL GetProfileEmployeeSalaryLevel(?, ?)", [
|
const positionLevel = await AppDataSource.query("CALL GetProfileEmployeeSalaryLevel(?, ?)", [
|
||||||
x.id,
|
x.id,
|
||||||
|
|
@ -263,27 +260,26 @@ export class ProfileSalaryController extends Controller {
|
||||||
},
|
},
|
||||||
{ days_diff: 0, positionType: null, positionLevel: null, positionCee: null },
|
{ days_diff: 0, positionType: null, positionLevel: null, positionCee: null },
|
||||||
);
|
);
|
||||||
const { year, month, day } = calculateTenure(calDayDiff.days_diff);
|
|
||||||
const mapData: any = {
|
const mapData: any = {
|
||||||
profileEmployeeId: x.id,
|
profileEmployeeId: x.id,
|
||||||
positionType: calDayDiff.positionType,
|
positionType: calDayDiff.positionType,
|
||||||
positionLevel: calDayDiff.positionLevel,
|
positionLevel: calDayDiff.positionLevel,
|
||||||
positionCee: calDayDiff.positionCee,
|
positionCee: calDayDiff.positionCee,
|
||||||
days_diff: calDayDiff.days_diff,
|
days_diff: calDayDiff.days_diff,
|
||||||
Years: x.posLevel == null ? 0 : year.toFixed(4),
|
Years: x.posLevel == null ? 0 : (calDayDiff.days_diff / 365.2524).toFixed(4),
|
||||||
Months: x.posLevel == null ? 0 : month.toFixed(4),
|
Months: x.posLevel == null ? 0 : ((calDayDiff.days_diff / 30.4375) % 12).toFixed(4),
|
||||||
Days: x.posLevel == null ? 0 : day.toFixed(4),
|
Days: x.posLevel == null ? 0 : (calDayDiff.days_diff % 30.4375).toFixed(4),
|
||||||
};
|
};
|
||||||
data.push(mapData);
|
// data.push(_mapData);
|
||||||
|
await this.levelEmployeeRepo.save(mapData);
|
||||||
}
|
}
|
||||||
await this.levelEmployeeRepo.save(data);
|
// await this.levelEmployeeRepo.save(data);
|
||||||
|
|
||||||
return new HttpSuccess();
|
return new HttpSuccess();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get("TenurePositionExecutiveOfficer")
|
@Get("TenurePositionExecutiveOfficer")
|
||||||
public async cronjobTenureExecutivePositionOfficer() {
|
public async cronjobTenureExecutivePositionOfficer() {
|
||||||
let data: any = [];
|
|
||||||
await this.positionExecutiveOfficerRepo.clear();
|
await this.positionExecutiveOfficerRepo.clear();
|
||||||
const profile = await this.profileRepo.find();
|
const profile = await this.profileRepo.find();
|
||||||
const orgRevision = await this.orgRevisionRepository.findOne({
|
const orgRevision = await this.orgRevisionRepository.findOne({
|
||||||
|
|
@ -294,12 +290,10 @@ export class ProfileSalaryController extends Controller {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
const CURRENT_DATE = await AppDataSource.query("SELECT CURRENT_DATE() as today");
|
const CURRENT_DATE = await AppDataSource.query("SELECT CURRENT_DATE() as today");
|
||||||
const baseCurrentDate = CURRENT_DATE[0].today;
|
let _currentDate = CURRENT_DATE[0].today;
|
||||||
for await (const x of profile) {
|
for await (const x of profile) {
|
||||||
// Use leave date if available and valid, otherwise use current date
|
if (x?.isLeave) {
|
||||||
let _currentDate = baseCurrentDate;
|
_currentDate = x.leaveDate ? Extension.toDateOnlyString(x.leaveDate) : _currentDate;
|
||||||
if (x?.isLeave && x.leaveDate) {
|
|
||||||
_currentDate = Extension.toDateOnlyString(x.leaveDate);
|
|
||||||
}
|
}
|
||||||
const position = await this.positionRepo.findOne({
|
const position = await this.positionRepo.findOne({
|
||||||
where: {
|
where: {
|
||||||
|
|
@ -337,18 +331,16 @@ export class ProfileSalaryController extends Controller {
|
||||||
},
|
},
|
||||||
{ days_diff: 0, positionExecutive: null },
|
{ days_diff: 0, positionExecutive: null },
|
||||||
);
|
);
|
||||||
const { year, month, day } = calculateTenure(calDayDiff.days_diff);
|
|
||||||
const mapData: any = {
|
const mapData: any = {
|
||||||
profileId: x.id,
|
profileId: x.id,
|
||||||
positionExecutiveName: calDayDiff.positionExecutive,
|
positionExecutiveName: calDayDiff.positionExecutive,
|
||||||
days_diff: calDayDiff.days_diff,
|
days_diff: calDayDiff.days_diff,
|
||||||
Years: year.toFixed(4),
|
Years: (calDayDiff.days_diff / 365.2524).toFixed(4),
|
||||||
Months: month.toFixed(4),
|
Months: ((calDayDiff.days_diff / 30.4375) % 12).toFixed(4),
|
||||||
Days: day.toFixed(4),
|
Days: (calDayDiff.days_diff % 30.4375).toFixed(4),
|
||||||
};
|
};
|
||||||
data.push(mapData);
|
await this.positionExecutiveOfficerRepo.save(mapData);
|
||||||
}
|
}
|
||||||
await this.positionExecutiveOfficerRepo.save(data);
|
|
||||||
return new HttpSuccess();
|
return new HttpSuccess();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -610,10 +602,10 @@ export class ProfileSalaryController extends Controller {
|
||||||
acc.push(existing);
|
acc.push(existing);
|
||||||
}
|
}
|
||||||
|
|
||||||
const { year, month, day } = calculateTenure(existing.days);
|
// Recalculate year, month, and day
|
||||||
existing.year = year;
|
existing.year = Math.floor(existing.days / 365.2524);
|
||||||
existing.month = month;
|
existing.month = Math.floor((existing.days / 30.4375) % 12);
|
||||||
existing.day = day;
|
existing.day = Math.ceil(existing.days % 30.4375);
|
||||||
|
|
||||||
return acc;
|
return acc;
|
||||||
},
|
},
|
||||||
|
|
@ -649,10 +641,10 @@ export class ProfileSalaryController extends Controller {
|
||||||
acc.push(existing);
|
acc.push(existing);
|
||||||
}
|
}
|
||||||
|
|
||||||
const { year, month, day } = calculateTenure(existing.days);
|
// Recalculate year, month, and day
|
||||||
existing.year = year;
|
existing.year = Math.floor(existing.days / 365.2524);
|
||||||
existing.month = month;
|
existing.month = Math.floor((existing.days / 30.4375) % 12);
|
||||||
existing.day = day;
|
existing.day = Math.ceil(existing.days % 30.4375);
|
||||||
|
|
||||||
return acc;
|
return acc;
|
||||||
},
|
},
|
||||||
|
|
@ -683,10 +675,10 @@ export class ProfileSalaryController extends Controller {
|
||||||
acc.push(existing);
|
acc.push(existing);
|
||||||
}
|
}
|
||||||
|
|
||||||
const { year, month, day } = calculateTenure(existing.days);
|
// Recalculate year, month, and day
|
||||||
existing.year = year;
|
existing.year = Math.floor(existing.days / 365.2524);
|
||||||
existing.month = month;
|
existing.month = Math.floor((existing.days / 30.4375) % 12);
|
||||||
existing.day = day;
|
existing.day = Math.ceil(existing.days % 30.4375);
|
||||||
|
|
||||||
return acc;
|
return acc;
|
||||||
},
|
},
|
||||||
|
|
@ -747,10 +739,10 @@ export class ProfileSalaryController extends Controller {
|
||||||
acc.push(existing);
|
acc.push(existing);
|
||||||
}
|
}
|
||||||
|
|
||||||
const { year, month, day } = calculateTenure(existing.days);
|
// Recalculate year, month, and day
|
||||||
existing.year = year;
|
existing.year = Math.floor(existing.days / 365.2524);
|
||||||
existing.month = month;
|
existing.month = Math.floor((existing.days / 30.4375) % 12);
|
||||||
existing.day = day;
|
existing.day = Math.ceil(existing.days % 30.4375);
|
||||||
|
|
||||||
return acc;
|
return acc;
|
||||||
},
|
},
|
||||||
|
|
@ -790,10 +782,10 @@ export class ProfileSalaryController extends Controller {
|
||||||
acc.push(existing);
|
acc.push(existing);
|
||||||
}
|
}
|
||||||
|
|
||||||
const { year, month, day } = calculateTenure(existing.days);
|
// Recalculate year, month, and day
|
||||||
existing.year = year;
|
existing.year = Math.floor(existing.days / 365.2524);
|
||||||
existing.month = month;
|
existing.month = Math.floor((existing.days / 30.4375) % 12);
|
||||||
existing.day = day;
|
existing.day = Math.ceil(existing.days % 30.4375);
|
||||||
|
|
||||||
return acc;
|
return acc;
|
||||||
},
|
},
|
||||||
|
|
@ -827,10 +819,10 @@ export class ProfileSalaryController extends Controller {
|
||||||
acc.push(existing);
|
acc.push(existing);
|
||||||
}
|
}
|
||||||
|
|
||||||
const { year, month, day } = calculateTenure(existing.days);
|
// Recalculate year, month, and day
|
||||||
existing.year = year;
|
existing.year = Math.floor(existing.days / 365.2524);
|
||||||
existing.month = month;
|
existing.month = Math.floor((existing.days / 30.4375) % 12);
|
||||||
existing.day = day;
|
existing.day = Math.ceil(existing.days % 30.4375);
|
||||||
|
|
||||||
return acc;
|
return acc;
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,6 @@ import { Profile } from "../entities/Profile";
|
||||||
import { In, LessThan, IsNull, MoreThan } from "typeorm";
|
import { In, LessThan, IsNull, MoreThan } from "typeorm";
|
||||||
import permission from "../interfaces/permission";
|
import permission from "../interfaces/permission";
|
||||||
import { setLogDataDiff } from "../interfaces/utils";
|
import { setLogDataDiff } from "../interfaces/utils";
|
||||||
import { calculateTenure } from "../utils/tenure";
|
|
||||||
import { Command } from "../entities/Command";
|
import { Command } from "../entities/Command";
|
||||||
import { OrgRoot } from "../entities/OrgRoot";
|
import { OrgRoot } from "../entities/OrgRoot";
|
||||||
import Extension from "../interfaces/extension";
|
import Extension from "../interfaces/extension";
|
||||||
|
|
@ -176,10 +175,9 @@ export class ProfileSalaryEmployeeController extends Controller {
|
||||||
acc.push(existing);
|
acc.push(existing);
|
||||||
}
|
}
|
||||||
|
|
||||||
const { year, month, day } = calculateTenure(existing.days);
|
existing.year = Math.floor(existing.days / 365.2524);
|
||||||
existing.year = year;
|
existing.month = Math.floor((existing.days / 30.4375) % 12);
|
||||||
existing.month = month;
|
existing.day = Math.ceil(existing.days % 30.4375);
|
||||||
existing.day = day;
|
|
||||||
|
|
||||||
return acc;
|
return acc;
|
||||||
},
|
},
|
||||||
|
|
@ -213,10 +211,9 @@ export class ProfileSalaryEmployeeController extends Controller {
|
||||||
acc.push(existing);
|
acc.push(existing);
|
||||||
}
|
}
|
||||||
|
|
||||||
const { year, month, day } = calculateTenure(existing.days);
|
existing.year = Math.floor(existing.days / 365.2524);
|
||||||
existing.year = year;
|
existing.month = Math.floor((existing.days / 30.4375) % 12);
|
||||||
existing.month = month;
|
existing.day = Math.ceil(existing.days % 30.4375);
|
||||||
existing.day = day;
|
|
||||||
|
|
||||||
return acc;
|
return acc;
|
||||||
},
|
},
|
||||||
|
|
@ -269,10 +266,9 @@ export class ProfileSalaryEmployeeController extends Controller {
|
||||||
acc.push(existing);
|
acc.push(existing);
|
||||||
}
|
}
|
||||||
|
|
||||||
const { year, month, day } = calculateTenure(existing.days);
|
existing.year = Math.floor(existing.days / 365.2524);
|
||||||
existing.year = year;
|
existing.month = Math.floor((existing.days / 30.4375) % 12);
|
||||||
existing.month = month;
|
existing.day = Math.ceil(existing.days % 30.4375);
|
||||||
existing.day = day;
|
|
||||||
|
|
||||||
return acc;
|
return acc;
|
||||||
},
|
},
|
||||||
|
|
@ -306,10 +302,9 @@ export class ProfileSalaryEmployeeController extends Controller {
|
||||||
acc.push(existing);
|
acc.push(existing);
|
||||||
}
|
}
|
||||||
|
|
||||||
const { year, month, day } = calculateTenure(existing.days);
|
existing.year = Math.floor(existing.days / 365.2524);
|
||||||
existing.year = year;
|
existing.month = Math.floor((existing.days / 30.4375) % 12);
|
||||||
existing.month = month;
|
existing.day = Math.ceil(existing.days % 30.4375);
|
||||||
existing.day = day;
|
|
||||||
|
|
||||||
return acc;
|
return acc;
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -1433,10 +1433,10 @@ export class ProfileSalaryTempController extends Controller {
|
||||||
profileEmployeeId: x.profileEmployeeId,
|
profileEmployeeId: x.profileEmployeeId,
|
||||||
dateStart: x.commandDateAffect,
|
dateStart: x.commandDateAffect,
|
||||||
dateEnd: null,
|
dateEnd: null,
|
||||||
posNo: `${x.posNoAbb ?? ""} ${x.posNo ?? ""}`.trim(),
|
posNo: `${x.posNoAbb} ${x.posNo}`,
|
||||||
position: x.positionName,
|
position: x.positionName,
|
||||||
commandId: x.commandId,
|
commandId: x.commandId,
|
||||||
refCommandNo: [x.commandNo, x.commandYear].filter(Boolean).join("/") || undefined,
|
refCommandNo: `${x.commandNo}/${x.commandYear}`,
|
||||||
refCommandDate: x.commandDateAffect,
|
refCommandDate: x.commandDateAffect,
|
||||||
status: false,
|
status: false,
|
||||||
isDeleted: false,
|
isDeleted: false,
|
||||||
|
|
@ -1456,7 +1456,7 @@ export class ProfileSalaryTempController extends Controller {
|
||||||
dateStart: x.commandDateAffect,
|
dateStart: x.commandDateAffect,
|
||||||
dateEnd: null,
|
dateEnd: null,
|
||||||
commandId: x.commandId,
|
commandId: x.commandId,
|
||||||
commandNo: [x.commandNo, x.commandYear].filter(Boolean).join("/") || undefined,
|
commandNo: `${x.commandNo}/${x.commandYear}`,
|
||||||
commandName: x.commandName ?? "ให้ช่วยราชการ",
|
commandName: x.commandName ?? "ให้ช่วยราชการ",
|
||||||
refCommandDate: x.commandDateSign,
|
refCommandDate: x.commandDateSign,
|
||||||
refId: x.refId,
|
refId: x.refId,
|
||||||
|
|
|
||||||
|
|
@ -237,18 +237,11 @@ export class WorkflowController extends Controller {
|
||||||
savedStates.find((state) => state.id === so.stateId && state.order === 1),
|
savedStates.find((state) => state.id === so.stateId && state.order === 1),
|
||||||
);
|
);
|
||||||
|
|
||||||
// add link sysName = REGISTRY_PROFILE or REGISTRY_PROFILE_EMP
|
|
||||||
let notiLink = '';
|
|
||||||
if (body.sysName === 'REGISTRY_PROFILE') {
|
|
||||||
notiLink = `${process.env.VITE_URL_MGT}/registry-officer/request-edit/personal/${body.refId}`;
|
|
||||||
} else if (body.sysName === 'REGISTRY_PROFILE_EMP') {
|
|
||||||
notiLink = `${process.env.VITE_URL_MGT}/registry-employee/request-edit/personal/${body.refId}`;
|
|
||||||
}
|
|
||||||
const notificationReceivers = stateOperatorUsersToCreate
|
const notificationReceivers = stateOperatorUsersToCreate
|
||||||
.filter((user) => firstStateOperators.some((op) => op.operator === user.operator))
|
.filter((user) => firstStateOperators.some((op) => op.operator === user.operator))
|
||||||
.map((user) => ({
|
.map((user) => ({
|
||||||
receiverUserId: user.profileType === "OFFICER" ? user.profileId : user.profileEmployeeId,
|
receiverUserId: user.profileType === "OFFICER" ? user.profileId : user.profileEmployeeId,
|
||||||
notiLink: notiLink,
|
notiLink: "",
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// ส่ง notification แบบ fire-and-forget
|
// ส่ง notification แบบ fire-and-forget
|
||||||
|
|
|
||||||
|
|
@ -140,54 +140,6 @@ export class Profile extends EntityBase {
|
||||||
})
|
})
|
||||||
posTypeId: string | null;
|
posTypeId: string | null;
|
||||||
|
|
||||||
@Column({
|
|
||||||
nullable: true,
|
|
||||||
comment: "สายงาน",
|
|
||||||
length: 45,
|
|
||||||
default: null,
|
|
||||||
})
|
|
||||||
positionField: string;
|
|
||||||
|
|
||||||
@Column({
|
|
||||||
nullable: true,
|
|
||||||
comment: "ตำแหน่งทางการบริหาร",
|
|
||||||
length: 255,
|
|
||||||
default: null,
|
|
||||||
})
|
|
||||||
posExecutive?: string;
|
|
||||||
|
|
||||||
@Column({
|
|
||||||
nullable: true,
|
|
||||||
comment: "ด้าน/สาขา",
|
|
||||||
length: 255,
|
|
||||||
default: null,
|
|
||||||
})
|
|
||||||
positionArea?: string;
|
|
||||||
|
|
||||||
@Column({
|
|
||||||
nullable: true,
|
|
||||||
comment: "ด้านทางการบริหาร",
|
|
||||||
length: 255,
|
|
||||||
default: null,
|
|
||||||
})
|
|
||||||
positionExecutiveField?: string;
|
|
||||||
|
|
||||||
@Column({
|
|
||||||
nullable: true,
|
|
||||||
comment: "เลขที่ตำแหน่ง",
|
|
||||||
length: 255,
|
|
||||||
default: null,
|
|
||||||
})
|
|
||||||
posMasterNo?: string;
|
|
||||||
|
|
||||||
@Column({
|
|
||||||
nullable: true,
|
|
||||||
comment: "สังกัด",
|
|
||||||
type: "text",
|
|
||||||
default: null,
|
|
||||||
})
|
|
||||||
org?: string;
|
|
||||||
|
|
||||||
@Column({
|
@Column({
|
||||||
nullable: true,
|
nullable: true,
|
||||||
length: 255,
|
length: 255,
|
||||||
|
|
|
||||||
|
|
@ -116,34 +116,6 @@ export async function withRetry<T>(
|
||||||
throw lastError;
|
throw lastError;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Fetch with timeout
|
|
||||||
* Aborts request if it takes longer than specified timeout
|
|
||||||
*/
|
|
||||||
async function fetchWithTimeout(
|
|
||||||
url: RequestInfo | URL,
|
|
||||||
options: RequestInit = {},
|
|
||||||
timeout: number = 10000,
|
|
||||||
): Promise<Response> {
|
|
||||||
const controller = new AbortController();
|
|
||||||
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
||||||
|
|
||||||
try {
|
|
||||||
const response = await fetch(url, {
|
|
||||||
...options,
|
|
||||||
signal: controller.signal,
|
|
||||||
});
|
|
||||||
clearTimeout(timeoutId);
|
|
||||||
return response;
|
|
||||||
} catch (error: any) {
|
|
||||||
clearTimeout(timeoutId);
|
|
||||||
if (error.name === "AbortError") {
|
|
||||||
throw new Error(`Request timeout after ${timeout}ms`);
|
|
||||||
}
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const KC_URL = process.env.KC_URL;
|
const KC_URL = process.env.KC_URL;
|
||||||
const KC_REALMS = process.env.KC_REALMS;
|
const KC_REALMS = process.env.KC_REALMS;
|
||||||
const KC_CLIENT_ID = process.env.KC_SERVICE_ACCOUNT_CLIENT_ID;
|
const KC_CLIENT_ID = process.env.KC_SERVICE_ACCOUNT_CLIENT_ID;
|
||||||
|
|
@ -172,12 +144,10 @@ export function isTokenExpired(token: string, beforeExpire: number = 30) {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get token from keycloak if needed
|
* Get token from keycloak if needed
|
||||||
* Returns null if Keycloak is unavailable
|
|
||||||
*/
|
*/
|
||||||
export async function getToken(): Promise<string | null> {
|
export async function getToken() {
|
||||||
if (!KC_CLIENT_ID || !KC_SECRET) {
|
if (!KC_CLIENT_ID || !KC_SECRET) {
|
||||||
console.error("[getToken] KC_CLIENT_ID and KC_SECRET are required");
|
throw new Error("KC_CLIENT_ID and KC_SECRET are required to used this feature.");
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (token && !isTokenExpired(token)) return token;
|
if (token && !isTokenExpired(token)) return token;
|
||||||
|
|
@ -188,35 +158,22 @@ export async function getToken(): Promise<string | null> {
|
||||||
body.append("client_secret", KC_SECRET);
|
body.append("client_secret", KC_SECRET);
|
||||||
body.append("grant_type", "client_credentials");
|
body.append("grant_type", "client_credentials");
|
||||||
|
|
||||||
try {
|
const res = await fetch(`${KC_URL}/realms/${KC_REALMS}/protocol/openid-connect/token`, {
|
||||||
const res = await fetchWithTimeout(
|
|
||||||
`${KC_URL}/realms/${KC_REALMS}/protocol/openid-connect/token`,
|
|
||||||
{
|
|
||||||
method: "POST",
|
method: "POST",
|
||||||
body: body,
|
body: body,
|
||||||
},
|
}).catch((e) => console.error(e));
|
||||||
10000,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!res.ok) {
|
if (!res) {
|
||||||
console.error(`[getToken] Keycloak token request failed: ${res.status}`);
|
throw new Error("Cannot get token from keycloak.");
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = (await res.json()) as any;
|
const data = (await res.json()) as any;
|
||||||
|
|
||||||
if (data && data.access_token) {
|
if (data && data.access_token) {
|
||||||
token = data.access_token;
|
token = data.access_token;
|
||||||
console.log(`[getToken] Token refreshed successfully`);
|
}
|
||||||
|
console.log(`token: ${token}`);
|
||||||
return token;
|
return token;
|
||||||
}
|
|
||||||
|
|
||||||
console.error("[getToken] No access_token in response");
|
|
||||||
return null;
|
|
||||||
} catch (error: any) {
|
|
||||||
console.error(`[getToken] Failed to get token: ${error.message}`);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -232,16 +189,10 @@ export async function createUser(
|
||||||
opts?: Record<string, any>,
|
opts?: Record<string, any>,
|
||||||
token?: string,
|
token?: string,
|
||||||
) {
|
) {
|
||||||
const authToken = token || (await getToken());
|
|
||||||
if (!authToken) {
|
|
||||||
console.error("[createUser] Failed to get Keycloak token");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const res = await fetch(`${KC_URL}/admin/realms/${KC_REALMS}/users`, {
|
const res = await fetch(`${KC_URL}/admin/realms/${KC_REALMS}/users`, {
|
||||||
// prettier-ignore
|
// prettier-ignore
|
||||||
headers: {
|
headers: {
|
||||||
"authorization": `Bearer ${authToken}`,
|
"authorization": `Bearer ${token || await getToken()}`,
|
||||||
"content-type": `application/json`,
|
"content-type": `application/json`,
|
||||||
},
|
},
|
||||||
method: "POST",
|
method: "POST",
|
||||||
|
|
@ -255,6 +206,7 @@ export async function createUser(
|
||||||
|
|
||||||
if (!res) return false;
|
if (!res) return false;
|
||||||
if (!res.ok) {
|
if (!res.ok) {
|
||||||
|
// return Boolean(console.error("Keycloak Error Response: ", await res.json()));
|
||||||
return await res.json();
|
return await res.json();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -271,16 +223,10 @@ export async function createUser(
|
||||||
* @returns user if success, false otherwise.
|
* @returns user if success, false otherwise.
|
||||||
*/
|
*/
|
||||||
export async function getUser(userId: string, token?: string) {
|
export async function getUser(userId: string, token?: string) {
|
||||||
const authToken = token || (await getToken());
|
|
||||||
if (!authToken) {
|
|
||||||
console.error("[getUser] Failed to get Keycloak token");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const res = await fetch(`${KC_URL}/admin/realms/${KC_REALMS}/users/${userId}`, {
|
const res = await fetch(`${KC_URL}/admin/realms/${KC_REALMS}/users/${userId}`, {
|
||||||
// prettier-ignore
|
// prettier-ignore
|
||||||
headers: {
|
headers: {
|
||||||
"authorization": `Bearer ${authToken}`,
|
"authorization": `Bearer ${token || await getToken()}`,
|
||||||
"content-type": `application/json`,
|
"content-type": `application/json`,
|
||||||
},
|
},
|
||||||
}).catch((e) => console.log("Keycloak Error: ", e));
|
}).catch((e) => console.log("Keycloak Error: ", e));
|
||||||
|
|
@ -299,16 +245,10 @@ export async function getUser(userId: string, token?: string) {
|
||||||
* @returns user if success, false otherwise.
|
* @returns user if success, false otherwise.
|
||||||
*/
|
*/
|
||||||
export async function getUserByUsername(citizenId: string, token?: string) {
|
export async function getUserByUsername(citizenId: string, token?: string) {
|
||||||
const authToken = token || (await getToken());
|
|
||||||
if (!authToken) {
|
|
||||||
console.error("[getUserByUsername] Failed to get Keycloak token");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const res = await fetch(`${KC_URL}/admin/realms/${KC_REALMS}/users?username=${citizenId}`, {
|
const res = await fetch(`${KC_URL}/admin/realms/${KC_REALMS}/users?username=${citizenId}`, {
|
||||||
// prettier-ignore
|
// prettier-ignore
|
||||||
headers: {
|
headers: {
|
||||||
"authorization": `Bearer ${authToken}`,
|
"authorization": `Bearer ${token || await getToken()}`,
|
||||||
"content-type": `application/json`,
|
"content-type": `application/json`,
|
||||||
},
|
},
|
||||||
}).catch((e) => console.log("Keycloak Error: ", e));
|
}).catch((e) => console.log("Keycloak Error: ", e));
|
||||||
|
|
@ -439,38 +379,23 @@ export async function getUserCountOrg(first = "", max = "", search = "", userIds
|
||||||
export async function editUser(userId: string, opts: Record<string, any>) {
|
export async function editUser(userId: string, opts: Record<string, any>) {
|
||||||
const { password, ...rest } = opts;
|
const { password, ...rest } = opts;
|
||||||
|
|
||||||
const token = await getToken();
|
|
||||||
if (!token) {
|
|
||||||
console.error("[editUser] Failed to get Keycloak token");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get existing user data to preserve other fields
|
|
||||||
const existingUser = await getUser(userId, token);
|
|
||||||
if (!existingUser) {
|
|
||||||
console.error(`[editUser] User ${userId} not found in Keycloak`);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Merge existing user data with updated fields
|
|
||||||
const updatedUser = {
|
|
||||||
...existingUser,
|
|
||||||
...rest,
|
|
||||||
credentials: (password && [{ type: "password", value: opts?.password }]) || undefined,
|
|
||||||
};
|
|
||||||
|
|
||||||
const res = await fetch(`${KC_URL}/admin/realms/${KC_REALMS}/users/${userId}`, {
|
const res = await fetch(`${KC_URL}/admin/realms/${KC_REALMS}/users/${userId}`, {
|
||||||
// prettier-ignore
|
// prettier-ignore
|
||||||
headers: {
|
headers: {
|
||||||
"authorization": `Bearer ${token}`,
|
"authorization": `Bearer ${await getToken()}`,
|
||||||
"content-type": `application/json`,
|
"content-type": `application/json`,
|
||||||
},
|
},
|
||||||
method: "PUT",
|
method: "PUT",
|
||||||
body: JSON.stringify(updatedUser),
|
body: JSON.stringify({
|
||||||
|
enabled: true,
|
||||||
|
credentials: (password && [{ type: "password", value: opts?.password }]) || undefined,
|
||||||
|
...rest,
|
||||||
|
}),
|
||||||
}).catch((e) => console.log("Keycloak Error: ", e));
|
}).catch((e) => console.log("Keycloak Error: ", e));
|
||||||
|
|
||||||
if (!res) return false;
|
if (!res) return false;
|
||||||
if (!res.ok) {
|
if (!res.ok) {
|
||||||
|
// return Boolean(console.error("Keycloak Error Response: ", await res.json()));
|
||||||
return await res.json();
|
return await res.json();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -494,24 +419,6 @@ export async function updateName(
|
||||||
) {
|
) {
|
||||||
// const { password, ...rest } = opts;
|
// const { password, ...rest } = opts;
|
||||||
|
|
||||||
// Get existing user data to preserve other fields
|
|
||||||
const existingUser = await getUser(userId);
|
|
||||||
if (!existingUser) {
|
|
||||||
console.error(`[updateName] User ${userId} not found in Keycloak`);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Merge existing user data with updated name fields
|
|
||||||
const updatedUser = {
|
|
||||||
...existingUser,
|
|
||||||
firstName,
|
|
||||||
lastName,
|
|
||||||
attributes: {
|
|
||||||
...(existingUser.attributes || {}),
|
|
||||||
prefix,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const res = await fetch(`${KC_URL}/admin/realms/${KC_REALMS}/users/${userId}`, {
|
const res = await fetch(`${KC_URL}/admin/realms/${KC_REALMS}/users/${userId}`, {
|
||||||
// prettier-ignore
|
// prettier-ignore
|
||||||
headers: {
|
headers: {
|
||||||
|
|
@ -519,7 +426,16 @@ export async function updateName(
|
||||||
"content-type": `application/json`,
|
"content-type": `application/json`,
|
||||||
},
|
},
|
||||||
method: "PUT",
|
method: "PUT",
|
||||||
body: JSON.stringify(updatedUser),
|
body: JSON.stringify({
|
||||||
|
enabled: true,
|
||||||
|
// credentials: (password && [{ type: "password", value: opts?.password }]) || undefined,
|
||||||
|
// ...rest,
|
||||||
|
firstName,
|
||||||
|
lastName,
|
||||||
|
attributes: {
|
||||||
|
prefix,
|
||||||
|
},
|
||||||
|
}),
|
||||||
}).catch((e) => console.log("Keycloak Error: ", e));
|
}).catch((e) => console.log("Keycloak Error: ", e));
|
||||||
|
|
||||||
if (!res) return false;
|
if (!res) return false;
|
||||||
|
|
@ -570,16 +486,10 @@ export async function enableStatus(userId: string, status: boolean) {
|
||||||
* @returns user true if success, false otherwise.
|
* @returns user true if success, false otherwise.
|
||||||
*/
|
*/
|
||||||
export async function deleteUser(userId: string, token?: string) {
|
export async function deleteUser(userId: string, token?: string) {
|
||||||
const authToken = token || (await getToken());
|
|
||||||
if (!authToken) {
|
|
||||||
console.error("[deleteUser] Failed to get Keycloak token");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const res = await fetch(`${KC_URL}/admin/realms/${KC_REALMS}/users/${userId}`, {
|
const res = await fetch(`${KC_URL}/admin/realms/${KC_REALMS}/users/${userId}`, {
|
||||||
// prettier-ignore
|
// prettier-ignore
|
||||||
headers: {
|
headers: {
|
||||||
"authorization": `Bearer ${authToken}`,
|
"authorization": `Bearer ${token || await getToken()}`,
|
||||||
"content-type": `application/json`,
|
"content-type": `application/json`,
|
||||||
},
|
},
|
||||||
method: "DELETE",
|
method: "DELETE",
|
||||||
|
|
@ -961,16 +871,10 @@ export async function removeUserGroup(userId: string, groupId: string) {
|
||||||
// Function to change user password
|
// Function to change user password
|
||||||
export async function changeUserPassword(userId: string, newPassword: string) {
|
export async function changeUserPassword(userId: string, newPassword: string) {
|
||||||
try {
|
try {
|
||||||
const token = await getToken();
|
|
||||||
if (!token) {
|
|
||||||
console.error("[changeUserPassword] Failed to get Keycloak token");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const res = await fetch(`${KC_URL}/admin/realms/${KC_REALMS}/users/${userId}/reset-password`, {
|
const res = await fetch(`${KC_URL}/admin/realms/${KC_REALMS}/users/${userId}/reset-password`, {
|
||||||
// prettier-ignore
|
// prettier-ignore
|
||||||
headers: {
|
headers: {
|
||||||
"authorization": `Bearer ${token}`,
|
"authorization": `Bearer ${await getToken()}`,
|
||||||
"content-type": `application/json`,
|
"content-type": `application/json`,
|
||||||
},
|
},
|
||||||
method: "PUT",
|
method: "PUT",
|
||||||
|
|
@ -981,15 +885,6 @@ export async function changeUserPassword(userId: string, newPassword: string) {
|
||||||
}),
|
}),
|
||||||
}).catch((e) => console.log("Keycloak Error: ", e));
|
}).catch((e) => console.log("Keycloak Error: ", e));
|
||||||
|
|
||||||
if (!res) {
|
|
||||||
console.error("[changeUserPassword] No response from Keycloak");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (!res.ok) {
|
|
||||||
console.error(`[changeUserPassword] Failed to change password: ${res.status}`);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error changing password:", error);
|
console.error("Error changing password:", error);
|
||||||
|
|
@ -1000,61 +895,60 @@ export async function changeUserPassword(userId: string, newPassword: string) {
|
||||||
// Function to reset password
|
// Function to reset password
|
||||||
export async function resetPassword(username: string) {
|
export async function resetPassword(username: string) {
|
||||||
try {
|
try {
|
||||||
const token = await getToken();
|
// if (!API_KEY || !AUTH_ACCOUNT_SECRET) {
|
||||||
if (!token) {
|
// throw new Error("KC_CLIENT_ID and KC_SECRET are required to used this feature.");
|
||||||
console.error("[resetPassword] Failed to get Keycloak token");
|
// }
|
||||||
return false;
|
// const body = new URLSearchParams();
|
||||||
}
|
// body.append("client_id", "gettoken");
|
||||||
|
// body.append("client_secret", AUTH_ACCOUNT_SECRET?.toString());
|
||||||
|
// body.append("grant_type", "client_credentials");
|
||||||
|
// const tokenResponse = await fetch(`${process.env.KC_URL}/realms/${process.env.KC_REALMS}/protocol/openid-connect/token`, {
|
||||||
|
// method: "POST",
|
||||||
|
// headers: {
|
||||||
|
// "Content-Type": "application/x-www-form-urlencoded",
|
||||||
|
// api_key: API_KEY,
|
||||||
|
// },
|
||||||
|
// body: body
|
||||||
|
// });
|
||||||
|
// if (!tokenResponse.ok) {
|
||||||
|
// throw new Error("Failed to get admin token");
|
||||||
|
// }
|
||||||
|
// const tokenData = await tokenResponse.json();
|
||||||
|
// const adminToken = tokenData.access_token;
|
||||||
|
|
||||||
const users = await fetchWithTimeout(
|
const users = await fetch(
|
||||||
`${KC_URL}/admin/realms/${KC_REALMS}/users?email=${encodeURIComponent(username)}`,
|
`${KC_URL}/admin/realms/${KC_REALMS}/users?email=${encodeURIComponent(username)}`,
|
||||||
{
|
{
|
||||||
headers: {
|
headers: {
|
||||||
authorization: `Bearer ${token}`,
|
authorization: `Bearer ${await getToken()}`,
|
||||||
|
// "authorization": `Bearer ${adminToken}`,
|
||||||
"content-type": `application/json`,
|
"content-type": `application/json`,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
10000,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!users.ok) {
|
if (!users.ok) {
|
||||||
const errorText = await users.text();
|
|
||||||
console.error(`[resetPassword] Failed to search user. Status: ${users.status}, Error: ${errorText}`);
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const usersData = await users.json();
|
const usersData = await users.json();
|
||||||
|
|
||||||
if (!usersData || usersData.length === 0) {
|
|
||||||
console.error(`[resetPassword] User not found with email: ${username}`);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const userId = usersData[0].id;
|
const userId = usersData[0].id;
|
||||||
|
const resetResponse = await fetch(
|
||||||
const resetResponse = await fetchWithTimeout(
|
|
||||||
`${KC_URL}/admin/realms/${KC_REALMS}/users/${userId}/execute-actions-email`,
|
`${KC_URL}/admin/realms/${KC_REALMS}/users/${userId}/execute-actions-email`,
|
||||||
{
|
{
|
||||||
method: "PUT",
|
method: "PUT",
|
||||||
headers: {
|
headers: {
|
||||||
Authorization: `Bearer ${await getToken()}`,
|
Authorization: `Bearer ${await getToken()}`,
|
||||||
|
// "Authorization": `Bearer ${adminToken}`,
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
},
|
},
|
||||||
body: JSON.stringify(["UPDATE_PASSWORD"]),
|
body: JSON.stringify(["UPDATE_PASSWORD"]),
|
||||||
},
|
},
|
||||||
10000,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!resetResponse.ok) {
|
if (!resetResponse.ok) {
|
||||||
const errorText = await resetResponse.text();
|
|
||||||
console.error(`[resetPassword] Failed to send reset email. Status: ${resetResponse.status}, Error: ${errorText}`);
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(`[resetPassword] Password reset email sent successfully to: ${username}`);
|
|
||||||
return { message: "Password reset email sent" };
|
return { message: "Password reset email sent" };
|
||||||
} catch (error: any) {
|
} catch (error) {
|
||||||
console.error(`[resetPassword] Error triggering password reset: ${error.message}`);
|
console.error("Error triggering password reset:", error);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1064,14 +958,8 @@ export async function updateUserAttributes(
|
||||||
attributes: Record<string, string[]>,
|
attributes: Record<string, string[]>,
|
||||||
): Promise<boolean> {
|
): Promise<boolean> {
|
||||||
try {
|
try {
|
||||||
const token = await getToken();
|
|
||||||
if (!token) {
|
|
||||||
console.error("[updateUserAttributes] Failed to get Keycloak token");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get existing user data to preserve other attributes
|
// Get existing user data to preserve other attributes
|
||||||
const existingUser = await getUser(userId, token);
|
const existingUser = await getUser(userId);
|
||||||
|
|
||||||
if (!existingUser) {
|
if (!existingUser) {
|
||||||
console.error(`User ${userId} not found in Keycloak`);
|
console.error(`User ${userId} not found in Keycloak`);
|
||||||
|
|
@ -1096,7 +984,7 @@ export async function updateUserAttributes(
|
||||||
|
|
||||||
const res = await fetch(`${KC_URL}/admin/realms/${KC_REALMS}/users/${userId}`, {
|
const res = await fetch(`${KC_URL}/admin/realms/${KC_REALMS}/users/${userId}`, {
|
||||||
headers: {
|
headers: {
|
||||||
authorization: `Bearer ${token}`,
|
authorization: `Bearer ${await getToken()}`,
|
||||||
"content-type": "application/json",
|
"content-type": "application/json",
|
||||||
},
|
},
|
||||||
method: "PUT",
|
method: "PUT",
|
||||||
|
|
|
||||||
|
|
@ -1,37 +0,0 @@
|
||||||
import { MigrationInterface, QueryRunner } from "typeorm";
|
|
||||||
|
|
||||||
export class AddPositionFieldsToProfile1776308026834 implements MigrationInterface {
|
|
||||||
name = 'AddPositionFieldsToProfile1776308026834'
|
|
||||||
|
|
||||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
|
||||||
await queryRunner.query(`ALTER TABLE \`profile\` ADD \`positionField\` varchar(45) NULL COMMENT 'สายงาน'`);
|
|
||||||
await queryRunner.query(`ALTER TABLE \`profile\` ADD \`posExecutive\` varchar(255) NULL COMMENT 'ตำแหน่งทางการบริหาร'`);
|
|
||||||
await queryRunner.query(`ALTER TABLE \`profile\` ADD \`positionArea\` varchar(255) NULL COMMENT 'ด้าน/สาขา'`);
|
|
||||||
await queryRunner.query(`ALTER TABLE \`profile\` ADD \`positionExecutiveField\` varchar(255) NULL COMMENT 'ด้านทางการบริหาร'`);
|
|
||||||
await queryRunner.query(`ALTER TABLE \`profile\` ADD \`posMasterNo\` varchar(255) NULL COMMENT 'เลขที่ตำแหน่ง'`);
|
|
||||||
await queryRunner.query(`ALTER TABLE \`profile\` ADD \`org\` text NULL COMMENT 'สังกัด'`);
|
|
||||||
|
|
||||||
await queryRunner.query(`ALTER TABLE \`profileHistory\` ADD \`positionField\` varchar(45) NULL COMMENT 'สายงาน'`);
|
|
||||||
await queryRunner.query(`ALTER TABLE \`profileHistory\` ADD \`posExecutive\` varchar(255) NULL COMMENT 'ตำแหน่งทางการบริหาร'`);
|
|
||||||
await queryRunner.query(`ALTER TABLE \`profileHistory\` ADD \`positionArea\` varchar(255) NULL COMMENT 'ด้าน/สาขา'`);
|
|
||||||
await queryRunner.query(`ALTER TABLE \`profileHistory\` ADD \`positionExecutiveField\` varchar(255) NULL COMMENT 'ด้านทางการบริหาร'`);
|
|
||||||
await queryRunner.query(`ALTER TABLE \`profileHistory\` ADD \`posMasterNo\` varchar(255) NULL COMMENT 'เลขที่ตำแหน่ง'`);
|
|
||||||
await queryRunner.query(`ALTER TABLE \`profileHistory\` ADD \`org\` text NULL COMMENT 'สังกัด'`);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
|
||||||
await queryRunner.query(`ALTER TABLE \`profileHistory\` DROP COLUMN \`org\``);
|
|
||||||
await queryRunner.query(`ALTER TABLE \`profileHistory\` DROP COLUMN \`posMasterNo\``);
|
|
||||||
await queryRunner.query(`ALTER TABLE \`profileHistory\` DROP COLUMN \`positionExecutiveField\``);
|
|
||||||
await queryRunner.query(`ALTER TABLE \`profileHistory\` DROP COLUMN \`positionArea\``);
|
|
||||||
await queryRunner.query(`ALTER TABLE \`profileHistory\` DROP COLUMN \`posExecutive\``);
|
|
||||||
await queryRunner.query(`ALTER TABLE \`profileHistory\` DROP COLUMN \`positionField\``);
|
|
||||||
|
|
||||||
await queryRunner.query(`ALTER TABLE \`profile\` DROP COLUMN \`org\``);
|
|
||||||
await queryRunner.query(`ALTER TABLE \`profile\` DROP COLUMN \`posMasterNo\``);
|
|
||||||
await queryRunner.query(`ALTER TABLE \`profile\` DROP COLUMN \`positionExecutiveField\``);
|
|
||||||
await queryRunner.query(`ALTER TABLE \`profile\` DROP COLUMN \`positionArea\``);
|
|
||||||
await queryRunner.query(`ALTER TABLE \`profile\` DROP COLUMN \`posExecutive\``);
|
|
||||||
await queryRunner.query(`ALTER TABLE \`profile\` DROP COLUMN \`positionField\``);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,186 +0,0 @@
|
||||||
import { AppDataSource } from "../database/data-source";
|
|
||||||
import { AuthRoleAttr } from "../entities/AuthRoleAttr";
|
|
||||||
import { PosMasterAct } from "../entities/PosMasterAct";
|
|
||||||
|
|
||||||
export interface ActingPositionData {
|
|
||||||
isAct: boolean;
|
|
||||||
posMasterActs: Array<{
|
|
||||||
privilege: string | null;
|
|
||||||
posNo: string | null;
|
|
||||||
rootDnaId: string | null;
|
|
||||||
child1DnaId: string | null;
|
|
||||||
child2DnaId: string | null;
|
|
||||||
child3DnaId: string | null;
|
|
||||||
child4DnaId: string | null;
|
|
||||||
}>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ActingPositionWithPrivilegeData extends ActingPositionData {
|
|
||||||
privilege?: string | null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Service สำหรับจัดการข้อมูลตำแหน่งที่รักษาการและ privilege
|
|
||||||
*/
|
|
||||||
export class ActingPositionService {
|
|
||||||
private posMasterActRepo = AppDataSource.getRepository(PosMasterAct);
|
|
||||||
private authRoleAttrRepo = AppDataSource.getRepository(AuthRoleAttr);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* ดึงข้อมูลตำแหน่งที่รักษาการและ privilege
|
|
||||||
*
|
|
||||||
* @param profileId - ID ของ profile ที่ต้องการตรวจสอบ
|
|
||||||
* @param orgRevisionId - ID ของ orgRevision ปัจจุบัน
|
|
||||||
* @param action - Action ที่ต้องการตรวจสอบสิทธิ์ (CREATE, DELETE, GET, LIST, UPDATE)
|
|
||||||
* @param system - System ID ที่ต้องการตรวจสอบสิทธิ์ (authSysId)
|
|
||||||
* @returns ข้อมูลตำแหน่งที่รักษาการและ privilege
|
|
||||||
*/
|
|
||||||
async getActingPositionsWithPrivilege(
|
|
||||||
profileId: string,
|
|
||||||
orgRevisionId: string | undefined,
|
|
||||||
action?: string,
|
|
||||||
system?: string
|
|
||||||
): Promise<ActingPositionWithPrivilegeData> {
|
|
||||||
// ดึงข้อมูล posMasterAct โดย join กับ posMaster (ตำแหน่งที่ถูกรักษาการ)
|
|
||||||
const posMasterActs = await this.posMasterActRepo
|
|
||||||
.createQueryBuilder("posMasterAct")
|
|
||||||
.leftJoinAndSelect("posMasterAct.posMaster", "posMaster")
|
|
||||||
.addSelect([
|
|
||||||
"posMaster.authRoleId", // เพิ่มการดึง authRoleId จากตำแหน่งที่ถูกรักษาการ
|
|
||||||
"posMaster.posMasterNo", // เพิ่มการดึงเลขที่ตำแหน่ง
|
|
||||||
"posMaster.posMasterNoPrefix", // เพิ่มการดึง prefix ของเลขที่ตำแหน่ง
|
|
||||||
"posMaster.posMasterNoSuffix" // เพิ่มการดึง suffix ของเลขที่ตำแหน่ง
|
|
||||||
])
|
|
||||||
.leftJoinAndSelect("posMaster.orgRoot", "orgRoot")
|
|
||||||
.leftJoinAndSelect("posMaster.orgChild1", "orgChild1")
|
|
||||||
.leftJoinAndSelect("posMaster.orgChild2", "orgChild2")
|
|
||||||
.leftJoinAndSelect("posMaster.orgChild3", "orgChild3")
|
|
||||||
.leftJoinAndSelect("posMaster.orgChild4", "orgChild4")
|
|
||||||
.leftJoinAndSelect("posMaster.orgRevision", "orgRevision")
|
|
||||||
.leftJoinAndSelect("posMasterAct.posMasterChild", "posMasterChild")
|
|
||||||
.leftJoinAndSelect("posMasterChild.current_holder", "profileChild")
|
|
||||||
.where("profileChild.id = :profileId", { profileId })
|
|
||||||
.andWhere("posMaster.orgRevisionId = :orgRevisionId", { orgRevisionId })
|
|
||||||
.andWhere("orgRevision.orgRevisionIsCurrent = true")
|
|
||||||
.andWhere("orgRevision.orgRevisionIsDraft = false")
|
|
||||||
.getMany();
|
|
||||||
|
|
||||||
if (posMasterActs.length === 0) {
|
|
||||||
return {
|
|
||||||
isAct: false,
|
|
||||||
posMasterActs: [],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// วนลูปแต่ละ posMasterAct เพื่อดึง privilege ของตำแหน่งที่รักษาการ
|
|
||||||
const posMasterActsResponse = await Promise.all(
|
|
||||||
posMasterActs.map(async (act) => {
|
|
||||||
let privilege: string | null = null;
|
|
||||||
let privileges: Record<string, string> = {};
|
|
||||||
|
|
||||||
if (act.posMaster?.authRoleId) {
|
|
||||||
// ถ้าระบุ action และ system มา ให้ดึงเฉพาะ privilege ของระบบนั้นๆ
|
|
||||||
if (action && system) {
|
|
||||||
const roleAttr = await this.authRoleAttrRepo
|
|
||||||
.createQueryBuilder("authRoleAttr")
|
|
||||||
.select(["authRoleAttr.attrPrivilege", "authRoleAttr.attrIsCreate", "authRoleAttr.attrIsDelete", "authRoleAttr.attrIsGet", "authRoleAttr.attrIsList", "authRoleAttr.attrIsUpdate"])
|
|
||||||
.where("authRoleAttr.authRoleId = :authRoleId", {
|
|
||||||
authRoleId: act.posMaster.authRoleId,
|
|
||||||
})
|
|
||||||
.andWhere("authRoleAttr.authSysId = :system", { system })
|
|
||||||
.getOne();
|
|
||||||
|
|
||||||
if (roleAttr) {
|
|
||||||
// ตรวจสอบสิทธิ์ตาม action
|
|
||||||
let hasPermission = false;
|
|
||||||
const actionUpper = action.trim().toUpperCase();
|
|
||||||
|
|
||||||
switch (actionUpper) {
|
|
||||||
case "CREATE":
|
|
||||||
hasPermission = roleAttr.attrIsCreate;
|
|
||||||
break;
|
|
||||||
case "DELETE":
|
|
||||||
hasPermission = roleAttr.attrIsDelete;
|
|
||||||
break;
|
|
||||||
case "GET":
|
|
||||||
hasPermission = roleAttr.attrIsGet;
|
|
||||||
break;
|
|
||||||
case "LIST":
|
|
||||||
hasPermission = roleAttr.attrIsList;
|
|
||||||
break;
|
|
||||||
case "UPDATE":
|
|
||||||
hasPermission = roleAttr.attrIsUpdate;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hasPermission) {
|
|
||||||
privilege = roleAttr.attrPrivilege;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// ดึงข้อมูล AuthRoleAttr สำหรับทุกระบบ
|
|
||||||
const roleAttrs = await this.authRoleAttrRepo
|
|
||||||
.createQueryBuilder("authRoleAttr")
|
|
||||||
.select(["authRoleAttr.authSysId", "authRoleAttr.attrPrivilege"])
|
|
||||||
.where("authRoleAttr.authRoleId = :authRoleId", {
|
|
||||||
authRoleId: act.posMaster.authRoleId,
|
|
||||||
})
|
|
||||||
.getMany();
|
|
||||||
|
|
||||||
privileges = roleAttrs.reduce((acc, attr) => {
|
|
||||||
acc[attr.authSysId] = attr.attrPrivilege;
|
|
||||||
return acc;
|
|
||||||
}, {} as Record<string, string>);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// จัดรูปแบบเลขที่ตำแหน่งตามรูปแบบ shortName ที่ใช้ในระบบ
|
|
||||||
const holder = act.posMaster;
|
|
||||||
const posNo = !holder
|
|
||||||
? null
|
|
||||||
: holder.orgChild4 != null
|
|
||||||
? `${holder.orgChild4.orgChild4ShortName} ${holder.posMasterNo}`
|
|
||||||
: holder.orgChild3 != null
|
|
||||||
? `${holder.orgChild3.orgChild3ShortName} ${holder.posMasterNo}`
|
|
||||||
: holder.orgChild2 != null
|
|
||||||
? `${holder.orgChild2.orgChild2ShortName} ${holder.posMasterNo}`
|
|
||||||
: holder.orgChild1 != null
|
|
||||||
? `${holder.orgChild1.orgChild1ShortName} ${holder.posMasterNo}`
|
|
||||||
: holder.orgRoot != null
|
|
||||||
? `${holder.orgRoot.orgRootShortName} ${holder.posMasterNo}`
|
|
||||||
: null;
|
|
||||||
|
|
||||||
return {
|
|
||||||
posNo: posNo,
|
|
||||||
privilege: action && system ? privilege : JSON.stringify(privileges),
|
|
||||||
rootDnaId: act.posMaster?.orgRoot?.ancestorDNA ?? null,
|
|
||||||
child1DnaId: act.posMaster?.orgChild1?.ancestorDNA ?? null,
|
|
||||||
child2DnaId: act.posMaster?.orgChild2?.ancestorDNA ?? null,
|
|
||||||
child3DnaId: act.posMaster?.orgChild3?.ancestorDNA ?? null,
|
|
||||||
child4DnaId: act.posMaster?.orgChild4?.ancestorDNA ?? null,
|
|
||||||
};
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
// ถ้าระบุ action และ system มา ให้ดึง privilege ของตำแหน่งแรก
|
|
||||||
let specificPrivilege: string | null = null;
|
|
||||||
if (action && system && posMasterActsResponse.length > 0) {
|
|
||||||
specificPrivilege = posMasterActsResponse[0].privilege;
|
|
||||||
}
|
|
||||||
|
|
||||||
const response: ActingPositionWithPrivilegeData = {
|
|
||||||
isAct: true,
|
|
||||||
posMasterActs: posMasterActsResponse,
|
|
||||||
};
|
|
||||||
|
|
||||||
// ถ้าระบุ action และ system มา ให้เพิ่ม privilege เข้าไปใน response ด้วย
|
|
||||||
if (action && system) {
|
|
||||||
response.privilege = specificPrivilege ?? null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return response;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Export singleton instance
|
|
||||||
export const actingPositionService = new ActingPositionService();
|
|
||||||
|
|
@ -442,223 +442,6 @@ export class KeycloakAttributeService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if Keycloak user has empty/null empType attribute
|
|
||||||
* @param keycloakUserId - Keycloak user ID
|
|
||||||
* @returns Object with isEmpty flag and currentEmpType value
|
|
||||||
*/
|
|
||||||
async checkEmpTypeEmpty(keycloakUserId: string): Promise<{
|
|
||||||
isEmpty: boolean;
|
|
||||||
currentEmpType?: string;
|
|
||||||
}> {
|
|
||||||
try {
|
|
||||||
const user = await getUser(keycloakUserId);
|
|
||||||
|
|
||||||
if (!user || !user.attributes) {
|
|
||||||
return { isEmpty: true };
|
|
||||||
}
|
|
||||||
|
|
||||||
const empType = user.attributes.empType?.[0];
|
|
||||||
|
|
||||||
return {
|
|
||||||
isEmpty: !empType || empType.trim() === "",
|
|
||||||
currentEmpType: empType || "",
|
|
||||||
};
|
|
||||||
} catch (error) {
|
|
||||||
console.error(`[checkEmpTypeEmpty] Error for user ${keycloakUserId}:`, error);
|
|
||||||
return { isEmpty: true }; // Assume empty on error
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sync profiles with missing empType for a specific month
|
|
||||||
* @param options - Sync configuration
|
|
||||||
* @returns Sync results summary
|
|
||||||
*/
|
|
||||||
async syncMissingEmpTypeByMonth(options: {
|
|
||||||
month: string; // "YYYY-MM" format
|
|
||||||
profileType?: "PROFILE" | "PROFILE_EMPLOYEE";
|
|
||||||
dryRun?: boolean;
|
|
||||||
concurrency?: number;
|
|
||||||
rateLimit?: number;
|
|
||||||
}): Promise<{
|
|
||||||
month: string;
|
|
||||||
profileType: string;
|
|
||||||
totalProfiles: number;
|
|
||||||
profilesChecked: number;
|
|
||||||
missingEmpType: number;
|
|
||||||
syncSuccess: number;
|
|
||||||
syncFailed: number;
|
|
||||||
skipped: number;
|
|
||||||
executionTime: string;
|
|
||||||
dryRun: boolean;
|
|
||||||
}> {
|
|
||||||
const startTime = Date.now();
|
|
||||||
const {
|
|
||||||
month,
|
|
||||||
profileType = "PROFILE",
|
|
||||||
dryRun = false,
|
|
||||||
concurrency = 5,
|
|
||||||
rateLimit = 10,
|
|
||||||
} = options;
|
|
||||||
|
|
||||||
const result = {
|
|
||||||
month,
|
|
||||||
profileType,
|
|
||||||
totalProfiles: 0,
|
|
||||||
profilesChecked: 0,
|
|
||||||
missingEmpType: 0,
|
|
||||||
syncSuccess: 0,
|
|
||||||
syncFailed: 0,
|
|
||||||
skipped: 0,
|
|
||||||
executionTime: "",
|
|
||||||
dryRun,
|
|
||||||
};
|
|
||||||
|
|
||||||
let rateLimiter: RateLimiter | null = null;
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Parse month (YYYY-MM) to date range
|
|
||||||
const [year, monthNum] = month.split("-").map(Number);
|
|
||||||
const startDate = new Date(Date.UTC(year, monthNum - 1, 1, 0, 0, 0));
|
|
||||||
const endDate = new Date(Date.UTC(year, monthNum, 0, 23, 59, 59, 999));
|
|
||||||
|
|
||||||
console.log(
|
|
||||||
`[syncMissingEmpTypeByMonth] Processing ${profileType} for ${month} (${startDate.toISOString()} to ${endDate.toISOString()})`,
|
|
||||||
);
|
|
||||||
|
|
||||||
// Initialize rate limiter if rate limiting is enabled
|
|
||||||
if (rateLimit && rateLimit > 0) {
|
|
||||||
rateLimiter = new RateLimiter(rateLimit);
|
|
||||||
console.log(`[syncMissingEmpTypeByMonth] Rate limiting enabled: ${rateLimit} requests/second`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Select repository based on profile type
|
|
||||||
const repo =
|
|
||||||
profileType === "PROFILE" ? this.profileRepo : this.profileEmployeeRepo;
|
|
||||||
|
|
||||||
// Query profiles updated within the month
|
|
||||||
const profiles = await repo
|
|
||||||
.createQueryBuilder("p")
|
|
||||||
.where("p.keycloak IS NOT NULL")
|
|
||||||
.andWhere("p.keycloak != :empty", { empty: "" })
|
|
||||||
.andWhere("p.lastUpdatedAt BETWEEN :start AND :end", {
|
|
||||||
start: startDate,
|
|
||||||
end: endDate,
|
|
||||||
})
|
|
||||||
.orderBy("p.lastUpdatedAt", "ASC")
|
|
||||||
.getMany();
|
|
||||||
|
|
||||||
result.totalProfiles = profiles.length;
|
|
||||||
console.log(`[syncMissingEmpTypeByMonth] Found ${profiles.length} profiles to check`);
|
|
||||||
|
|
||||||
if (profiles.length === 0) {
|
|
||||||
result.executionTime = `${((Date.now() - startTime) / 1000).toFixed(2)}s`;
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Process profiles in parallel with concurrency limit
|
|
||||||
for (let i = 0; i < profiles.length; i += concurrency) {
|
|
||||||
const batch = profiles.slice(i, i + concurrency);
|
|
||||||
|
|
||||||
await Promise.all(
|
|
||||||
batch.map(async (profile) => {
|
|
||||||
// Apply rate limiting if enabled
|
|
||||||
if (rateLimiter) {
|
|
||||||
await rateLimiter.throttle();
|
|
||||||
}
|
|
||||||
|
|
||||||
const keycloakUserId = profile.keycloak;
|
|
||||||
if (!keycloakUserId) {
|
|
||||||
return {
|
|
||||||
profileId: profile.id,
|
|
||||||
status: "skipped" as const,
|
|
||||||
reason: "No keycloak ID",
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Check if empType is empty in Keycloak
|
|
||||||
const { isEmpty, currentEmpType } =
|
|
||||||
await this.checkEmpTypeEmpty(keycloakUserId);
|
|
||||||
|
|
||||||
result.profilesChecked++;
|
|
||||||
|
|
||||||
if (!isEmpty) {
|
|
||||||
result.skipped++;
|
|
||||||
return {
|
|
||||||
profileId: profile.id,
|
|
||||||
status: "skipped" as const,
|
|
||||||
reason: "empType already exists",
|
|
||||||
empType: currentEmpType,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
result.missingEmpType++;
|
|
||||||
|
|
||||||
if (dryRun) {
|
|
||||||
return {
|
|
||||||
profileId: profile.id,
|
|
||||||
status: "skipped" as const,
|
|
||||||
reason: "dry run",
|
|
||||||
wouldSync: true,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sync the profile
|
|
||||||
const success = await withRetry(
|
|
||||||
async () =>
|
|
||||||
this.syncOnOrganizationChange(profile.id, profileType),
|
|
||||||
3, // maxRetries
|
|
||||||
1000, // baseDelay
|
|
||||||
);
|
|
||||||
|
|
||||||
if (success) {
|
|
||||||
result.syncSuccess++;
|
|
||||||
return {
|
|
||||||
profileId: profile.id,
|
|
||||||
status: "synced" as const,
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
result.syncFailed++;
|
|
||||||
return {
|
|
||||||
profileId: profile.id,
|
|
||||||
status: "failed" as const,
|
|
||||||
reason: "Sync returned false",
|
|
||||||
};
|
|
||||||
}
|
|
||||||
} catch (error: any) {
|
|
||||||
result.syncFailed++;
|
|
||||||
return {
|
|
||||||
profileId: profile.id,
|
|
||||||
status: "failed" as const,
|
|
||||||
reason: error.message || "Unknown error",
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
// Log progress every 50 profiles
|
|
||||||
const completed = Math.min(i + concurrency, profiles.length);
|
|
||||||
if (completed % 50 === 0 || completed === profiles.length) {
|
|
||||||
console.log(
|
|
||||||
`[syncMissingEmpTypeByMonth] Progress: ${completed}/${profiles.length} profiles processed`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
result.executionTime = `${((Date.now() - startTime) / 1000).toFixed(2)}s`;
|
|
||||||
console.log(
|
|
||||||
`[syncMissingEmpTypeByMonth] Completed: total=${result.totalProfiles}, checked=${result.profilesChecked}, missing=${result.missingEmpType}, synced=${result.syncSuccess}, failed=${result.syncFailed}, skipped=${result.skipped}, elapsed=${result.executionTime}`,
|
|
||||||
);
|
|
||||||
} catch (error) {
|
|
||||||
console.error("[syncMissingEmpTypeByMonth] Error:", error);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Clear org DNA attributes in Keycloak for given profiles
|
* Clear org DNA attributes in Keycloak for given profiles
|
||||||
* Sets all org DNA fields to empty strings
|
* Sets all org DNA fields to empty strings
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@ import { AppDataSource } from "../database/data-source";
|
||||||
import { Command } from "../entities/Command";
|
import { Command } from "../entities/Command";
|
||||||
import { chunkArray, commandTypePath } from "../interfaces/utils";
|
import { chunkArray, commandTypePath } from "../interfaces/utils";
|
||||||
import CallAPI from "../interfaces/call-api";
|
import CallAPI from "../interfaces/call-api";
|
||||||
import { getPosMasterNo, getOrgFullName } from "../utils/org-formatting";
|
|
||||||
import HttpError from "../interfaces/http-error";
|
import HttpError from "../interfaces/http-error";
|
||||||
import HttpStatusCode from "../interfaces/http-status";
|
import HttpStatusCode from "../interfaces/http-status";
|
||||||
import { PosMaster } from "../entities/PosMaster";
|
import { PosMaster } from "../entities/PosMaster";
|
||||||
|
|
@ -652,17 +651,12 @@ async function handler_org(msg: amqp.ConsumeMessage): Promise<boolean> {
|
||||||
await posMasterAssignRepository.save(newAssigns);
|
await posMasterAssignRepository.save(newAssigns);
|
||||||
}
|
}
|
||||||
|
|
||||||
// อัพเดท org และ posMasterNo ตลอดไม่ต้องดัก isSit
|
// ถ้าไม่ใช่ตำแหน่งนั่งทับ (isSit = false) ถึงจะอัพเดทตำแหน่งในทะเบียนประวัติ
|
||||||
if (item.next_holderId != null) {
|
if (item.next_holderId != null && !item.isSit) {
|
||||||
const profile = await repoProfile.findOne({
|
const profile = await repoProfile.findOne({
|
||||||
where: { id: item.next_holderId == null ? "" : item.next_holderId },
|
where: { id: item.next_holderId == null ? "" : item.next_holderId },
|
||||||
});
|
});
|
||||||
if (profile != null) {
|
if (profile != null && item.positions.length > 0) {
|
||||||
profile.posMasterNo = getPosMasterNo(item) ?? _null;
|
|
||||||
profile.org = getOrgFullName(item) ?? _null;
|
|
||||||
|
|
||||||
// ถ้าไม่ใช่ตำแหน่งนั่งทับ (isSit = false) ถึงจะอัพเดทตำแหน่งในทะเบียนประวัติ
|
|
||||||
if (!item.isSit && item.positions.length > 0) {
|
|
||||||
let position = await item.positions.find((x) => x.positionIsSelected == true);
|
let position = await item.positions.find((x) => x.positionIsSelected == true);
|
||||||
if (position == null) {
|
if (position == null) {
|
||||||
position = await item.positions.find((x) => x.posLevelId == profile?.posLevelId);
|
position = await item.positions.find((x) => x.posLevelId == profile?.posLevelId);
|
||||||
|
|
@ -674,11 +668,6 @@ async function handler_org(msg: amqp.ConsumeMessage): Promise<boolean> {
|
||||||
profile.posLevelId = position?.posLevelId ?? _null;
|
profile.posLevelId = position?.posLevelId ?? _null;
|
||||||
profile.posTypeId = position?.posTypeId ?? _null;
|
profile.posTypeId = position?.posTypeId ?? _null;
|
||||||
profile.position = position?.positionName ?? _null;
|
profile.position = position?.positionName ?? _null;
|
||||||
profile.positionField = position?.positionField ?? _null;
|
|
||||||
profile.posExecutive = position?.posExecutive?.posExecutiveName ?? _null;
|
|
||||||
profile.positionArea = position?.positionArea ?? _null;
|
|
||||||
profile.positionExecutiveField = position?.positionExecutiveField ?? _null;
|
|
||||||
}
|
|
||||||
await repoProfile.save(profile);
|
await repoProfile.save(profile);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -68,47 +68,3 @@ export function filterPosMasters(
|
||||||
): PosMaster[] {
|
): PosMaster[] {
|
||||||
return posMasters.filter((x) => x[childLevelIdKey] == null && x.isDirector === true);
|
return posMasters.filter((x) => x[childLevelIdKey] == null && x.isDirector === true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* สร้าง orgShortName จาก posMaster (ต้อง load org relations มาก่อน)
|
|
||||||
*/
|
|
||||||
export function getOrgShortName(posMaster: PosMaster): string {
|
|
||||||
if (posMaster.orgChild1Id === null) {
|
|
||||||
return posMaster.orgRoot?.orgRootShortName ?? "";
|
|
||||||
} else if (posMaster.orgChild2Id === null) {
|
|
||||||
return posMaster.orgChild1?.orgChild1ShortName ?? "";
|
|
||||||
} else if (posMaster.orgChild3Id === null) {
|
|
||||||
return posMaster.orgChild2?.orgChild2ShortName ?? "";
|
|
||||||
} else if (posMaster.orgChild4Id === null) {
|
|
||||||
return posMaster.orgChild3?.orgChild3ShortName ?? "";
|
|
||||||
} else {
|
|
||||||
return posMaster.orgChild4?.orgChild4ShortName ?? "";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* สร้างชื่อสังกัดเต็ม จาก posMaster (join ด้วย \n)
|
|
||||||
*/
|
|
||||||
export function getOrgFullName(posMaster: PosMaster): string {
|
|
||||||
const parts = [
|
|
||||||
posMaster.orgChild4?.orgChild4Name,
|
|
||||||
posMaster.orgChild3?.orgChild3Name,
|
|
||||||
posMaster.orgChild2?.orgChild2Name,
|
|
||||||
posMaster.orgChild1?.orgChild1Name,
|
|
||||||
posMaster.orgRoot?.orgRootName,
|
|
||||||
];
|
|
||||||
return parts.filter((part) => part !== undefined && part !== null).join("\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* สร้างเลขที่ตำแหน่ง เช่น "กทม. กบ.1234ช"
|
|
||||||
*/
|
|
||||||
export function getPosMasterNo(posMaster: PosMaster): string {
|
|
||||||
const orgShortName = getOrgShortName(posMaster);
|
|
||||||
const parts = [
|
|
||||||
posMaster.posMasterNoPrefix,
|
|
||||||
posMaster.posMasterNo,
|
|
||||||
posMaster.posMasterNoSuffix,
|
|
||||||
].filter((part) => part !== null && part !== undefined);
|
|
||||||
return `${orgShortName} ${parts.join('')}`;
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1,23 +0,0 @@
|
||||||
/**
|
|
||||||
* คำนวณอายุงานจากจำนวนวันรวม
|
|
||||||
* @param totalDays จำนวนวันรวม
|
|
||||||
* @returns { year, month, day } ปี เดือน วัน
|
|
||||||
*/
|
|
||||||
export function calculateTenure(totalDays: number) {
|
|
||||||
// 1. แปลงเป็น year เต็ม
|
|
||||||
const year = Math.floor(totalDays / 365.2524);
|
|
||||||
|
|
||||||
// 2. วันที่เหลือหลังหัก year ออก
|
|
||||||
const remainAfterYear = totalDays - year * 365.2524;
|
|
||||||
|
|
||||||
// 3. แปลงเป็น month เต็ม
|
|
||||||
const month = Math.floor(remainAfterYear / 30.4375);
|
|
||||||
|
|
||||||
// 4. วันที่เหลือหลังหัก month ออก
|
|
||||||
const remainAfterMonth = remainAfterYear - month * 30.4375;
|
|
||||||
|
|
||||||
// 5. ปัดลง เฉพาะวัน
|
|
||||||
const day = Math.floor(remainAfterMonth);
|
|
||||||
|
|
||||||
return { year, month, day };
|
|
||||||
}
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue