Compare commits

...
Sign in to create a new pull request.

21 commits
develop ... dev

Author SHA1 Message Date
28319f443f add api get profile keycloak/position-checkin
All checks were successful
Build & Deploy on Dev / build (push) Successful in 1m26s
2026-04-27 19:13:21 +07:00
8705d1abf5 update 2026-04-24 16:15:47 +07:00
2cbc6569e3 update script sql 2026-04-24 13:41:10 +07:00
b9b73ca994 rollback code cronjob time
All checks were successful
Build & Deploy on Dev / build (push) Successful in 1m27s
2026-04-24 13:07:26 +07:00
ec6b4a7ac8 fix calculate
All checks were successful
Build & Deploy on Dev / build (push) Successful in 1m22s
2026-04-24 12:19:17 +07:00
c348a10207 Merge branch 'develop' into refactor/cronjob-position
* develop:
  fixed#1568 แก้ไขรายการตำแหน่งติดเงื่อนไข
  fix bug
  update path sql script
  #231 และ #2438 checkpoint
2026-04-24 11:39:23 +07:00
b8ef607078 Merge branch 'develop' of github.com:Frappet/bma-ehr-organization into develop
* 'develop' of github.com:Frappet/bma-ehr-organization:
  fix bug
  update path sql script
  #231 และ #2438 checkpoint
2026-04-24 11:38:57 +07:00
5980c140f0 fixed#1568 แก้ไขรายการตำแหน่งติดเงื่อนไข 2026-04-24 11:38:45 +07:00
da4fd18e08 fix bug
All checks were successful
Build & Deploy on Dev / build (push) Successful in 1m22s
2026-04-24 11:07:55 +07:00
1d16f78132 update path sql script
All checks were successful
Build & Deploy on Dev / build (push) Successful in 1m24s
2026-04-24 10:31:42 +07:00
8f83ab781b fix.save batch insert 2026-04-24 10:28:30 +07:00
d46dd03eaf Merge branch 'develop' into adiDev 2026-04-24 09:26:03 +07:00
harid
8912e83227 api import profileSalaryTemp #1570
All checks were successful
Build & Deploy on Dev / build (push) Successful in 1m4s
& Fix Report KK1 #2439
2026-04-23 16:31:22 +07:00
adisak
194d79bf04 #231 และ #2438 checkpoint 2026-04-21 17:37:17 +07:00
7e3982a96d fixed calculate tenure (สูตรคำนวนอายุราชการจาก diff date)
All checks were successful
Build & Deploy on Dev / build (push) Successful in 1m6s
2026-04-20 18:20:20 +07:00
5e52206987 update
All checks were successful
Build & Deploy on Dev / build (push) Successful in 1m6s
2026-04-20 17:23:15 +07:00
f1c8ecf699 insert position to profile
All checks were successful
Build & Deploy on Dev / build (push) Successful in 1m8s
2026-04-20 16:01:38 +07:00
adisak
28b5408d5b #2427 and migration 2026-04-20 08:05:16 +07:00
harid
7f3408e2f5 API permission with acting positions
All checks were successful
Build & Deploy on Dev / build (push) Successful in 1m36s
2026-04-17 14:18:54 +07:00
99bd789702 fixed#230 noti เพิ่มลิ้งค์ไปหน้ารายละเอียดแก้ไขข้อมูล "ขอแก้ไขทะเบียนประวัติ"
All checks were successful
Build & Deploy on Dev / build (push) Successful in 1m37s
2026-04-17 14:00:00 +07:00
harid
e7a973b764 fix issues #2428 #2383
All checks were successful
Build & Deploy on Dev / build (push) Successful in 1m5s
2026-04-16 15:59:36 +07:00
20 changed files with 1628 additions and 513 deletions

View file

@ -0,0 +1,154 @@
-- =====================================================
-- 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;

View file

@ -48,6 +48,7 @@ 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";
@ -3660,6 +3661,7 @@ 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, "ไม่พบข้อมูลตำแหน่งนี้");
@ -3715,14 +3717,22 @@ 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;
@ -6876,7 +6886,7 @@ export class CommandController extends Controller {
where: { where: {
id: item.bodyPosition.posmasterId, id: item.bodyPosition.posmasterId,
}, },
relations: { orgRevision: true } relations: { orgRevision: true, orgRoot: true, orgChild1: true, orgChild2: true, orgChild3: true, orgChild4: true }
}); });
// เช็คว่า posMaster ที่หามาอยู่ในโครงสร้างปัจจุบันหรือไม่ // เช็คว่า posMaster ที่หามาอยู่ในโครงสร้างปัจจุบันหรือไม่
@ -6893,9 +6903,8 @@ export class CommandController extends Controller {
orgRevisionIsDraft: false orgRevisionIsDraft: false
} }
}, },
relations: { orgRevision: true } relations: { orgRevision: true, orgRoot: true, orgChild1: true, orgChild2: true, orgChild3: true, orgChild4: true }
}); }); }
}
if (posMaster == null) if (posMaster == null)
throw new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบข้อมูลตำแหน่งนี้"); throw new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบข้อมูลตำแหน่งนี้");
@ -6983,20 +6992,28 @@ 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 });
setLogDataDiff(req, { before, after: profile });
} }
await this.profileRepository.save(profile, { data: req });
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);

View file

@ -15,6 +15,7 @@ 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;
@ -88,7 +89,8 @@ 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}`);

View file

@ -1,4 +1,4 @@
import { Controller, Post, Route, Security, Tags, Request, UploadedFile } from "tsoa"; import { Controller, Post, Route, Security, Tags, Request, UploadedFile, Path } 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,6 +105,7 @@ 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")
@ -6815,4 +6816,502 @@ 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 });
}
} }

View file

@ -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 } from "../utils/org-formatting"; import { formatPosMaster, generateLabelName, filterPosMasters, getPosMasterNo, getOrgFullName } from "../utils/org-formatting";
@Route("api/v1/org") @Route("api/v1/org")
@Tags("Organization") @Tags("Organization")
@ -8933,13 +8933,25 @@ 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) {
profileUpdates.set(nextHolderId, { const existing = profileUpdates.get(nextHolderId) || {};
position: draftPos.positionName, existing.position = draftPos.positionName;
posTypeId: draftPos.posTypeId, existing.posTypeId = draftPos.posTypeId;
posLevelId: draftPos.posLevelId, existing.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 =

View file

@ -15,6 +15,7 @@ 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;
@ -254,6 +255,64 @@ 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)

View file

@ -39,6 +39,7 @@ 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";
@ -1256,7 +1257,15 @@ 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: ["positions", "orgRevision"], relations: [
"positions",
"orgRevision",
"orgRoot",
"orgChild1",
"orgChild2",
"orgChild3",
"orgChild4",
],
where: { id: id }, where: { id: id },
}); });
if (!posMaster) { if (!posMaster) {
@ -1451,6 +1460,17 @@ 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);
@ -1463,6 +1483,10 @@ 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);
} }
} }
@ -2387,16 +2411,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
@ -2427,26 +2451,27 @@ 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.orWhere( query
new Brackets((qb) => { .orWhere(
qb.andWhere( new Brackets((qb) => {
body.keyword != null && body.keyword != "" qb.andWhere(
? body.isAll == false body.keyword != null && body.keyword != ""
? searchShortName ? body.isAll == false
: `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}%'` ? searchShortName
: "1=1", : `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",
.andWhere(checkChildConditions) )
.andWhere(typeCondition) .andWhere(checkChildConditions)
.andWhere(revisionCondition); .andWhere(typeCondition)
}), .andWhere(revisionCondition);
) }),
)
.orWhere( .orWhere(
new Brackets((qb) => { new Brackets((qb) => {
qb.andWhere( qb.andWhere(
@ -2955,50 +2980,50 @@ export class PositionController extends Controller {
const type0LastPosMasterNo = const type0LastPosMasterNo =
requestBody.type == 0 requestBody.type == 0
? await this.posMasterRepository.find({ ? await this.posMasterRepository.find({
where: { where: {
orgRootId: requestBody.id, orgRootId: requestBody.id,
orgChild1Id: IsNull(), orgChild1Id: IsNull(),
}, },
}) })
: []; : [];
const type1LastPosMasterNo = const type1LastPosMasterNo =
requestBody.type == 1 requestBody.type == 1
? await this.posMasterRepository.find({ ? await this.posMasterRepository.find({
where: { where: {
orgChild1Id: requestBody.id, orgChild1Id: requestBody.id,
orgChild2Id: IsNull(), orgChild2Id: IsNull(),
}, },
}) })
: []; : [];
const type2LastPosMasterNo = const type2LastPosMasterNo =
requestBody.type == 2 requestBody.type == 2
? await this.posMasterRepository.find({ ? await this.posMasterRepository.find({
where: { where: {
orgChild2Id: requestBody.id, orgChild2Id: requestBody.id,
orgChild3Id: IsNull(), orgChild3Id: IsNull(),
}, },
}) })
: []; : [];
const type3LastPosMasterNo = const type3LastPosMasterNo =
requestBody.type == 3 requestBody.type == 3
? await this.posMasterRepository.find({ ? await this.posMasterRepository.find({
where: { where: {
orgChild3Id: requestBody.id, orgChild3Id: requestBody.id,
orgChild4Id: IsNull(), orgChild4Id: IsNull(),
}, },
}) })
: []; : [];
const type4LastPosMasterNo = const type4LastPosMasterNo =
requestBody.type == 4 requestBody.type == 4
? await this.posMasterRepository.find({ ? await this.posMasterRepository.find({
where: { where: {
orgChild4Id: requestBody.id, orgChild4Id: requestBody.id,
}, },
}) })
: []; : [];
const allLastPosMasterNo = [ const allLastPosMasterNo = [
@ -3793,7 +3818,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"], relations: ["positions", "orgRoot", "orgChild1", "orgChild2", "orgChild3", "orgChild4"],
}); });
if (!dataMaster) { if (!dataMaster) {
throw new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบข้อมูลตำแหน่งนี้"); throw new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบข้อมูลตำแหน่งนี้");
@ -3825,16 +3850,24 @@ 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;
await this.profileRepository.save(_profile); _profile.positionField = _position.positionField ?? _null;
setLogDataDiff(request, { before, after: _profile }); _profile.posExecutive = _position.posExecutive?.posExecutiveName ?? _null;
_profile.positionArea = _position.positionArea ?? _null;
_profile.positionExecutiveField = _position.positionExecutiveField ?? _null;
} }
await this.profileRepository.save(_profile);
setLogDataDiff(request, { before, after: _profile });
} }
} }
dataMaster.current_holderId = requestBody.profileId; dataMaster.current_holderId = requestBody.profileId;
@ -5169,9 +5202,9 @@ export class PositionController extends Controller {
} }
/** /**
* API * API
* *
* @summary ORG_070 - (ADMIN) #56 * @summary
* *
*/ */
@Post("master/position-condition") @Post("master/position-condition")
@ -5182,7 +5215,7 @@ export class PositionController extends Controller {
id: string; id: string;
revisionId: string; revisionId: string;
type: number; type: number;
isAll: boolean; isAll: boolean; // true คือเลือกเฉพาะตำแหน่งติดเงื่อนไข / false คือเลือกตำแหน่งทั้งหมด
page: number; page: number;
pageSize: number; pageSize: number;
keyword?: string; keyword?: string;
@ -5202,7 +5235,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);
@ -5234,46 +5267,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 }),
@ -5346,7 +5379,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}%`) })),
current_holderId: IsNull(), ...(!body.isAll && { isCondition: true }),
}, },
]; ];
let [posMaster, total] = await AppDataSource.getRepository(PosMaster) let [posMaster, total] = await AppDataSource.getRepository(PosMaster)
@ -5415,15 +5448,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 != ""
? body.isAll == false ? `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}%'`
? 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);
.andWhere({ current_holderId: IsNull() }); if (!body.isAll) {
qb.andWhere({ isCondition: true });
}
}), }),
) )
.orWhere( .orWhere(
@ -5433,8 +5466,10 @@ export class PositionController extends Controller {
) )
.andWhere(checkChildConditions) .andWhere(checkChildConditions)
.andWhere(typeCondition) .andWhere(typeCondition)
.andWhere(revisionCondition) .andWhere(revisionCondition);
.andWhere({ current_holderId: IsNull() }); if (!body.isAll) {
qb.andWhere({ isCondition: true });
}
}), }),
) )
.orderBy("orgRoot.orgRootOrder", "ASC") .orderBy("orgRoot.orgRootOrder", "ASC")

View file

@ -1679,35 +1679,84 @@ 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" },
// });
// if (retire_raw) { // commandCode ที่ถือว่าออกจากราชการ
// const startDate = retire_raw.commandDateAffect; const retireCommandCodes = ["12", "15", "16"];
// // คำนวณจำนวนวันจากวันพ้นสภาพถึงปัจจุบัน // ดึงข้อมูล profileSalary ทั้งหมดเพื่อหาประวัติพ้นจากราชการ
// let daysCount = 0; const salaries = await this.salaryRepo.find({
// if (startDate) { where: { profileId: id },
// const start = new Date(startDate); order: { order: "ASC" },
// daysCount = Math.ceil((currentDate.getTime() - start.getTime()) / (1000 * 60 * 60 * 24)); });
// }
// const startDateStr = startDate // มีคำสั่งพ้นราชการหรือไม่
// ? Extension.ToThaiNumber(Extension.ToThaiFullDate2(startDate)) if (
// : "-"; 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({ // วนลูปหาคู่ของ "ออกราชการ" และ "กลับเข้าราชการ"
// date: `${startDateStr}`, for (let i = 0; i < uniqueSalaries.length; i++) {
// detail: retire_raw.commandName ?? "-", const current = uniqueSalaries[i];
// 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) {
@ -11965,4 +12014,90 @@ 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);
}
} }

View file

@ -1950,35 +1950,78 @@ 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" },
// });
// if (retire_raw) { // commandCode ที่ถือว่าออกจากราชการ
// const startDate = retire_raw.commandDateAffect; const retireCommandCodes = ["12", "15", "16"];
// // คำนวณจำนวนวันจากวันพ้นสภาพถึงปัจจุบัน // ดึงข้อมูล profileSalary ทั้งหมดเพื่อหาประวัติพ้นจากราชการ
// let daysCount = 0; const salaries = await this.salaryRepo.find({
// if (startDate) { where: { profileEmployeeId: id },
// const start = new Date(startDate); order: { order: "ASC" },
// daysCount = Math.ceil((currentDate.getTime() - start.getTime()) / (1000 * 60 * 60 * 24)); });
// }
// const startDateStr = startDate // มีคำสั่งพ้นราชการหรือไม่
// ? Extension.ToThaiNumber(Extension.ToThaiFullDate2(startDate)) if (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({ // วนลูปหาคู่ของ "ออกราชการ" และ "กลับเข้าราชการ"
// date: `${startDateStr} - ปัจจุบัน`, for (let i = 0; i < uniqueSalaries.length; i++) {
// detail: retire_raw.commandName ?? "-", const current = uniqueSalaries[i];
// 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) {

View file

@ -6,8 +6,6 @@ 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,
@ -15,7 +13,6 @@ 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")
@ -23,9 +20,6 @@ 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
@ -33,13 +27,6 @@ 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 ดังกล่าว");
@ -51,79 +38,19 @@ export class ProfileGovernmentHistoryController extends Controller {
posLevel: true, posLevel: true,
}, },
}); });
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,
},
});
if (!record) throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูล"); if (!record) throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูล");
const fullNameParts = [
posMaster == null || posMaster.orgChild4 == null ? null : posMaster.orgChild4.orgChild4Name, // ดึงข้อมูลจาก profile ที่เก็บไว้แล้ว
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: org, //สังกัด org: record.org ?? null, //สังกัด
positionField: position == null || posMaster?.isSit ? null : position.positionField, //สายงาน positionField: record.positionField ?? null, //สายงาน
position: record.position, //ตำแหน่ง position: record.position, //ตำแหน่ง
posLevel: record.posLevel == null ? null : record.posLevel.posLevelName, //ระดับ posLevel: record.posLevel == null ? null : record.posLevel.posLevelName, //ระดับ
posMasterNo: posMaster == null ? null : `${orgShortName} ${posMaster.posMasterNo}`, //เลขที่ตำแหน่ง posMasterNo: record.posMasterNo ?? null, //เลขที่ตำแหน่ง
posType: record.posType == null ? null : record.posType.posTypeName, //ประเภท posType: record.posType == null ? null : record.posType.posTypeName, //ประเภท
posExecutive: posExecutive: record.posExecutive ?? null, //ตำแหน่งทางการบริหาร
position == null || position.posExecutive == null || posMaster?.isSit positionArea: record.positionArea ?? null, //ด้าน/สาขา
? null positionExecutiveField: record.positionExecutiveField ?? 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),
@ -135,10 +62,10 @@ export class ProfileGovernmentHistoryController extends Controller {
govAgePlus: record.govAgePlus, govAgePlus: record.govAgePlus,
reasonSameDate: record.reasonSameDate, reasonSameDate: record.reasonSameDate,
}; };
return new HttpSuccess(data); return new HttpSuccess(data);
} }
/** /**
* *
* @summary * @summary
@ -150,25 +77,17 @@ export class ProfileGovernmentHistoryController extends Controller {
let _workflow = await new permission().Workflow(req, profileId, "SYS_REGISTRY_OFFICER"); let _workflow = await new permission().Workflow(req, profileId, "SYS_REGISTRY_OFFICER");
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 },
relations: ["posType", "posLevel"], relations: ["posType", "posLevel"],
}); });
if (!record) { if (!record) {
throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูล profile"); throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูล profile");
} }
// ค้นหา profileSalary แยกต่างหาก // ค้นหา profileSalary แยกต่างหาก
const profileWithSalary = await this.profileRepo.findOne({ const profileWithSalary = await this.profileRepo.findOne({
where: { where: {
@ -201,70 +120,13 @@ 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
@ -288,27 +150,23 @@ 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 ? org : orgLeave, //สังกัด org: record?.isLeave == false ? (record.org ?? null) : orgLeave, //สังกัด
positionField: position == null || posMaster?.isSit ? null : position.positionField, //สายงาน positionField: record.positionField ?? null, //สายงาน
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
? posMaster == null ? record.posMasterNo ?? 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: posExecutive: record.posExecutive ?? null, //ตำแหน่งทางการบริหาร
position == null || position.posExecutive == null || posMaster?.isSit positionArea: record.positionArea ?? null, //ด้าน/สาขา
? null positionExecutiveField: record.positionExecutiveField ?? 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),
@ -320,30 +178,22 @@ export class ProfileGovernmentHistoryController extends Controller {
govAgePlus: record?.govAgePlus, govAgePlus: record?.govAgePlus,
reasonSameDate: record?.reasonSameDate, reasonSameDate: record?.reasonSameDate,
}; };
return new HttpSuccess(data); return new HttpSuccess(data);
} }
@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 },
relations: ["posType", "posLevel"], relations: ["posType", "posLevel"],
}); });
if (!record) { if (!record) {
throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูล profile"); throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูล profile");
} }
// ค้นหา profileSalary แยกต่างหาก // ค้นหา profileSalary แยกต่างหาก
const profileWithSalary = await this.profileRepo.findOne({ const profileWithSalary = await this.profileRepo.findOne({
where: { where: {
@ -376,70 +226,13 @@ 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
@ -463,27 +256,23 @@ 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 ? org : orgLeave, //สังกัด org: record?.isLeave == false ? (record.org ?? null) : orgLeave, //สังกัด
positionField: position == null || posMaster?.isSit ? null : position.positionField, //สายงาน positionField: record.positionField ?? null, //สายงาน
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
? posMaster == null ? record.posMasterNo ?? 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: posExecutive: record.posExecutive ?? null, //ตำแหน่งทางการบริหาร
position == null || position.posExecutive == null || posMaster?.isSit positionArea: record.positionArea ?? null, //ด้าน/สาขา
? null positionExecutiveField: record.positionExecutiveField ?? 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),
@ -496,10 +285,10 @@ export class ProfileGovernmentHistoryController extends Controller {
reasonSameDate: record?.reasonSameDate, reasonSameDate: record?.reasonSameDate,
isLeave: record?.isLeave, isLeave: record?.isLeave,
}; };
return new HttpSuccess(data); return new HttpSuccess(data);
} }
/** /**
* *
* @summary by keycloak * @summary by keycloak
@ -517,7 +306,7 @@ export class ProfileGovernmentHistoryController extends Controller {
}); });
return new HttpSuccess(record); return new HttpSuccess(record);
} }
/** /**
* *
* @summary * @summary
@ -533,12 +322,12 @@ export class ProfileGovernmentHistoryController extends Controller {
order: { lastUpdatedAt: "DESC" }, order: { lastUpdatedAt: "DESC" },
where: { profileId: profileId }, where: { profileId: profileId },
}); });
// record.pop(); // record.pop();
return new HttpSuccess(record); return new HttpSuccess(record);
} }
/** /**
* *
* @summary * @summary
@ -554,14 +343,14 @@ export class ProfileGovernmentHistoryController extends Controller {
const record = await this.profileRepo.findOne({ const record = await this.profileRepo.findOne({
where: { id: profileId }, where: { id: profileId },
}); });
if (!record) throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูล"); if (!record) throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูล");
const before = structuredClone(record); const before = structuredClone(record);
const history = new ProfileGovernment(); const history = new ProfileGovernment();
Object.assign(record, body); Object.assign(record, body);
Object.assign(history, { ...record, id: undefined }); Object.assign(history, { ...record, id: undefined });
history.profileId = profileId; history.profileId = profileId;
record.lastUpdateUserId = req.user.sub; record.lastUpdateUserId = req.user.sub;
record.lastUpdateFullName = req.user.name; record.lastUpdateFullName = req.user.name;
@ -572,13 +361,14 @@ export class ProfileGovernmentHistoryController extends Controller {
history.createdFullName = req.user.name; history.createdFullName = req.user.name;
history.createdAt = new Date(); history.createdAt = new Date();
history.lastUpdatedAt = new Date(); history.lastUpdatedAt = new Date();
await Promise.all([ await Promise.all([
this.profileRepo.save(record, { data: req }), this.profileRepo.save(record, { data: req }),
setLogDataDiff(req, { before, after: record }), setLogDataDiff(req, { before, after: record }),
this.govRepo.save(history, { data: req }), this.govRepo.save(history, { data: req }),
]); ]);
return new HttpSuccess(); return new HttpSuccess();
} }
} }

View file

@ -23,6 +23,7 @@ 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";
@ -65,10 +66,12 @@ 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");
let _currentDate = CURRENT_DATE[0].today; const baseCurrentDate = CURRENT_DATE[0].today;
for await (const x of profile) { for await (const x of profile) {
if (x.isLeave) { // Use leave date if available and valid, otherwise use current date
_currentDate = x.leaveDate ? Extension.toDateOnlyString(x.leaveDate) : _currentDate; let _currentDate = baseCurrentDate;
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,
@ -92,21 +95,18 @@ 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: (calDayDiff.days_diff / 365.2524).toFixed(4), Years: year,
// Months: ((calDayDiff.days_diff / 30.4375) % 12).toFixed(4), Months: month,
// Days: (calDayDiff.days_diff % 30.4375).toFixed(4), Days: day,
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,11 +115,13 @@ 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");
let _currentDate = CURRENT_DATE[0].today; const baseCurrentDate = 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) {
if (x?.isLeave) { // Use leave date if available and valid, otherwise use current date
_currentDate = x.leaveDate ? Extension.toDateOnlyString(x.leaveDate) : _currentDate; let _currentDate = baseCurrentDate;
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,
@ -143,21 +145,18 @@ 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: (calDayDiff.days_diff / 365.2524).toFixed(4), Years: year,
// Months: ((calDayDiff.days_diff / 30.4375) % 12).toFixed(4), Months: month,
// Days: (calDayDiff.days_diff % 30.4375).toFixed(4), Days: day,
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();
} }
@ -167,10 +166,12 @@ 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");
let _currentDate = CURRENT_DATE[0].today; const baseCurrentDate = CURRENT_DATE[0].today;
for await (const x of profile) { for await (const x of profile) {
if (x?.isLeave) { // Use leave date if available and valid, otherwise use current date
_currentDate = x.leaveDate ? Extension.toDateOnlyString(x.leaveDate) : _currentDate; let _currentDate = baseCurrentDate;
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,
@ -202,20 +203,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 : (calDayDiff.days_diff / 365.2524).toFixed(4), Years: x.posLevel == null ? 0 : year.toFixed(4),
Months: x.posLevel == null ? 0 : ((calDayDiff.days_diff / 30.4375) % 12).toFixed(4), Months: x.posLevel == null ? 0 : month.toFixed(4),
Days: x.posLevel == null ? 0 : (calDayDiff.days_diff % 30.4375).toFixed(4), Days: x.posLevel == null ? 0 : day.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();
} }
@ -225,10 +226,12 @@ 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");
let _currentDate = CURRENT_DATE[0].today; const baseCurrentDate = CURRENT_DATE[0].today;
for await (const x of profile) { for await (const x of profile) {
if (x?.isLeave) { // Use leave date if available and valid, otherwise use current date
_currentDate = x.leaveDate ? Extension.toDateOnlyString(x.leaveDate) : _currentDate; let _currentDate = baseCurrentDate;
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,
@ -260,26 +263,27 @@ 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 : (calDayDiff.days_diff / 365.2524).toFixed(4), Years: x.posLevel == null ? 0 : year.toFixed(4),
Months: x.posLevel == null ? 0 : ((calDayDiff.days_diff / 30.4375) % 12).toFixed(4), Months: x.posLevel == null ? 0 : month.toFixed(4),
Days: x.posLevel == null ? 0 : (calDayDiff.days_diff % 30.4375).toFixed(4), Days: x.posLevel == null ? 0 : day.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({
@ -290,10 +294,12 @@ 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");
let _currentDate = CURRENT_DATE[0].today; const baseCurrentDate = CURRENT_DATE[0].today;
for await (const x of profile) { for await (const x of profile) {
if (x?.isLeave) { // Use leave date if available and valid, otherwise use current date
_currentDate = x.leaveDate ? Extension.toDateOnlyString(x.leaveDate) : _currentDate; let _currentDate = baseCurrentDate;
if (x?.isLeave && x.leaveDate) {
_currentDate = Extension.toDateOnlyString(x.leaveDate);
} }
const position = await this.positionRepo.findOne({ const position = await this.positionRepo.findOne({
where: { where: {
@ -331,16 +337,18 @@ 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: (calDayDiff.days_diff / 365.2524).toFixed(4), Years: year.toFixed(4),
Months: ((calDayDiff.days_diff / 30.4375) % 12).toFixed(4), Months: month.toFixed(4),
Days: (calDayDiff.days_diff % 30.4375).toFixed(4), Days: day.toFixed(4),
}; };
await this.positionExecutiveOfficerRepo.save(mapData); data.push(mapData);
} }
await this.positionExecutiveOfficerRepo.save(data);
return new HttpSuccess(); return new HttpSuccess();
} }
@ -602,10 +610,10 @@ export class ProfileSalaryController extends Controller {
acc.push(existing); acc.push(existing);
} }
// Recalculate year, month, and day 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;
}, },
@ -641,10 +649,10 @@ export class ProfileSalaryController extends Controller {
acc.push(existing); acc.push(existing);
} }
// Recalculate year, month, and day 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;
}, },
@ -675,10 +683,10 @@ export class ProfileSalaryController extends Controller {
acc.push(existing); acc.push(existing);
} }
// Recalculate year, month, and day 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;
}, },
@ -739,10 +747,10 @@ export class ProfileSalaryController extends Controller {
acc.push(existing); acc.push(existing);
} }
// Recalculate year, month, and day 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;
}, },
@ -782,10 +790,10 @@ export class ProfileSalaryController extends Controller {
acc.push(existing); acc.push(existing);
} }
// Recalculate year, month, and day 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;
}, },
@ -819,10 +827,10 @@ export class ProfileSalaryController extends Controller {
acc.push(existing); acc.push(existing);
} }
// Recalculate year, month, and day 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;
}, },

View file

@ -27,6 +27,7 @@ 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";
@ -175,9 +176,10 @@ export class ProfileSalaryEmployeeController extends Controller {
acc.push(existing); acc.push(existing);
} }
existing.year = Math.floor(existing.days / 365.2524); const { year, month, day } = calculateTenure(existing.days);
existing.month = Math.floor((existing.days / 30.4375) % 12); existing.year = year;
existing.day = Math.ceil(existing.days % 30.4375); existing.month = month;
existing.day = day;
return acc; return acc;
}, },
@ -211,9 +213,10 @@ export class ProfileSalaryEmployeeController extends Controller {
acc.push(existing); acc.push(existing);
} }
existing.year = Math.floor(existing.days / 365.2524); const { year, month, day } = calculateTenure(existing.days);
existing.month = Math.floor((existing.days / 30.4375) % 12); existing.year = year;
existing.day = Math.ceil(existing.days % 30.4375); existing.month = month;
existing.day = day;
return acc; return acc;
}, },
@ -266,9 +269,10 @@ export class ProfileSalaryEmployeeController extends Controller {
acc.push(existing); acc.push(existing);
} }
existing.year = Math.floor(existing.days / 365.2524); const { year, month, day } = calculateTenure(existing.days);
existing.month = Math.floor((existing.days / 30.4375) % 12); existing.year = year;
existing.day = Math.ceil(existing.days % 30.4375); existing.month = month;
existing.day = day;
return acc; return acc;
}, },
@ -302,9 +306,10 @@ export class ProfileSalaryEmployeeController extends Controller {
acc.push(existing); acc.push(existing);
} }
existing.year = Math.floor(existing.days / 365.2524); const { year, month, day } = calculateTenure(existing.days);
existing.month = Math.floor((existing.days / 30.4375) % 12); existing.year = year;
existing.day = Math.ceil(existing.days % 30.4375); existing.month = month;
existing.day = day;
return acc; return acc;
}, },

View file

@ -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}`, posNo: `${x.posNoAbb ?? ""} ${x.posNo ?? ""}`.trim(),
position: x.positionName, position: x.positionName,
commandId: x.commandId, commandId: x.commandId,
refCommandNo: `${x.commandNo}/${x.commandYear}`, refCommandNo: [x.commandNo, x.commandYear].filter(Boolean).join("/") || undefined,
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}`, commandNo: [x.commandNo, x.commandYear].filter(Boolean).join("/") || undefined,
commandName: x.commandName ?? "ให้ช่วยราชการ", commandName: x.commandName ?? "ให้ช่วยราชการ",
refCommandDate: x.commandDateSign, refCommandDate: x.commandDateSign,
refId: x.refId, refId: x.refId,

View file

@ -237,11 +237,18 @@ 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

View file

@ -140,6 +140,54 @@ 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,

View file

@ -0,0 +1,37 @@
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\``);
}
}

View file

@ -0,0 +1,186 @@
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();

View file

@ -3,6 +3,7 @@ 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";
@ -651,23 +652,33 @@ async function handler_org(msg: amqp.ConsumeMessage): Promise<boolean> {
await posMasterAssignRepository.save(newAssigns); await posMasterAssignRepository.save(newAssigns);
} }
// ถ้าไม่ใช่ตำแหน่งนั่งทับ (isSit = false) ถึงจะอัพเดทตำแหน่งในทะเบียนประวัติ // อัพเดท org และ posMasterNo ตลอดไม่ต้องดัก isSit
if (item.next_holderId != null && !item.isSit) { if (item.next_holderId != null) {
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 && item.positions.length > 0) { if (profile != null) {
let position = await item.positions.find((x) => x.positionIsSelected == true); profile.posMasterNo = getPosMasterNo(item) ?? _null;
if (position == null) { profile.org = getOrgFullName(item) ?? _null;
position = await item.positions.find((x) => x.posLevelId == profile?.posLevelId);
if (position == null) {
position = await item.positions.sort((a, b) => a.orderNo - b.orderNo)[0];
}
}
profile.posLevelId = position?.posLevelId ?? _null; // ถ้าไม่ใช่ตำแหน่งนั่งทับ (isSit = false) ถึงจะอัพเดทตำแหน่งในทะเบียนประวัติ
profile.posTypeId = position?.posTypeId ?? _null; if (!item.isSit && item.positions.length > 0) {
profile.position = position?.positionName ?? _null; let position = await item.positions.find((x) => x.positionIsSelected == true);
if (position == null) {
position = await item.positions.find((x) => x.posLevelId == profile?.posLevelId);
if (position == null) {
position = await item.positions.sort((a, b) => a.orderNo - b.orderNo)[0];
}
}
profile.posLevelId = position?.posLevelId ?? _null;
profile.posTypeId = position?.posTypeId ?? _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);
} }
} }

View file

@ -68,3 +68,47 @@ 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('')}`;
}

23
src/utils/tenure.ts Normal file
View file

@ -0,0 +1,23 @@
/**
*
* @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 };
}