diff --git a/docs/migrations/fix_GetProfileEmployeeSalaryLevel_calendar_arithmetic.sql b/docs/migrations/fix_GetProfileEmployeeSalaryLevel_calendar_arithmetic.sql new file mode 100644 index 00000000..bca30538 --- /dev/null +++ b/docs/migrations/fix_GetProfileEmployeeSalaryLevel_calendar_arithmetic.sql @@ -0,0 +1,140 @@ +-- ==================================================================== +-- Fix GetProfileEmployeeSalaryLevel to use calendar arithmetic +-- This changes from fixed formulas to actual calendar arithmetic, +-- matching calculateGovAge and GetProfileSalaryLevel behavior +-- ==================================================================== + +DELIMITER $$ + +DROP PROCEDURE IF EXISTS `GetProfileEmployeeSalaryLevel`$$ + +CREATE DEFINER=`root`@`%` PROCEDURE `GetProfileEmployeeSalaryLevel`( + IN personId VARCHAR(36), + IN _date DATE +) +BEGIN +WITH ordered AS ( + SELECT * + FROM profileSalary + WHERE profileEmployeeId = personId + AND commandCode IN ('0','1','2','3','4','8','9','10','11','12','13','14','15','16','20') +), +work_session AS ( + SELECT *, + COALESCE( + SUM(CASE WHEN commandCode IN (12,15,16) THEN 1 ELSE 0 END) + OVER (ORDER BY commandDateAffect, commandDateSign + ROWS BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING), + 0) AS sessionId + FROM ordered +), +session_end AS ( + SELECT sessionId, MAX(commandDateAffect) AS sessionEndDate + FROM work_session + GROUP BY sessionId +), +level_change AS ( + SELECT *, + CASE + WHEN LAG(positionCee) OVER (ORDER BY commandDateAffect, commandDateSign) <=> positionCee + AND LAG(positionType) OVER (ORDER BY commandDateAffect, commandDateSign) <=> positionType + AND LAG(positionLevel) OVER (ORDER BY commandDateAffect, commandDateSign) <=> positionLevel + AND LAG(sessionId) OVER (ORDER BY commandDateAffect, commandDateSign) = sessionId + THEN 0 + ELSE 1 + END AS isNewLevel + FROM work_session +), +level_group AS ( + SELECT *, + SUM(isNewLevel) OVER (ORDER BY commandDateAffect, commandDateSign) AS levelGroup + FROM level_change +), +first_rows AS ( + SELECT * FROM ( + SELECT *, + ROW_NUMBER() OVER (PARTITION BY levelGroup ORDER BY commandDateAffect, commandDateSign) AS rnLevel + FROM level_group + ) t WHERE rnLevel = 1 +), +rows_with_duration AS ( + SELECT + fr.*, + CASE + WHEN LEAD(fr.commandDateAffect) OVER (ORDER BY fr.commandDateAffect, fr.commandDateSign) IS NULL + THEN NULL + WHEN LEAD(fr.sessionId) OVER (ORDER BY fr.commandDateAffect, fr.commandDateSign) <> fr.sessionId + THEN TIMESTAMPDIFF(DAY, fr.commandDateAffect, se.sessionEndDate) + 1 + ELSE + TIMESTAMPDIFF(DAY, fr.commandDateAffect, + LEAD(fr.commandDateAffect) OVER (ORDER BY fr.commandDateAffect, fr.commandDateSign)) + END AS duration_days + FROM first_rows fr + LEFT JOIN session_end se ON se.sessionId = fr.sessionId +), +resultWithDiff AS ( + SELECT + *, + LAG(duration_days) OVER (ORDER BY commandDateAffect, commandDateSign) AS days_diff + FROM rows_with_duration +) +SELECT + r.commandDateAffect, + r.positionType, + r.positionLevel, + r.positionCee, + r.days_diff, + CASE + WHEN LAG(commandDateAffect) OVER (ORDER BY commandDateAffect, commandDateSign) IS NOT NULL THEN + TIMESTAMPDIFF(YEAR, LAG(commandDateAffect) OVER (ORDER BY commandDateAffect, commandDateSign), r.commandDateAffect) + ELSE 0 + END AS Years, + CASE + WHEN LAG(commandDateAffect) OVER (ORDER BY commandDateAffect, commandDateSign) IS NOT NULL THEN + TIMESTAMPDIFF(MONTH, LAG(commandDateAffect) OVER (ORDER BY commandDateAffect, commandDateSign), r.commandDateAffect) % 12 + ELSE 0 + END AS Months, + CASE + WHEN LAG(commandDateAffect) OVER (ORDER BY commandDateAffect, commandDateSign) IS NOT NULL THEN + DATEDIFF(r.commandDateAffect, + DATE_ADD( + DATE_ADD(LAG(commandDateAffect) OVER (ORDER BY commandDateAffect, commandDateSign), + INTERVAL TIMESTAMPDIFF(YEAR, LAG(commandDateAffect) OVER (ORDER BY commandDateAffect, commandDateSign), r.commandDateAffect) YEAR), + INTERVAL TIMESTAMPDIFF(MONTH, LAG(commandDateAffect) OVER (ORDER BY commandDateAffect, commandDateSign), r.commandDateAffect) % 12 MONTH) + ) + ELSE 0 + END AS Days, + r.posNo, + r.positionExecutive, + r.orgRoot, + r.orgChild1, + r.orgChild2, + r.orgChild3, + r.orgChild4, + r.commandCode, + r.commandName, + r.commandNo, + r.commandYear, + r.remark +FROM resultWithDiff r + +UNION ALL + +SELECT + _date, NULL, NULL, NULL, + TIMESTAMPDIFF(DAY, MAX(commandDateAffect), _date) + 1, + TIMESTAMPDIFF(YEAR, MAX(commandDateAffect), _date), + TIMESTAMPDIFF(MONTH, MAX(commandDateAffect), _date) % 12, + DATEDIFF(_date, + DATE_ADD( + DATE_ADD(MAX(commandDateAffect), + INTERVAL TIMESTAMPDIFF(YEAR, MAX(commandDateAffect), _date) YEAR), + INTERVAL TIMESTAMPDIFF(MONTH, MAX(commandDateAffect), _date) % 12 MONTH) + ), + NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL, + NULL,NULL,NULL,NULL +FROM resultWithDiff; + +END$$ + +DELIMITER ; diff --git a/docs/migrations/fix_GetProfileEmployeeSalaryPosition_calendar_arithmetic.sql b/docs/migrations/fix_GetProfileEmployeeSalaryPosition_calendar_arithmetic.sql new file mode 100644 index 00000000..fa53b467 --- /dev/null +++ b/docs/migrations/fix_GetProfileEmployeeSalaryPosition_calendar_arithmetic.sql @@ -0,0 +1,137 @@ +-- ==================================================================== +-- Fix GetProfileEmployeeSalaryPosition to use calendar arithmetic +-- This changes from fixed formulas to actual calendar arithmetic, +-- matching calculateGovAge and GetProfileSalaryPosition behavior +-- ==================================================================== + +DELIMITER $$ + +DROP PROCEDURE IF EXISTS `GetProfileEmployeeSalaryPosition`$$ + +CREATE DEFINER=`root`@`%` PROCEDURE `GetProfileEmployeeSalaryPosition`( + IN personId VARCHAR(36), + IN _date DATE +) +BEGIN +WITH ordered AS ( + SELECT * FROM profileSalary WHERE profileEmployeeId = personId AND commandCode IN ('0','1','2','3','4','8','9','10','11','12','13','14','15','16','20') +), +work_session AS ( + SELECT *, + COALESCE( + SUM(CASE WHEN commandCode IN (12,15,16) THEN 1 ELSE 0 END) + OVER (ORDER BY commandDateAffect, commandDateSign + ROWS BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING), + 0) AS sessionId + FROM ordered +), +session_end AS ( + SELECT sessionId, MAX(commandDateAffect) AS sessionEndDate + FROM work_session + GROUP BY sessionId +), +position_change AS ( + SELECT *, + CASE + WHEN LAG(positionName) OVER (ORDER BY commandDateAffect, commandDateSign) = positionName + AND LAG(sessionId) OVER (ORDER BY commandDateAffect, commandDateSign) = sessionId + THEN 0 + ELSE 1 + END AS isNewPosition + FROM work_session +), +position_group AS ( + SELECT *, + SUM(isNewPosition) OVER (ORDER BY commandDateAffect, commandDateSign) AS posGroup + FROM position_change +), +first_rows AS ( + SELECT * FROM ( + SELECT *, + ROW_NUMBER() OVER (PARTITION BY posGroup ORDER BY commandDateAffect, commandDateSign) AS rnPos + FROM position_group + ) t WHERE rnPos = 1 +), +rows_with_duration AS ( + SELECT + fr.*, + LEAD(fr.sessionId) OVER (ORDER BY fr.commandDateAffect, fr.commandDateSign) AS nextSessionId, + CASE + WHEN LEAD(fr.commandDateAffect) OVER (ORDER BY fr.commandDateAffect, fr.commandDateSign) IS NULL + THEN NULL + WHEN LEAD(fr.sessionId) OVER (ORDER BY fr.commandDateAffect, fr.commandDateSign) <> fr.sessionId + THEN TIMESTAMPDIFF(DAY, fr.commandDateAffect, se.sessionEndDate) + 1 + ELSE + TIMESTAMPDIFF(DAY, fr.commandDateAffect, + LEAD(fr.commandDateAffect) OVER (ORDER BY fr.commandDateAffect, fr.commandDateSign)) + END AS duration_days + FROM first_rows fr + LEFT JOIN session_end se ON se.sessionId = fr.sessionId +), +resultWithDiff AS ( + SELECT + *, + LAG(duration_days) OVER (ORDER BY commandDateAffect, commandDateSign) AS days_diff + FROM rows_with_duration +) +SELECT + r.commandDateAffect, + r.positionName, + r.positionCee, + r.days_diff, + CASE + WHEN LAG(commandDateAffect) OVER (ORDER BY commandDateAffect, commandDateSign) IS NOT NULL THEN + TIMESTAMPDIFF(YEAR, LAG(commandDateAffect) OVER (ORDER BY commandDateAffect, commandDateSign), r.commandDateAffect) + ELSE 0 + END AS Years, + CASE + WHEN LAG(commandDateAffect) OVER (ORDER BY commandDateAffect, commandDateSign) IS NOT NULL THEN + TIMESTAMPDIFF(MONTH, LAG(commandDateAffect) OVER (ORDER BY commandDateAffect, commandDateSign), r.commandDateAffect) % 12 + ELSE 0 + END AS Months, + CASE + WHEN LAG(commandDateAffect) OVER (ORDER BY commandDateAffect, commandDateSign) IS NOT NULL THEN + TIMESTAMPDIFF(DAY, + DATE_ADD( + DATE_ADD(LAG(commandDateAffect) OVER (ORDER BY commandDateAffect, commandDateSign), + INTERVAL TIMESTAMPDIFF(YEAR, LAG(commandDateAffect) OVER (ORDER BY commandDateAffect, commandDateSign), r.commandDateAffect) YEAR), + INTERVAL TIMESTAMPDIFF(MONTH, LAG(commandDateAffect) OVER (ORDER BY commandDateAffect, commandDateSign), r.commandDateAffect) % 12 MONTH), + r.commandDateAffect) + ELSE 0 + END AS Days, + r.posNo, + r.positionExecutive, + r.positionType, + r.positionLevel, + r.orgRoot, + r.orgChild1, + r.orgChild2, + r.orgChild3, + r.orgChild4, + r.commandCode, + r.commandName, + r.commandNo, + r.commandYear, + r.remark +FROM resultWithDiff r + +UNION ALL + +SELECT + _date, NULL, NULL, + TIMESTAMPDIFF(DAY, MAX(commandDateAffect), _date) + 1, + TIMESTAMPDIFF(YEAR, MAX(commandDateAffect), _date), + TIMESTAMPDIFF(MONTH, MAX(commandDateAffect), _date) % 12, + DATEDIFF(_date, + DATE_ADD( + DATE_ADD(MAX(commandDateAffect), + INTERVAL TIMESTAMPDIFF(YEAR, MAX(commandDateAffect), _date) YEAR), + INTERVAL TIMESTAMPDIFF(MONTH, MAX(commandDateAffect), _date) % 12 MONTH) + ), + NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL, + NULL,NULL,NULL,NULL,NULL +FROM resultWithDiff; + +END$$ + +DELIMITER ; diff --git a/docs/migrations/fix_GetProfileSalaryExecutive_calendar_arithmetic.sql b/docs/migrations/fix_GetProfileSalaryExecutive_calendar_arithmetic.sql index e3585c9f..9b1d5d50 100644 --- a/docs/migrations/fix_GetProfileSalaryExecutive_calendar_arithmetic.sql +++ b/docs/migrations/fix_GetProfileSalaryExecutive_calendar_arithmetic.sql @@ -14,7 +14,7 @@ CREATE DEFINER=`root`@`%` PROCEDURE `GetProfileSalaryExecutive`( ) BEGIN WITH ordered AS ( - SELECT * FROM profileSalary WHERE profileId = personId AND commandCode IN ('0','1','2','3','4','8','9','10','11','12','13','14','15','16','20') + SELECT * FROM profileSalary WHERE profileId = personId AND commandCode IN ('0','1','2','3','4','8','9','10','11','12','13','14','15','16','20') AND positionExecutive <> '' ), work_session AS ( SELECT *, @@ -90,12 +90,12 @@ SELECT END AS Months, CASE WHEN LAG(r.commandDateAffect) OVER (ORDER BY r.commandDateAffect, r.commandDateSign) IS NOT NULL THEN - TIMESTAMPDIFF(DAY, + DATEDIFF(r.commandDateAffect, DATE_ADD( DATE_ADD(LAG(r.commandDateAffect) OVER (ORDER BY r.commandDateAffect, r.commandDateSign), INTERVAL TIMESTAMPDIFF(YEAR, LAG(r.commandDateAffect) OVER (ORDER BY r.commandDateAffect, r.commandDateSign), r.commandDateAffect) YEAR), - INTERVAL TIMESTAMPDIFF(MONTH, LAG(r.commandDateAffect) OVER (ORDER BY r.commandDateAffect, r.commandDateSign), r.commandDateAffect) MONTH), - r.commandDateAffect) + 1 + INTERVAL TIMESTAMPDIFF(MONTH, LAG(r.commandDateAffect) OVER (ORDER BY r.commandDateAffect, r.commandDateSign), r.commandDateAffect) % 12 MONTH) + ) ELSE 0 END AS Days, r.posNo, @@ -121,12 +121,12 @@ SELECT TIMESTAMPDIFF(DAY, MAX(commandDateAffect), _date) + 1, TIMESTAMPDIFF(YEAR, MAX(commandDateAffect), _date), TIMESTAMPDIFF(MONTH, MAX(commandDateAffect), _date) % 12, - TIMESTAMPDIFF(DAY, + DATEDIFF(_date, DATE_ADD( DATE_ADD(MAX(commandDateAffect), INTERVAL TIMESTAMPDIFF(YEAR, MAX(commandDateAffect), _date) YEAR), - INTERVAL TIMESTAMPDIFF(MONTH, MAX(commandDateAffect), _date) MONTH), - _date) + 1, + INTERVAL TIMESTAMPDIFF(MONTH, MAX(commandDateAffect), _date) % 12 MONTH) + ), NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL, NULL,NULL,NULL,NULL,NULL,NULL FROM resultWithDiff; diff --git a/docs/migrations/fix_GetProfileSalaryLevel_calendar_arithmetic.sql b/docs/migrations/fix_GetProfileSalaryLevel_calendar_arithmetic.sql index ca811a23..0ce8bbb5 100644 --- a/docs/migrations/fix_GetProfileSalaryLevel_calendar_arithmetic.sql +++ b/docs/migrations/fix_GetProfileSalaryLevel_calendar_arithmetic.sql @@ -94,12 +94,12 @@ SELECT END AS Months, CASE WHEN LAG(r.commandDateAffect) OVER (ORDER BY r.commandDateAffect, r.commandDateSign) IS NOT NULL THEN - TIMESTAMPDIFF(DAY, + DATEDIFF(r.commandDateAffect, DATE_ADD( DATE_ADD(LAG(r.commandDateAffect) OVER (ORDER BY r.commandDateAffect, r.commandDateSign), INTERVAL TIMESTAMPDIFF(YEAR, LAG(r.commandDateAffect) OVER (ORDER BY r.commandDateAffect, r.commandDateSign), r.commandDateAffect) YEAR), - INTERVAL TIMESTAMPDIFF(MONTH, LAG(r.commandDateAffect) OVER (ORDER BY r.commandDateAffect, r.commandDateSign), r.commandDateAffect) MONTH), - r.commandDateAffect) + 1 + INTERVAL TIMESTAMPDIFF(MONTH, LAG(r.commandDateAffect) OVER (ORDER BY r.commandDateAffect, r.commandDateSign), r.commandDateAffect) % 12 MONTH) + ) ELSE 0 END AS Days, r.posNo, @@ -123,12 +123,12 @@ SELECT TIMESTAMPDIFF(DAY, MAX(commandDateAffect), _date) + 1, TIMESTAMPDIFF(YEAR, MAX(commandDateAffect), _date), TIMESTAMPDIFF(MONTH, MAX(commandDateAffect), _date) % 12, - TIMESTAMPDIFF(DAY, + DATEDIFF(_date, DATE_ADD( DATE_ADD(MAX(commandDateAffect), INTERVAL TIMESTAMPDIFF(YEAR, MAX(commandDateAffect), _date) YEAR), - INTERVAL TIMESTAMPDIFF(MONTH, MAX(commandDateAffect), _date) MONTH), - _date) + 1, + INTERVAL TIMESTAMPDIFF(MONTH, MAX(commandDateAffect), _date) % 12 MONTH) + ), NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL, NULL,NULL,NULL,NULL FROM resultWithDiff; diff --git a/docs/migrations/fix_GetProfileSalaryPosition_calendar_arithmetic.sql b/docs/migrations/fix_GetProfileSalaryPosition_calendar_arithmetic.sql index a546ad97..aed2e9e7 100644 --- a/docs/migrations/fix_GetProfileSalaryPosition_calendar_arithmetic.sql +++ b/docs/migrations/fix_GetProfileSalaryPosition_calendar_arithmetic.sql @@ -96,8 +96,8 @@ SELECT DATE_ADD( DATE_ADD(LAG(r.commandDateAffect) OVER (ORDER BY r.commandDateAffect, r.commandDateSign), INTERVAL TIMESTAMPDIFF(YEAR, LAG(r.commandDateAffect) OVER (ORDER BY r.commandDateAffect, r.commandDateSign), r.commandDateAffect) YEAR), - INTERVAL TIMESTAMPDIFF(MONTH, LAG(r.commandDateAffect) OVER (ORDER BY r.commandDateAffect, r.commandDateSign), r.commandDateAffect) MONTH), - r.commandDateAffect) + 1 + INTERVAL TIMESTAMPDIFF(MONTH, LAG(r.commandDateAffect) OVER (ORDER BY r.commandDateAffect, r.commandDateSign), r.commandDateAffect) % 12 MONTH), + r.commandDateAffect) ELSE 0 END AS Days, r.posNo, @@ -124,12 +124,12 @@ SELECT TIMESTAMPDIFF(DAY, MAX(commandDateAffect), _date) + 1, TIMESTAMPDIFF(YEAR, MAX(commandDateAffect), _date), TIMESTAMPDIFF(MONTH, MAX(commandDateAffect), _date) % 12, - TIMESTAMPDIFF(DAY, + DATEDIFF(_date, DATE_ADD( DATE_ADD(MAX(commandDateAffect), INTERVAL TIMESTAMPDIFF(YEAR, MAX(commandDateAffect), _date) YEAR), - INTERVAL TIMESTAMPDIFF(MONTH, MAX(commandDateAffect), _date) MONTH), - _date) + 1, + INTERVAL TIMESTAMPDIFF(MONTH, MAX(commandDateAffect), _date) % 12 MONTH) + ), NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL, NULL,NULL,NULL,NULL,NULL FROM resultWithDiff; diff --git a/src/controllers/ApiKeyController.ts b/src/controllers/ApiKeyController.ts index 4c9664d7..7de6e415 100644 --- a/src/controllers/ApiKeyController.ts +++ b/src/controllers/ApiKeyController.ts @@ -20,6 +20,12 @@ import { In } from "typeorm"; import { RequestWithUser } from "../middlewares/user"; import { ApiName } from "../entities/ApiName"; import { ApiHistory } from "../entities/ApiHistory"; +import { OrgRoot } from "../entities/OrgRoot"; +import { OrgChild1 } from "../entities/OrgChild1"; +import { OrgChild2 } from "../entities/OrgChild2"; +import { OrgChild3 } from "../entities/OrgChild3"; +import { OrgChild4 } from "../entities/OrgChild4"; +import { OrgRevision } from "../entities/OrgRevision"; const jwt = require("jsonwebtoken"); @Route("api/v1/org/apiKey") @@ -33,6 +39,12 @@ export class ApiKeyController extends Controller { private apiKeyRepository = AppDataSource.getRepository(ApiKey); private apiNameRepository = AppDataSource.getRepository(ApiName); private apiHistoryRepository = AppDataSource.getRepository(ApiHistory); + private orgRootRepository = AppDataSource.getRepository(OrgRoot); + private orgChild1Repository = AppDataSource.getRepository(OrgChild1); + private orgChild2Repository = AppDataSource.getRepository(OrgChild2); + private orgChild3Repository = AppDataSource.getRepository(OrgChild3); + private orgChild4Repository = AppDataSource.getRepository(OrgChild4); + private orgRevisionRepository = AppDataSource.getRepository(OrgRevision); /** * API ตรวจสอบและถอดรหัส JWT token @@ -151,6 +163,9 @@ export class ApiKeyController extends Controller { relations: ["apiNames", "apiHistorys"], order: { createdAt: "DESC", apiNames: { createdAt: "DESC" } }, }); + + const orgNames = await this.buildOrgNameBatch(apiKey); + const data = apiKey.map((_data) => ({ id: _data.id, createdAt: _data.createdAt, @@ -163,6 +178,7 @@ export class ApiKeyController extends Controller { dnaChild2Id: _data.dnaChild2Id, dnaChild3Id: _data.dnaChild3Id, dnaChild4Id: _data.dnaChild4Id, + orgName: orgNames.get(_data.id), apiNames: _data.apiNames.map((x) => ({ id: x.id, name: x.name, @@ -174,10 +190,139 @@ export class ApiKeyController extends Controller { return new HttpSuccess(data); } + private async buildOrgNameBatch(apiKeys: ApiKey[]): Promise> { + const currentRevision = await this.orgRevisionRepository.findOne({ + where: { orgRevisionIsCurrent: true, orgRevisionIsDraft: false }, + }); + + if (!currentRevision) { + return new Map(apiKeys.map((k) => [k.id, null])); + } + + const currentRevisionId = currentRevision.id; + + const rootIds = [...new Set(apiKeys.map((k) => k.dnaRootId).filter(Boolean))]; + const child1Ids = [...new Set(apiKeys.map((k) => k.dnaChild1Id).filter(Boolean))]; + const child2Ids = [...new Set(apiKeys.map((k) => k.dnaChild2Id).filter(Boolean))]; + const child3Ids = [...new Set(apiKeys.map((k) => k.dnaChild3Id).filter(Boolean))]; + const child4Ids = [...new Set(apiKeys.map((k) => k.dnaChild4Id).filter(Boolean))]; + + const [roots, child1s, child2s, child3s, child4s] = await Promise.all([ + rootIds.length > 0 + ? this.orgRootRepository.find({ + where: [ + { id: In(rootIds), orgRevisionId: currentRevisionId }, + { ancestorDNA: In(rootIds), orgRevisionId: currentRevisionId }, + ], + select: ["id", "ancestorDNA", "orgRootName"], + }) + : [], + child1Ids.length > 0 + ? this.orgChild1Repository.find({ + where: [ + { id: In(child1Ids), orgRevisionId: currentRevisionId }, + { ancestorDNA: In(child1Ids), orgRevisionId: currentRevisionId }, + ], + select: ["id", "ancestorDNA", "orgChild1Name"], + }) + : [], + child2Ids.length > 0 + ? this.orgChild2Repository.find({ + where: [ + { id: In(child2Ids), orgRevisionId: currentRevisionId }, + { ancestorDNA: In(child2Ids), orgRevisionId: currentRevisionId }, + ], + select: ["id", "ancestorDNA", "orgChild2Name"], + }) + : [], + child3Ids.length > 0 + ? this.orgChild3Repository.find({ + where: [ + { id: In(child3Ids), orgRevisionId: currentRevisionId }, + { ancestorDNA: In(child3Ids), orgRevisionId: currentRevisionId }, + ], + select: ["id", "ancestorDNA", "orgChild3Name"], + }) + : [], + child4Ids.length > 0 + ? this.orgChild4Repository.find({ + where: [ + { id: In(child4Ids), orgRevisionId: currentRevisionId }, + { ancestorDNA: In(child4Ids), orgRevisionId: currentRevisionId }, + ], + select: ["id", "ancestorDNA", "orgChild4Name"], + }) + : [], + ]); + + const rootMap = new Map( + roots.map((r) => [r.id, { name: r.orgRootName, ancestorDNA: r.ancestorDNA }]), + ); + const child1Map = new Map( + child1s.map((c) => [c.id, { name: c.orgChild1Name, ancestorDNA: c.ancestorDNA }]), + ); + const child2Map = new Map( + child2s.map((c) => [c.id, { name: c.orgChild2Name, ancestorDNA: c.ancestorDNA }]), + ); + const child3Map = new Map( + child3s.map((c) => [c.id, { name: c.orgChild3Name, ancestorDNA: c.ancestorDNA }]), + ); + const child4Map = new Map( + child4s.map((c) => [c.id, { name: c.orgChild4Name, ancestorDNA: c.ancestorDNA }]), + ); + + const result = new Map(); + for (const apiKey of apiKeys) { + if (apiKey.accessType === "ALL") { + result.set(apiKey.id, null); + continue; + } + + const parts: string[] = []; + + const getOrgName = ( + dnaId: string, + orgMap: Map, + ): string | null => { + const byId = orgMap.get(dnaId); + if (byId) return byId.name; + for (const [, value] of orgMap) { + if (value.ancestorDNA === dnaId) return value.name; + } + return null; + }; + + if (apiKey.dnaChild4Id) { + const name = getOrgName(apiKey.dnaChild4Id, child4Map); + if (name) parts.push(name); + } + if (apiKey.dnaChild3Id) { + const name = getOrgName(apiKey.dnaChild3Id, child3Map); + if (name) parts.push(name); + } + if (apiKey.dnaChild2Id) { + const name = getOrgName(apiKey.dnaChild2Id, child2Map); + if (name) parts.push(name); + } + if (apiKey.dnaChild1Id) { + const name = getOrgName(apiKey.dnaChild1Id, child1Map); + if (name) parts.push(name); + } + if (apiKey.dnaRootId) { + const name = getOrgName(apiKey.dnaRootId, rootMap); + if (name) parts.push(name); + } + + result.set(apiKey.id, parts.length > 0 ? parts.join(" ") : null); + } + + return result; + } + /** - * API รายการ Api Key + * API รายการ Api Name * - * @summary รายการ Api Key (ADMIN) + * @summary รายการ Api Name (ADMIN) * */ @Get("name") diff --git a/src/controllers/ApiManageController.ts b/src/controllers/ApiManageController.ts index 01a27eb5..17f14050 100644 --- a/src/controllers/ApiManageController.ts +++ b/src/controllers/ApiManageController.ts @@ -106,10 +106,10 @@ export class ApiManageController extends Controller { code: "organization", name: "ข้อมูลโครงสร้าง", }, - { - code: "position", - name: "ข้อมูลอัตรากำลัง", - }, + // { + // code: "position", + // name: "ข้อมูลอัตรากำลัง", + // }, ]; // รายการเอนทิตีทั้งหมด @@ -273,59 +273,240 @@ export class ApiManageController extends Controller { description: "ข้อมูลส่วนราชการ ระดับที่ 4", system: ["organization"], }, - { - name: "PosMaster", - repository: this.posMasterRepository, - description: "ข้อมูลอัตรากำลัง", - isMain: true, - system: ["position"], - }, - { - name: "Position", - repository: this.positionRepository, - description: "ข้อมูลตำแหน่ง", - system: ["position"], - }, - { - name: "OrgRoot", - repository: this.orgRootRepository, - description: "ข้อมูลหน่วยงาน", - system: ["position"], - }, - { - name: "OrgChild1", - repository: this.orgChild1Repository, - description: "ข้อมูลส่วนราชการ ระดับที่ 1", - system: ["position"], - }, - { - name: "OrgChild2", - repository: this.orgChild2Repository, - description: "ข้อมูลส่วนราชการ ระดับที่ 2", - system: ["position"], - }, - { - name: "OrgChild3", - repository: this.orgChild3Repository, - description: "ข้อมูลส่วนราชการ ระดับที่ 3", - system: ["position"], - }, - { - name: "OrgChild4", - repository: this.orgChild4Repository, - description: "ข้อมูลส่วนราชการ ระดับที่ 4", - system: ["position"], - }, - { - name: "Profile", - repository: this.profileRepository, - description: "ข้อมูลคนครอง", - system: ["position"], - }, + // { + // name: "PosMaster", + // repository: this.posMasterRepository, + // description: "ข้อมูลอัตรากำลัง", + // isMain: true, + // system: ["position"], + // }, + // { + // name: "Position", + // repository: this.positionRepository, + // description: "ข้อมูลตำแหน่ง", + // system: ["position"], + // }, + // { + // name: "OrgRoot", + // repository: this.orgRootRepository, + // description: "ข้อมูลหน่วยงาน", + // system: ["position"], + // }, + // { + // name: "OrgChild1", + // repository: this.orgChild1Repository, + // description: "ข้อมูลส่วนราชการ ระดับที่ 1", + // system: ["position"], + // }, + // { + // name: "OrgChild2", + // repository: this.orgChild2Repository, + // description: "ข้อมูลส่วนราชการ ระดับที่ 2", + // system: ["position"], + // }, + // { + // name: "OrgChild3", + // repository: this.orgChild3Repository, + // description: "ข้อมูลส่วนราชการ ระดับที่ 3", + // system: ["position"], + // }, + // { + // name: "OrgChild4", + // repository: this.orgChild4Repository, + // description: "ข้อมูลส่วนราชการ ระดับที่ 4", + // system: ["position"], + // }, + // { + // name: "Profile", + // repository: this.profileRepository, + // description: "ข้อมูลคนครอง", + // system: ["position"], + // }, ]; private readonly DEFAULT_PAGE_SIZE = 10; // ขนาดหน้าเริ่มต้น - private readonly EXCLUDED_COLUMNS = ["createdUserId", "lastUpdateUserId"]; // ฟิลด์ที่ไม่ต้องการแสดงในผลลัพธ์ + private readonly EXCLUDED_COLUMNS = [ + "createdUserId", + "lastUpdateUserId", + "createdAt", + "createdFullName", + "lastUpdateFullName", + "avatarName", + "profileId", + "prefixId", + "profileEmployeeId", + "documentId", + "orgRevisionId", + "posMasterId", + "orgRootId", + "orgChild1Id", + "orgChild2Id", + "orgChild3Id", + "orgChild4Id", + "keycloak", + "commandId", + "prefixMain", + "authRoleId", + "next_holderId", + "current_holderId", + "ancestorDNA", + "leaveCommandId", + "posLevelId", + "posTypeId", + "posExecutiveId", + "registrationProvinceId", + "registrationDistrictId", + "registrationSubDistrictId", + "currentProvinceId", + "currentDistrictId", + "currentSubDistrictId", + "isDelete", + "keycloak", + "statusCheckEdit", + "privacyCheckin", + "privacyUser", + "privacyMgt", + "dutyTimeId", + "dutyTimeEffectiveDate", + "profileId", + "profileEmployeeId", + "orgRevisionId", + "rank", + "isUpload", + "isDeleted", + "isEntry", + "prefixId", + "leaveId", + "leaveTypeId", + "isDeputy", + "isCommission", + ]; // ฟิลด์ที่ไม่ต้องการแสดงในผลลัพธ์ + + // การแทนที่ฟิลด์ ID ด้วยฟิลด์ Name สำหรับ Profile entity + private readonly PROFILE_FIELD_REPLACEMENTS: Record< + string, + { propertyName: string; type: string; comment: string; joinTable: string; joinField: string } + > = { + posLevelId: { + propertyName: "posLevelName", + type: "string", + comment: "ระดับตำแหน่ง", + joinTable: "PosLevel", + joinField: "posLevelName", + }, + posTypeId: { + propertyName: "posTypeName", + type: "string", + comment: "ประเภทตำแหน่ง", + joinTable: "PosType", + joinField: "posTypeName", + }, + registrationProvinceId: { + propertyName: "registrationProvinceName", + type: "string", + comment: "จังหวัดตามทะเบียนบ้าน", + joinTable: "Province", + joinField: "name", + }, + registrationDistrictId: { + propertyName: "registrationDistrictName", + type: "string", + comment: "เขตตามทะเบียนบ้าน", + joinTable: "District", + joinField: "name", + }, + registrationSubDistrictId: { + propertyName: "registrationSubDistrictName", + type: "string", + comment: "แขวงตามทะเบียนบ้าน", + joinTable: "SubDistrict", + joinField: "name", + }, + currentProvinceId: { + propertyName: "currentProvinceName", + type: "string", + comment: "จังหวัดตามปัจจุบัน", + joinTable: "Province", + joinField: "name", + }, + currentDistrictId: { + propertyName: "currentDistrictName", + type: "string", + comment: "เขตตามปัจจุบัน", + joinTable: "District", + joinField: "name", + }, + currentSubDistrictId: { + propertyName: "currentSubDistrictName", + type: "string", + comment: "แขวงตามปัจจุบัน", + joinTable: "SubDistrict", + joinField: "name", + }, + }; + + // การแทนที่ฟิลด์ ID ด้วยฟิลด์ Name สำหรับ Position entity + private readonly POSITION_FIELD_REPLACEMENTS: Record< + string, + { propertyName: string; type: string; comment: string; joinTable: string; joinField: string } + > = { + posTypeId: { + propertyName: "posTypeName", + type: "string", + comment: "ประเภทตำแหน่ง", + joinTable: "PosType", + joinField: "posTypeName", + }, + posLevelId: { + propertyName: "posLevelName", + type: "string", + comment: "ระดับตำแหน่ง", + joinTable: "PosLevel", + joinField: "posLevelName", + }, + posExecutiveId: { + propertyName: "posExecutiveName", + type: "string", + comment: "ตำแหน่งทางการบริหาร", + joinTable: "PosExecutive", + joinField: "posExecutiveName", + }, + }; + + // การแทนที่ฟิลด์ ID ด้วยฟิลด์ Name สำหรับ ProfileEmployee entity + private readonly PROFILEEMPLOYEE_FIELD_REPLACEMENTS: Record< + string, + { propertyName: string; type: string; comment: string; joinTable: string; joinField: string } + > = { + posLevelId: { + propertyName: "posLevelName", + type: "string", + comment: "ระดับชั้นงาน", + joinTable: "EmployeePosLevel", + joinField: "posLevelName", + }, + posTypeId: { + propertyName: "posTypeName", + type: "string", + comment: "กลุ่มงาน", + joinTable: "EmployeePosType", + joinField: "posTypeName", + }, + }; + + // การแทนที่ฟิลด์ ID ด้วยฟิลด์ Name สำหรับ ProfileLeave entity + private readonly PROFILELEAVE_FIELD_REPLACEMENTS: Record< + string, + { propertyName: string; type: string; comment: string; joinTable: string; joinField: string } + > = { + leaveTypeId: { + propertyName: "leaveTypeName", + type: "string", + comment: "ประเภทการลา", + joinTable: "LeaveType", + joinField: "name", + }, + }; private validateSuperAdminRole(user: any): void { if (!user.role.includes("SUPER_ADMIN")) { @@ -364,11 +545,8 @@ export class ApiManageController extends Controller { const result = this.entities .filter((entity) => entity.system.includes(system)) - .map(({ name, repository, description, isMain }) => ({ - tb: name, - description, - isMain: isMain || false, - propertys: repository.metadata.columns + .map(({ name, repository, description, isMain }) => { + let columns = repository.metadata.columns .filter( (column: any) => !column.isPrimary && !this.EXCLUDED_COLUMNS.includes(column.propertyName), @@ -378,8 +556,114 @@ export class ApiManageController extends Controller { type: typeof column.type === "string" ? column.type : "string", comment: column.comment, key: column.propertyName, - })), - })); + })); + + // Special handling for Profile entity - replace ID fields with name fields + if (name === "Profile") { + const replacementKeys = Object.keys(this.PROFILE_FIELD_REPLACEMENTS); + + // Remove ID fields that should be replaced + columns = columns.filter( + (col: { propertyName: string }) => !replacementKeys.includes(col.propertyName), + ); + + // Add the corresponding name fields + const nameFields = replacementKeys.map((key) => ({ + propertyName: this.PROFILE_FIELD_REPLACEMENTS[key].propertyName, + type: "string", + comment: this.PROFILE_FIELD_REPLACEMENTS[key].comment, + key: this.PROFILE_FIELD_REPLACEMENTS[key].propertyName, + })); + + columns = [...columns, ...nameFields]; + } + + // Special handling for Position entity - replace ID fields with name fields + if (name === "Position") { + const replacementKeys = Object.keys(this.POSITION_FIELD_REPLACEMENTS); + + // Remove ID fields that should be replaced + columns = columns.filter( + (col: { propertyName: string }) => !replacementKeys.includes(col.propertyName), + ); + + // Add the corresponding name fields + const nameFields = replacementKeys.map((key) => ({ + propertyName: this.POSITION_FIELD_REPLACEMENTS[key].propertyName, + type: "string", + comment: this.POSITION_FIELD_REPLACEMENTS[key].comment, + key: this.POSITION_FIELD_REPLACEMENTS[key].propertyName, + })); + + columns = [...columns, ...nameFields]; + } + + // Special handling for ProfileEmployee entity - replace ID fields with name fields + if (name === "ProfileEmployee") { + const replacementKeys = Object.keys(this.PROFILEEMPLOYEE_FIELD_REPLACEMENTS); + + // Remove ID fields that should be replaced + columns = columns.filter( + (col: { propertyName: string }) => !replacementKeys.includes(col.propertyName), + ); + + // Add the corresponding name fields + const nameFields = replacementKeys.map((key) => ({ + propertyName: this.PROFILEEMPLOYEE_FIELD_REPLACEMENTS[key].propertyName, + type: "string", + comment: this.PROFILEEMPLOYEE_FIELD_REPLACEMENTS[key].comment, + key: this.PROFILEEMPLOYEE_FIELD_REPLACEMENTS[key].propertyName, + })); + + columns = [...columns, ...nameFields]; + } + + // Special handling for ProfileLeave entity - replace ID fields with name fields + if (name === "ProfileLeave") { + const replacementKeys = Object.keys(this.PROFILELEAVE_FIELD_REPLACEMENTS); + + // Remove ID fields that should be replaced + columns = columns.filter( + (col: { propertyName: string }) => !replacementKeys.includes(col.propertyName), + ); + + // Add the corresponding name fields + const nameFields = replacementKeys.map((key) => ({ + propertyName: this.PROFILELEAVE_FIELD_REPLACEMENTS[key].propertyName, + type: "string", + comment: this.PROFILELEAVE_FIELD_REPLACEMENTS[key].comment, + key: this.PROFILELEAVE_FIELD_REPLACEMENTS[key].propertyName, + })); + + columns = [...columns, ...nameFields]; + } + + // Special handling for PosMaster entity - add Profile fields for holder information + if (name === "PosMaster") { + // Add Profile fields that are accessible via current_holder relation + const profileFields = ["prefix", "rank", "firstName", "lastName", "citizenId"]; + const profileRepository = AppDataSource.getRepository(Profile); + const profileColumns = profileRepository.metadata.columns + .filter( + (column: any) => !column.isPrimary && profileFields.includes(column.propertyName), + ) + .map((column: any) => ({ + propertyName: `Profile.${column.propertyName}`, + type: typeof column.type === "string" ? column.type : "string", + comment: column.comment, + key: `Profile.${column.propertyName}`, + })); + + columns = [...columns, ...profileColumns]; + } + + return { + tb: name, + description, + isMain: isMain || false, + propertys: columns, + }; + }); return new HttpSuccess(result); } catch (error) { diff --git a/src/controllers/ApiWebServiceController.ts b/src/controllers/ApiWebServiceController.ts index 61f3d54a..fbf0ab78 100644 --- a/src/controllers/ApiWebServiceController.ts +++ b/src/controllers/ApiWebServiceController.ts @@ -8,6 +8,11 @@ import { isPermissionRequest } from "../middlewares/authWebService"; import { RequestWithUserWebService } from "../middlewares/user"; import { OrgRevision } from "../entities/OrgRevision"; import { ApiHistory } from "../entities/ApiHistory"; +import { OrgRoot } from "../entities/OrgRoot"; +import { OrgChild1 } from "../entities/OrgChild1"; +import { OrgChild2 } from "../entities/OrgChild2"; +import { OrgChild3 } from "../entities/OrgChild3"; +import { OrgChild4 } from "../entities/OrgChild4"; import { SystemCode } from "./../interfaces/api-type"; @Route("api/v1/org/api-service") @Tags("ApiKey") @@ -20,6 +25,198 @@ export class ApiWebServiceController extends Controller { private apiNameRepository = AppDataSource.getRepository(ApiName); private orgRevisionRepository = AppDataSource.getRepository(OrgRevision); private apiHistoryRepository = AppDataSource.getRepository(ApiHistory); + private currentRevisionId: string = ""; + + // การแทนที่ฟิลด์ ID ด้วยฟิลด์ Name สำหรับ Profile entity + private readonly PROFILE_FIELD_REPLACEMENTS: Record< + string, + { propertyName: string; joinRelation: string; joinField: string } + > = { + posLevelName: { + propertyName: "posLevelId", + joinRelation: "posLevel", + joinField: "posLevelName", + }, + posTypeName: { + propertyName: "posTypeId", + joinRelation: "posType", + joinField: "posTypeName", + }, + registrationProvinceName: { + propertyName: "registrationProvinceId", + joinRelation: "registrationProvince", + joinField: "name", + }, + registrationDistrictName: { + propertyName: "registrationDistrictId", + joinRelation: "registrationDistrict", + joinField: "name", + }, + registrationSubDistrictName: { + propertyName: "registrationSubDistrictId", + joinRelation: "registrationSubDistrict", + joinField: "name", + }, + currentProvinceName: { + propertyName: "currentProvinceId", + joinRelation: "currentProvince", + joinField: "name", + }, + currentDistrictName: { + propertyName: "currentDistrictId", + joinRelation: "currentDistrict", + joinField: "name", + }, + currentSubDistrictName: { + propertyName: "currentSubDistrictId", + joinRelation: "currentSubDistrict", + joinField: "name", + }, + }; + + // การแทนที่ฟิลด์ ID ด้วยฟิลด์ Name สำหรับ ProfileLeave entity + private readonly PROFILELEAVE_FIELD_REPLACEMENTS: Record< + string, + { propertyName: string; joinRelation: string; joinField: string } + > = { + leaveTypeName: { + propertyName: "leaveTypeId", + joinRelation: "leaveType", + joinField: "name", + }, + }; + + // การแทนที่ฟิลด์ ID ด้วยฟิลด์ Name สำหรับ Position entity + private readonly POSITION_FIELD_REPLACEMENTS: Record< + string, + { propertyName: string; joinRelation: string; joinField: string } + > = { + posTypeName: { + propertyName: "posTypeId", + joinRelation: "posType", + joinField: "posTypeName", + }, + posLevelName: { + propertyName: "posLevelId", + joinRelation: "posLevel", + joinField: "posLevelName", + }, + posExecutiveName: { + propertyName: "posExecutiveId", + joinRelation: "posExecutive", + joinField: "posExecutiveName", + }, + }; + + // การแทนที่ฟิลด์ ID ด้วยฟิลด์ Name สำหรับ ProfileEmployee entity + private readonly PROFILEEMPLOYEE_FIELD_REPLACEMENTS: Record< + string, + { propertyName: string; joinRelation: string; joinField: string } + > = { + posTypeName: { + propertyName: "posTypeId", + joinRelation: "posType", + joinField: "posTypeName", + }, + posLevelName: { + propertyName: "posLevelId", + joinRelation: "posLevel", + joinField: "posLevelName", + }, + }; + + /** + * build posMaster permission condition + * @summary สร้างเงื่อนไขการกรองข้อมูลตามสิทธิ์การเข้าถึง + */ + private buildPosMasterPermissionCondition( + accessType: string | undefined, + dnaIds: { + dnaRootId?: string | null; + dnaChild1Id?: string | null; + dnaChild2Id?: string | null; + dnaChild3Id?: string | null; + dnaChild4Id?: string | null; + }, + tableAlias: string = "posMaster", + ): string { + // ALL - no filtering + if (accessType === "ALL") { + return "1=1"; + } + + // No access type specified but has DNA IDs - default to NORMAL behavior + const conditions: string[] = []; + + if (accessType === "ROOT" && dnaIds.dnaRootId) { + // All organizations under this root + conditions.push( + `${tableAlias}.orgRootId IN (SELECT id FROM orgRoot WHERE orgRevisionId = "${this.currentRevisionId}" AND ancestorDNA LIKE "${dnaIds.dnaRootId}%")`, + ); + } else if (accessType === "CHILD" || accessType === "NORMAL") { + // Build conditions based on which DNA level is specified + if (dnaIds.dnaChild4Id) { + conditions.push( + `${tableAlias}.orgChild4Id IN (SELECT id FROM orgChild4 WHERE orgRevisionId = "${this.currentRevisionId}" AND ancestorDNA = "${dnaIds.dnaChild4Id}")`, + ); + } else if (dnaIds.dnaChild3Id) { + conditions.push( + `${tableAlias}.orgChild3Id IN (SELECT id FROM orgChild3 WHERE orgRevisionId = "${this.currentRevisionId}" AND ancestorDNA = "${dnaIds.dnaChild3Id}")`, + ); + // For CHILD type, include all descendants + if (accessType === "CHILD") { + conditions.push( + `(${tableAlias}.orgChild3Id IN (SELECT id FROM orgChild3 WHERE orgRevisionId = "${this.currentRevisionId}" AND ancestorDNA LIKE "${dnaIds.dnaChild3Id}%") OR ${tableAlias}.orgChild4Id IS NOT NULL)`, + ); + } + } else if (dnaIds.dnaChild2Id) { + conditions.push( + `${tableAlias}.orgChild2Id IN (SELECT id FROM orgChild2 WHERE orgRevisionId = "${this.currentRevisionId}" AND ancestorDNA = "${dnaIds.dnaChild2Id}")`, + ); + if (accessType === "CHILD") { + conditions.push( + `(${tableAlias}.orgChild2Id IN (SELECT id FROM orgChild2 WHERE orgRevisionId = "${this.currentRevisionId}" AND ancestorDNA LIKE "${dnaIds.dnaChild2Id}%") OR ${tableAlias}.orgChild3Id IS NOT NULL)`, + ); + } + } else if (dnaIds.dnaChild1Id) { + conditions.push( + `${tableAlias}.orgChild1Id IN (SELECT id FROM orgChild1 WHERE orgRevisionId = "${this.currentRevisionId}" AND ancestorDNA = "${dnaIds.dnaChild1Id}")`, + ); + if (accessType === "CHILD") { + conditions.push( + `(${tableAlias}.orgChild1Id IN (SELECT id FROM orgChild1 WHERE orgRevisionId = "${this.currentRevisionId}" AND ancestorDNA LIKE "${dnaIds.dnaChild1Id}%") OR ${tableAlias}.orgChild2Id IS NOT NULL)`, + ); + } + } else if (dnaIds.dnaRootId) { + conditions.push( + `${tableAlias}.orgRootId IN (SELECT id FROM orgRoot WHERE orgRevisionId = "${this.currentRevisionId}" AND ancestorDNA = "${dnaIds.dnaRootId}")`, + ); + if (accessType === "CHILD") { + conditions.push( + `(${tableAlias}.orgRootId IN (SELECT id FROM orgRoot WHERE orgRevisionId = "${this.currentRevisionId}" AND ancestorDNA LIKE "${dnaIds.dnaRootId}%") OR ${tableAlias}.orgChild1Id IS NOT NULL)`, + ); + } + } + } + + return conditions.length > 0 ? `(${conditions.join(" OR ")})` : "1=1"; + } + + /** + * rename ancestorDNA to id + * @summary เปลี่ยนชื่อฟิลด์ ancestorDNA เป็น id + */ + private renameAncestorDnaToId(obj: any): any { + if (!obj || typeof obj !== "object") { + return obj; + } + const result = { ...obj }; + if (result.ancestorDNA !== undefined) { + result.id = result.ancestorDNA; + delete result.ancestorDNA; + } + return result; + } /** * list fields by systems @@ -50,12 +247,42 @@ export class ApiWebServiceController extends Controller { } await isPermissionRequest(request, apiName.id); const offset = (page - 1) * pageSize; - const propertyKey = apiName.apiAttributes.map((attr) => `${attr.tbName}.${attr.propertyKey}`); + let propertyKey = apiName.apiAttributes.map((attr) => `${attr.tbName}.${attr.propertyKey}`); + const selectedFieldsByTable: Record> = {}; + apiName.apiAttributes.forEach((attr) => { + if (!selectedFieldsByTable[attr.tbName]) { + selectedFieldsByTable[attr.tbName] = new Set(); + } + selectedFieldsByTable[attr.tbName].add(attr.propertyKey); + }); + + // สำหรับ Organization: ให้รวม ancestorDNA เสมอ เพื่อแสดงเป็น id + if (system === "organization") { + // สำหรับ OrgRoot + const ancestorDnaField = "OrgRoot.ancestorDNA"; + if (!propertyKey.includes(ancestorDnaField)) { + propertyKey.push(ancestorDnaField); + } + if (!selectedFieldsByTable["OrgRoot"]) { + selectedFieldsByTable["OrgRoot"] = new Set(); + } + selectedFieldsByTable["OrgRoot"].add("ancestorDNA"); + + // สำหรับ OrgChild1, OrgChild2, OrgChild3, OrgChild4 + const childTables = ["OrgChild1", "OrgChild2", "OrgChild3", "OrgChild4"]; + childTables.forEach((table) => { + if (!selectedFieldsByTable[table]) { + selectedFieldsByTable[table] = new Set(); + } + selectedFieldsByTable[table].add("ancestorDNA"); + }); + } let tbMain: string = ""; let condition: string = "1=1"; if (system == "registry") { tbMain = "Profile"; + condition = `Profile.isActive = true AND Profile.isDelete = false`; } else if (system == "registry_emp") { tbMain = "ProfileEmployee"; condition = `ProfileEmployee.employeeClass = "PERM"`; @@ -78,6 +305,152 @@ export class ApiWebServiceController extends Controller { condition = `PosMaster.orgRevisionId = "${revision?.id}"`; } + let posMasterCondition: string = "1=1"; + let posMasterAlias: string = ""; + + // Add isDeleted filtering for entities that have this field + // Profile.ts uses isDelete (singular) instead of isDeleted + if (tbMain === "Profile") { + // Already handled above in the registry system condition + } else if ( + [ + "ProfileAbility", + "ProfileAbilityHistory", + "ProfileAbsentLate", + "ProfileActposition", + "ProfileActpositionHistory", + "ProfileAssistance", + "ProfileAssistanceHistory", + "ProfileAssessment", + "ProfileAssessmentHistory", + "ProfileCertificate", + "ProfileCertificateHistory", + "ProfileChangeName", + "ProfileChangeNameHistory", + "ProfileChildren", + "ProfileChildrenHistory", + "ProfileDiscipline", + "ProfileDisciplineHistory", + "ProfileDevelopment", + "ProfileDevelopmentHistory", + "ProfileDuty", + "ProfileDutyHistory", + "ProfileEducation", + "ProfileEducationHistory", + "ProfileHonor", + "ProfileHonorHistory", + "ProfileInsignia", + "ProfileInsigniaHistory", + "ProfileLeave", + "ProfileNopaid", + "ProfileNopaidHistory", + "ProfileOther", + "ProfileOtherHistory", + "ProfileSalary", + "ProfileSalaryHistory", + "ProfileSalaryTemp", + "ProfileTraining", + "ProfileTrainingHistory", + ].includes(tbMain) + ) { + condition = `${tbMain}.isDeleted = false`; + } + + // Special handling for Profile and ProfileEmployee systems with permission filtering + if (system == "registry") { + // Get current revision + const revision = await this.orgRevisionRepository.findOne({ + select: ["id"], + where: { orgRevisionIsCurrent: true, orgRevisionIsDraft: false }, + }); + + // Store for use in permission building + this.currentRevisionId = revision?.id || ""; + posMasterAlias = "posMaster"; + + // Build permission condition + posMasterCondition = this.buildPosMasterPermissionCondition( + request.user.accessType, + { + dnaRootId: request.user.dnaRootId, + dnaChild1Id: request.user.dnaChild1Id, + dnaChild2Id: request.user.dnaChild2Id, + dnaChild3Id: request.user.dnaChild3Id, + dnaChild4Id: request.user.dnaChild4Id, + }, + posMasterAlias, + ); + } else if (system == "registry_emp") { + // Get current revision + const revision = await this.orgRevisionRepository.findOne({ + select: ["id"], + where: { orgRevisionIsCurrent: true, orgRevisionIsDraft: false }, + }); + + // Store for use in permission building + this.currentRevisionId = revision?.id || ""; + posMasterAlias = "employeePosMaster"; + + // Build permission condition + posMasterCondition = this.buildPosMasterPermissionCondition( + request.user.accessType, + { + dnaRootId: request.user.dnaRootId, + dnaChild1Id: request.user.dnaChild1Id, + dnaChild2Id: request.user.dnaChild2Id, + dnaChild3Id: request.user.dnaChild3Id, + dnaChild4Id: request.user.dnaChild4Id, + }, + posMasterAlias, + ); + } else if (system == "registry_temp") { + // Get current revision + const revision = await this.orgRevisionRepository.findOne({ + select: ["id"], + where: { orgRevisionIsCurrent: true, orgRevisionIsDraft: false }, + }); + + // Store for use in permission building + this.currentRevisionId = revision?.id || ""; + posMasterAlias = "employeeTempPosMaster"; + + // Build permission condition + posMasterCondition = this.buildPosMasterPermissionCondition( + request.user.accessType, + { + dnaRootId: request.user.dnaRootId, + dnaChild1Id: request.user.dnaChild1Id, + dnaChild2Id: request.user.dnaChild2Id, + dnaChild3Id: request.user.dnaChild3Id, + dnaChild4Id: request.user.dnaChild4Id, + }, + posMasterAlias, + ); + } else if (system == "position") { + // Get current revision + const revision = await this.orgRevisionRepository.findOne({ + select: ["id"], + where: { orgRevisionIsCurrent: true, orgRevisionIsDraft: false }, + }); + + // Store for use in permission building + this.currentRevisionId = revision?.id || ""; + posMasterAlias = "PosMaster"; // Note: Uses PascalCase to match tbMain alias + + // Build permission condition + posMasterCondition = this.buildPosMasterPermissionCondition( + request.user.accessType, + { + dnaRootId: request.user.dnaRootId, + dnaChild1Id: request.user.dnaChild1Id, + dnaChild2Id: request.user.dnaChild2Id, + dnaChild3Id: request.user.dnaChild3Id, + dnaChild4Id: request.user.dnaChild4Id, + }, + posMasterAlias, + ); + } + const repo = AppDataSource.getRepository(tbMain); const metadata = repo.metadata; @@ -92,27 +465,271 @@ export class ApiWebServiceController extends Controller { ...new Set(propertyKey.map((x) => x.split(".")[0]).filter((tb) => tb !== tbMain)), ]; + // Organization hierarchy is assembled in a separate step; keep main query focused on OrgRoot only. + if (tbMain === "OrgRoot") { + const orgChildTables = new Set(["OrgChild1", "OrgChild2", "OrgChild3", "OrgChild4"]); + propertyKey = propertyKey.filter((key) => !orgChildTables.has(key.split(".")[0])); + propertyOtherKey = propertyOtherKey.filter((tb) => !orgChildTables.has(tb)); + } + + // สำหรับ Profile: ตรวจสอบฟิลด์ที่ต้องการ join และแปลง propertyKey + const profileFieldJoins: Record = {}; // alias -> relationName + if (tbMain === "Profile") { + propertyKey = propertyKey.map((key) => { + const [table, field] = key.split("."); + if (table === "Profile") { + const replacement = this.PROFILE_FIELD_REPLACEMENTS[field]; + if (replacement) { + const alias = `${table}_${replacement.joinRelation}`; + profileFieldJoins[alias] = replacement.joinRelation; + return `${alias}.${replacement.joinField}`; + } + } + return key; + }); + } + + // สำหรับ Position: ตรวจสอบฟิลด์ที่ต้องการ join และแปลง propertyKey + const positionFieldJoins: Record = {}; // alias -> relationName + if (tbMain === "Position" || tbMain === "PosMaster") { + propertyKey = propertyKey.map((key) => { + const [table, field] = key.split("."); + if (table === "Position") { + const replacement = this.POSITION_FIELD_REPLACEMENTS[field]; + if (replacement) { + const alias = `${table}_${replacement.joinRelation}`; + positionFieldJoins[alias] = replacement.joinRelation; + return `${alias}.${replacement.joinField}`; + } + } + return key; + }); + } + + // สำหรับ ProfileEmployee: ตรวจสอบฟิลด์ที่ต้องการ join และแปลง propertyKey + const profileEmployeeFieldJoins: Record = {}; // alias -> relationName + if (tbMain === "ProfileEmployee") { + propertyKey = propertyKey.map((key) => { + const [table, field] = key.split("."); + if (table === "ProfileEmployee") { + const replacement = this.PROFILEEMPLOYEE_FIELD_REPLACEMENTS[field]; + if (replacement) { + const alias = `${table}_${replacement.joinRelation}`; + profileEmployeeFieldJoins[alias] = replacement.joinRelation; + return `${alias}.${replacement.joinField}`; + } + } + return key; + }); + } + + // สำหรับ ProfileLeave: ตรวจสอบฟิลด์ที่ต้องการ join และแปลง propertyKey + const profileLeaveFieldJoins: Record = {}; // alias -> relationName + // Process ProfileLeave fields regardless of main table + propertyKey = propertyKey.map((key) => { + const [table, field] = key.split("."); + if (table === "ProfileLeave") { + const replacement = this.PROFILELEAVE_FIELD_REPLACEMENTS[field]; + if (replacement) { + const alias = `${table}_${replacement.joinRelation}`; + profileLeaveFieldJoins[alias] = replacement.joinRelation; + return `${alias}.${replacement.joinField}`; + } + } + return key; + }); + const queryBuilder = repo.createQueryBuilder(tbMain); // join กับตารารอง if (propertyOtherKey.length > 0) { propertyOtherKey.forEach((tb) => { + // Skip Profile join for PosMaster - it's handled separately below + if (tbMain === "PosMaster" && tb === "Profile") { + return; + } + + // Skip Position join for PosMaster - it's handled separately below + if (tbMain === "PosMaster" && tb === "Position") { + return; + } + const relationName = relationMap[tb]; if (relationName) { - queryBuilder.leftJoin( - `${tbMain}.${relationName === "next_holder" ? "current_holder" : relationName}`, // เช็คว่าถ้าเป็น next_holder ให้ใช้ current_holder แทน - tb, - ); + queryBuilder.leftJoin(`${tbMain}.${relationName}`, tb); + } else { + // Remove fields from this table from propertyKey + propertyKey = propertyKey.filter((key) => !key.startsWith(`${tb}.`)); } }); } + // Check if propertyKey is empty after filtering + if (propertyKey.length === 0) { + throw new HttpError( + HttpStatusCode.BAD_REQUEST, + "ไม่พบฟิลด์ที่ต้องการแสดงผล กรุณาตรวจสอบการตั้งค่า API (ไม่สามารถ join ตารางลูกได้)", + ); + } + + // join สำหรับฟิลด์ Profile ที่ต้องการดึงค่าจากตารางอื่น + if (tbMain === "Profile" && Object.keys(profileFieldJoins).length > 0) { + Object.entries(profileFieldJoins).forEach(([alias, relationName]) => { + queryBuilder.leftJoin(`${tbMain}.${relationName}`, alias); + }); + } + + // join สำหรับฟิลด์ Position ที่ต้องการดึงค่าจากตารางอื่น + if ( + (tbMain === "Position" || tbMain === "PosMaster") && + Object.keys(positionFieldJoins).length > 0 + ) { + if (tbMain === "PosMaster") { + const posMasterPositionRelation = relationMap["Position"]; + if (!posMasterPositionRelation) { + throw new HttpError( + HttpStatusCode.BAD_REQUEST, + "ไม่พบความสัมพันธ์ระหว่าง PosMaster กับ Position กรุณาตรวจสอบการตั้งค่า API", + ); + } + + // Join PosMaster -> Position once using actual relation name from metadata + queryBuilder.leftJoin(`PosMaster.${posMasterPositionRelation}`, "Position"); + } + + Object.entries(positionFieldJoins).forEach(([alias, relationName]) => { + if (tbMain === "PosMaster") { + queryBuilder.leftJoin(`Position.${relationName}`, alias); + } else { + queryBuilder.leftJoin(`${tbMain}.${relationName}`, alias); + } + }); + } + + // join สำหรับฟิลด์ ProfileEmployee ที่ต้องการดึงค่าจากตารางอื่น + if (tbMain === "ProfileEmployee" && Object.keys(profileEmployeeFieldJoins).length > 0) { + Object.entries(profileEmployeeFieldJoins).forEach(([alias, relationName]) => { + queryBuilder.leftJoin(`${tbMain}.${relationName}`, alias); + }); + } + + // join สำหรับฟิลด์ ProfileLeave ที่ต้องการดึงค่าจากตารางอื่น + if (Object.keys(profileLeaveFieldJoins).length > 0) { + if (tbMain === "ProfileLeave") { + // ProfileLeave is the main table - direct join + Object.entries(profileLeaveFieldJoins).forEach(([alias, relationName]) => { + queryBuilder.leftJoin(`${tbMain}.${relationName}`, alias); + }); + } else { + // ProfileLeave is a related table - the base join is already created by propertyOtherKey logic + // Join from the ProfileLeave alias (not from nested path) + Object.entries(profileLeaveFieldJoins).forEach(([alias, relationName]) => { + queryBuilder.leftJoin(`ProfileLeave.${relationName}`, alias); + }); + } + } + + // join สำหรับ PosMaster เมื่อต้องการดึงค่าจาก Profile (ข้อมูลคนครอง) + const posMasterProfileFields: string[] = []; + if (tbMain === "PosMaster") { + // Collect Profile fields from both formats: "Profile.xxx" and "PosMaster.Profile.xxx" + const extractedProfileFields = propertyKey + .filter((key) => key.startsWith("Profile.") || key.startsWith("PosMaster.Profile.")) + .map((key) => key.replace(/^PosMaster\.Profile\./, "Profile.")); + + posMasterProfileFields.push(...new Set(extractedProfileFields)); + + // Remove Profile fields then add back normalized "Profile.xxx" form + propertyKey = propertyKey.filter( + (key) => !key.startsWith("Profile.") && !key.startsWith("PosMaster.Profile."), + ); + propertyKey.push(...posMasterProfileFields); + } + + // join PosMaster กับ Profile เมื่อมีการขอ Profile fields + if (tbMain === "PosMaster" && posMasterProfileFields.length > 0) { + // Always join via current_holder (not next_holder) because PosMaster has two relations + // to Profile and relationMap["Profile"] would resolve to next_holder (last defined in entity) + queryBuilder.leftJoin("PosMaster.current_holder", "Profile"); + } + + // สำหรับ registry system: เก็บ posMaster เพื่อดึง org IDs แล้วค่อย query ancestorDNA + let includeOrgAncestorDna = false; + if (system === "registry" || system === "registry_emp" || system === "registry_temp") { + // Always join posMaster for registry systems (inner join to exclude profiles without current posMaster) + // Only include posMaster from current revision + if (tbMain === "Profile") { + queryBuilder.innerJoin("Profile.current_holders", "posMaster", "posMaster.orgRevisionId = :currentRevisionId"); + queryBuilder.setParameter("currentRevisionId", this.currentRevisionId); + + // Add org ID fields from posMaster to propertyKey + propertyKey.push("posMaster.orgRootId"); + propertyKey.push("posMaster.orgChild1Id"); + propertyKey.push("posMaster.orgChild2Id"); + propertyKey.push("posMaster.orgChild3Id"); + propertyKey.push("posMaster.orgChild4Id"); + } else if (tbMain === "ProfileEmployee") { + // For registry_emp and registry_temp, also use inner join with current revision + if (system === "registry_temp") { + queryBuilder.innerJoin("ProfileEmployee.current_holderTemps", "employeeTempPosMaster", "employeeTempPosMaster.orgRevisionId = :currentRevisionId"); + queryBuilder.setParameter("currentRevisionId", this.currentRevisionId); + + // Add org ID fields from employeeTempPosMaster to propertyKey + propertyKey.push("employeeTempPosMaster.orgRootId"); + propertyKey.push("employeeTempPosMaster.orgChild1Id"); + propertyKey.push("employeeTempPosMaster.orgChild2Id"); + propertyKey.push("employeeTempPosMaster.orgChild3Id"); + propertyKey.push("employeeTempPosMaster.orgChild4Id"); + } else { + // registry_emp + queryBuilder.innerJoin("ProfileEmployee.current_holders", "employeePosMaster", "employeePosMaster.orgRevisionId = :currentRevisionId"); + queryBuilder.setParameter("currentRevisionId", this.currentRevisionId); + + // Add org ID fields from employeePosMaster to propertyKey + propertyKey.push("employeePosMaster.orgRootId"); + propertyKey.push("employeePosMaster.orgChild1Id"); + propertyKey.push("employeePosMaster.orgChild2Id"); + propertyKey.push("employeePosMaster.orgChild3Id"); + propertyKey.push("employeePosMaster.orgChild4Id"); + } + } + + // Mark that we need to include ancestorDNA fields + includeOrgAncestorDna = true; + } + + // join กับ posMaster/employeePosMaster/employeeTempPosMaster เพื่อกรองตามสิทธิ์การเข้าถึง + // Skip duplicate join - posMaster already joined for registry systems at lines 569-571 + // Permission condition will use the existing alias + const posMasterAlreadyJoined = (system === "registry" || system === "registry_emp" || system === "registry_temp") && tbMain === "Profile"; + + if ((tbMain === "Profile" || tbMain === "ProfileEmployee") && posMasterCondition !== "1=1") { + if (tbMain === "Profile") { + // Only join if not already joined for registry systems + if (!posMasterAlreadyJoined) { + queryBuilder.innerJoin("Profile.current_holders", "posMaster", "posMaster.orgRevisionId = :currentRevisionIdPerm"); + queryBuilder.setParameter("currentRevisionIdPerm", this.currentRevisionId); + } + } else if (tbMain === "ProfileEmployee") { + // Check if already joined for registry_emp systems + const alreadyJoined = (system === "registry_emp" || system === "registry_temp"); + // Use the correct relation based on posMasterAlias + if (posMasterAlias === "employeeTempPosMaster" && !alreadyJoined) { + queryBuilder.innerJoin("ProfileEmployee.current_holderTemps", "employeeTempPosMaster", "employeeTempPosMaster.orgRevisionId = :currentRevisionIdPerm"); + queryBuilder.setParameter("currentRevisionIdPerm", this.currentRevisionId); + } else if (posMasterAlias === "employeePosMaster" && !alreadyJoined) { + queryBuilder.innerJoin("ProfileEmployee.current_holders", "employeePosMaster", "employeePosMaster.orgRevisionId = :currentRevisionIdPerm"); + queryBuilder.setParameter("currentRevisionIdPerm", this.currentRevisionId); + } + } + } + // // เพิ่ม Main.id เพราะจะใช้ pk ในการแมบและนับจำนวน // if (!propertyKey.includes(`${Main}.id`)) { // propertyKey.push(`${Main}.id`); // } - // add FK + // add PK - ensure propertyKey is never empty let pk: string = ""; const primaryColumns = metadata.primaryColumns; primaryColumns.forEach((col) => { @@ -122,13 +739,89 @@ export class ApiWebServiceController extends Controller { } }); - const [items, total] = await queryBuilder - .select(propertyKey) - .where(condition) - .orderBy(propertyKey[0], "ASC") - .skip(offset) - .take(pageSize) - .getManyAndCount(); + let items: any[] = []; + let total = 0; + + if (tbMain === "OrgRoot") { + // Organization API should always return full hierarchy regardless of page/pageSize. + [items, total] = await queryBuilder + .select(propertyKey) + .where(condition) + .andWhere(posMasterCondition) + .orderBy(propertyKey[0] || `${tbMain}.${pk}`, "ASC") + .getManyAndCount(); + } else { + [items, total] = await queryBuilder + .select(propertyKey) + .where(condition) + .andWhere(posMasterCondition) + .orderBy(propertyKey[0] || `${tbMain}.${pk}`, "ASC") + .skip(offset) + .take(pageSize) + .getManyAndCount(); + } + + // สำหรับ registry systems: ดึง ancestorDNA จาก org tables + let orgRootAncestorMap: Record = {}; + let orgChild1AncestorMap: Record = {}; + let orgChild2AncestorMap: Record = {}; + let orgChild3AncestorMap: Record = {}; + let orgChild4AncestorMap: Record = {}; + + if (includeOrgAncestorDna && items.length > 0) { + // Collect all unique org IDs + const orgRootIds = new Set(); + const orgChild1Ids = new Set(); + const orgChild2Ids = new Set(); + const orgChild3Ids = new Set(); + const orgChild4Ids = new Set(); + + items.forEach((item) => { + // Handle Profile (registry) - current_holders + if (item["current_holders"] && Array.isArray(item["current_holders"]) && item["current_holders"].length > 0) { + const posMaster = item["current_holders"][0]; + if (posMaster.orgRootId) orgRootIds.add(posMaster.orgRootId); + if (posMaster.orgChild1Id) orgChild1Ids.add(posMaster.orgChild1Id); + if (posMaster.orgChild2Id) orgChild2Ids.add(posMaster.orgChild2Id); + if (posMaster.orgChild3Id) orgChild3Ids.add(posMaster.orgChild3Id); + if (posMaster.orgChild4Id) orgChild4Ids.add(posMaster.orgChild4Id); + } + // Handle ProfileEmployee (registry_emp) - current_holders + if (item["current_holders"] && Array.isArray(item["current_holders"]) && item["current_holders"].length > 0) { + const posMaster = item["current_holders"][0]; + if (posMaster.orgRootId) orgRootIds.add(posMaster.orgRootId); + if (posMaster.orgChild1Id) orgChild1Ids.add(posMaster.orgChild1Id); + if (posMaster.orgChild2Id) orgChild2Ids.add(posMaster.orgChild2Id); + if (posMaster.orgChild3Id) orgChild3Ids.add(posMaster.orgChild3Id); + if (posMaster.orgChild4Id) orgChild4Ids.add(posMaster.orgChild4Id); + } + // Handle ProfileEmployee (registry_temp) - current_holderTemps + if (item["current_holderTemps"] && Array.isArray(item["current_holderTemps"]) && item["current_holderTemps"].length > 0) { + const posMaster = item["current_holderTemps"][0]; + if (posMaster.orgRootId) orgRootIds.add(posMaster.orgRootId); + if (posMaster.orgChild1Id) orgChild1Ids.add(posMaster.orgChild1Id); + if (posMaster.orgChild2Id) orgChild2Ids.add(posMaster.orgChild2Id); + if (posMaster.orgChild3Id) orgChild3Ids.add(posMaster.orgChild3Id); + if (posMaster.orgChild4Id) orgChild4Ids.add(posMaster.orgChild4Id); + } + }); + + // Query org tables to get ancestorDNA + const [orgRoots, orgChild1s, orgChild2s, orgChild3s, orgChild4s] = await Promise.all([ + orgRootIds.size > 0 ? AppDataSource.getRepository(OrgRoot).createQueryBuilder("orgRoot").select(["orgRoot.id", "orgRoot.ancestorDNA"]).where("orgRoot.id IN (:...ids)", { ids: Array.from(orgRootIds) }).getMany() : [], + orgChild1Ids.size > 0 ? AppDataSource.getRepository(OrgChild1).createQueryBuilder("orgChild1").select(["orgChild1.id", "orgChild1.ancestorDNA"]).where("orgChild1.id IN (:...ids)", { ids: Array.from(orgChild1Ids) }).getMany() : [], + orgChild2Ids.size > 0 ? AppDataSource.getRepository(OrgChild2).createQueryBuilder("orgChild2").select(["orgChild2.id", "orgChild2.ancestorDNA"]).where("orgChild2.id IN (:...ids)", { ids: Array.from(orgChild2Ids) }).getMany() : [], + orgChild3Ids.size > 0 ? AppDataSource.getRepository(OrgChild3).createQueryBuilder("orgChild3").select(["orgChild3.id", "orgChild3.ancestorDNA"]).where("orgChild3.id IN (:...ids)", { ids: Array.from(orgChild3Ids) }).getMany() : [], + orgChild4Ids.size > 0 ? AppDataSource.getRepository(OrgChild4).createQueryBuilder("orgChild4").select(["orgChild4.id", "orgChild4.ancestorDNA"]).where("orgChild4.id IN (:...ids)", { ids: Array.from(orgChild4Ids) }).getMany() : [], + ]); + + // Create separate maps for each org level + orgRoots.forEach((org: any) => { orgRootAncestorMap[org.id] = org.ancestorDNA; }); + orgChild1s.forEach((org: any) => { orgChild1AncestorMap[org.id] = org.ancestorDNA; }); + orgChild2s.forEach((org: any) => { orgChild2AncestorMap[org.id] = org.ancestorDNA; }); + orgChild3s.forEach((org: any) => { orgChild3AncestorMap[org.id] = org.ancestorDNA; }); + orgChild4s.forEach((org: any) => { orgChild4AncestorMap[org.id] = org.ancestorDNA; }); + } // ลบ Main.id // const results = items.map(({ id, ...x }) => x); @@ -141,11 +834,330 @@ export class ApiWebServiceController extends Controller { // split object id ออกก่อน return const data = items.map((item) => { - const { [pk]: removedPk, ...x } = item; - return x; + const { [pk]: removedPk, ...rest } = item; + + // สำหรับ Profile: แปลงฟิลด์ที่มาจาก join กลับเป็นชื่อเดิม + if (tbMain === "Profile") { + const flattened: any = { ...rest }; + Object.entries(this.PROFILE_FIELD_REPLACEMENTS).forEach(([nameField, config]) => { + const alias = `${tbMain}_${config.joinRelation}`; + if (rest[alias] && rest[alias][config.joinField] !== undefined) { + flattened[nameField] = rest[alias][config.joinField]; + delete flattened[alias]; + } + // Also handle nested relation objects (e.g., "posLevel": { "posLevelName": "..." }) + if ( + rest[config.joinRelation] && + rest[config.joinRelation][config.joinField] !== undefined + ) { + flattened[nameField] = rest[config.joinRelation][config.joinField]; + delete flattened[config.joinRelation]; + } + }); + + // แปลง ancestorDNA เป็น orgRootId, orgChild1Id, etc. + if (includeOrgAncestorDna) { + if (rest["current_holders"] && Array.isArray(rest["current_holders"]) && rest["current_holders"].length > 0) { + const posMaster = rest["current_holders"][0]; + + // Get ancestorDNA from separate maps using org IDs + // Always set the fields, use null if no value + flattened.orgRootId = (posMaster.orgRootId && orgRootAncestorMap[posMaster.orgRootId]) ? orgRootAncestorMap[posMaster.orgRootId] : null; + flattened.orgChild1Id = (posMaster.orgChild1Id && orgChild1AncestorMap[posMaster.orgChild1Id]) ? orgChild1AncestorMap[posMaster.orgChild1Id] : null; + flattened.orgChild2Id = (posMaster.orgChild2Id && orgChild2AncestorMap[posMaster.orgChild2Id]) ? orgChild2AncestorMap[posMaster.orgChild2Id] : null; + flattened.orgChild3Id = (posMaster.orgChild3Id && orgChild3AncestorMap[posMaster.orgChild3Id]) ? orgChild3AncestorMap[posMaster.orgChild3Id] : null; + flattened.orgChild4Id = (posMaster.orgChild4Id && orgChild4AncestorMap[posMaster.orgChild4Id]) ? orgChild4AncestorMap[posMaster.orgChild4Id] : null; + } else { + // No current_holders, set all to null + flattened.orgRootId = null; + flattened.orgChild1Id = null; + flattened.orgChild2Id = null; + flattened.orgChild3Id = null; + flattened.orgChild4Id = null; + } + + // Delete current_holders array + delete flattened["current_holders"]; + } + return flattened; + } + + // สำหรับ Position: แปลงฟิลด์ที่มาจาก join กลับเป็นชื่อเดิม + if (tbMain === "Position" || tbMain === "PosMaster") { + const flattened: any = { ...rest }; + Object.entries(this.POSITION_FIELD_REPLACEMENTS).forEach(([nameField, config]) => { + // Remove the original ID field + delete flattened[config.propertyName]; + // Add the name field from joined table + const alias = `Position_${config.joinRelation}`; + if (rest[alias] && rest[alias][config.joinField] !== undefined) { + flattened[nameField] = rest[alias][config.joinField]; + } + // Remove the joined table object + delete flattened[alias]; + }); + // Remove Position object if exists + if (flattened["Position"]) { + delete flattened["Position"]; + } + return flattened; + } + + // สำหรับ ProfileEmployee: แปลงฟิลด์ที่มาจาก join กลับเป็นชื่อเดิม + if (tbMain === "ProfileEmployee") { + const flattened: any = { ...rest }; + Object.entries(this.PROFILEEMPLOYEE_FIELD_REPLACEMENTS).forEach(([nameField, config]) => { + // Remove the original ID field + delete flattened[config.propertyName]; + // Add the name field from joined table + const alias = `${tbMain}_${config.joinRelation}`; + if (rest[alias] && rest[alias][config.joinField] !== undefined) { + flattened[nameField] = rest[alias][config.joinField]; + } + // Remove the joined table object + delete flattened[alias]; + // Also handle nested relation objects (e.g., "posType": { "posTypeName": "..." }) + if ( + rest[config.joinRelation] && + rest[config.joinRelation][config.joinField] !== undefined + ) { + flattened[nameField] = rest[config.joinRelation][config.joinField]; + delete flattened[config.joinRelation]; + } + }); + + // แปลง ancestorDNA เป็น orgRootId, orgChild1Id, etc. (สำหรับ registry_emp และ registry_temp) + if (includeOrgAncestorDna) { + const posMasterKey = system === "registry_temp" ? "current_holderTemps" : "current_holders"; + if (rest[posMasterKey] && Array.isArray(rest[posMasterKey]) && rest[posMasterKey].length > 0) { + const posMaster = rest[posMasterKey][0]; + + // Always set the fields, use null if no value + flattened.orgRootId = (posMaster.orgRootId && orgRootAncestorMap[posMaster.orgRootId]) ? orgRootAncestorMap[posMaster.orgRootId] : null; + flattened.orgChild1Id = (posMaster.orgChild1Id && orgChild1AncestorMap[posMaster.orgChild1Id]) ? orgChild1AncestorMap[posMaster.orgChild1Id] : null; + flattened.orgChild2Id = (posMaster.orgChild2Id && orgChild2AncestorMap[posMaster.orgChild2Id]) ? orgChild2AncestorMap[posMaster.orgChild2Id] : null; + flattened.orgChild3Id = (posMaster.orgChild3Id && orgChild3AncestorMap[posMaster.orgChild3Id]) ? orgChild3AncestorMap[posMaster.orgChild3Id] : null; + flattened.orgChild4Id = (posMaster.orgChild4Id && orgChild4AncestorMap[posMaster.orgChild4Id]) ? orgChild4AncestorMap[posMaster.orgChild4Id] : null; + } else { + // No posMaster data, set all to null + flattened.orgRootId = null; + flattened.orgChild1Id = null; + flattened.orgChild2Id = null; + flattened.orgChild3Id = null; + flattened.orgChild4Id = null; + } + + // Delete the posMaster array + delete flattened[posMasterKey]; + } + return flattened; + } + + // สำหรับ PosMaster: แปลงฟิลด์ Profile ที่มาจาก join กลับเป็นฟิลด์ระดับบน + if (tbMain === "PosMaster" && posMasterProfileFields.length > 0) { + const flattened: any = { ...rest }; + const profileFieldNames = posMasterProfileFields + .filter((field) => field.startsWith("Profile.")) + .map((field) => field.replace("Profile.", "")); + + // Extract only requested Profile fields and add top-level aliases + if (rest["Profile"]) { + profileFieldNames.forEach((fieldName) => { + if (rest["Profile"][fieldName] !== undefined) { + flattened[`profile_${fieldName}`] = rest["Profile"][fieldName]; + } + }); + // Remove the nested Profile object + delete flattened["Profile"]; + } + return flattened; + } + + // สำหรับ OrgRoot: เก็บ primary key ไว้ใช้ group ข้อมูล แล้วแยก children ภายหลัง + if (tbMain === "OrgRoot") { + return { __rootPk: removedPk, ...rest }; + } + + return rest; }); - // console.log("queryBuilder ===> ", queryBuilder.getQuery()); + let responseData: any[] = data; + let responseTotal = total; + + // สำหรับ Organization: รวมข้อมูลให้เหลือ 1 root ต่อ 1 object และจัด children ตาม hierarchy + if (tbMain === "OrgRoot") { + const rootVisibleFields = Array.from(selectedFieldsByTable["OrgRoot"] || []); + const child1VisibleFields = Array.from(selectedFieldsByTable["OrgChild1"] || []); + const child2VisibleFields = Array.from(selectedFieldsByTable["OrgChild2"] || []); + const child3VisibleFields = Array.from(selectedFieldsByTable["OrgChild3"] || []); + const child4VisibleFields = Array.from(selectedFieldsByTable["OrgChild4"] || []); + + const pickVisibleFields = (obj: any, fields: string[]) => { + const out: any = {}; + fields.forEach((field) => { + if (obj[field] !== undefined) { + // ถ้าเป็น ancestorDNA ให้เปลี่ยนชื่อเป็น id + if (field === "ancestorDNA") { + out.id = obj[field]; + } else { + out[field] = obj[field]; + } + } + }); + return out; + }; + + const rootMap = new Map(); + data.forEach((row: any) => { + if (!row.__rootPk || rootMap.has(row.__rootPk)) { + return; + } + + const rootNode = { + ...pickVisibleFields(row, rootVisibleFields), + children: [], + }; + rootMap.set(row.__rootPk, rootNode); + }); + + const rootIds = Array.from(rootMap.keys()); + + if (rootIds.length > 0) { + const buildSelect = (alias: string, required: string[], visible: string[]) => + Array.from(new Set([...required, ...visible])).map((field) => `${alias}.${field}`); + + const [child1Rows, child2Rows, child3Rows, child4Rows] = await Promise.all([ + AppDataSource.getRepository(OrgChild1) + .createQueryBuilder("OrgChild1") + .select(buildSelect("OrgChild1", ["id", "orgRootId", "ancestorDNA"], child1VisibleFields)) + .where("OrgChild1.orgRootId IN (:...rootIds)", { rootIds }) + .orderBy("OrgChild1.id", "ASC") + .getMany(), + AppDataSource.getRepository(OrgChild2) + .createQueryBuilder("OrgChild2") + .select( + buildSelect("OrgChild2", ["id", "orgRootId", "orgChild1Id", "ancestorDNA"], child2VisibleFields), + ) + .where("OrgChild2.orgRootId IN (:...rootIds)", { rootIds }) + .orderBy("OrgChild2.id", "ASC") + .getMany(), + AppDataSource.getRepository(OrgChild3) + .createQueryBuilder("OrgChild3") + .select( + buildSelect( + "OrgChild3", + ["id", "orgRootId", "orgChild1Id", "orgChild2Id", "ancestorDNA"], + child3VisibleFields, + ), + ) + .where("OrgChild3.orgRootId IN (:...rootIds)", { rootIds }) + .orderBy("OrgChild3.id", "ASC") + .getMany(), + AppDataSource.getRepository(OrgChild4) + .createQueryBuilder("OrgChild4") + .select( + buildSelect( + "OrgChild4", + ["id", "orgRootId", "orgChild1Id", "orgChild2Id", "orgChild3Id", "ancestorDNA"], + child4VisibleFields, + ), + ) + .where("OrgChild4.orgRootId IN (:...rootIds)", { rootIds }) + .orderBy("OrgChild4.id", "ASC") + .getMany(), + ]); + + const child1Map = new Map(); + const child2Map = new Map(); + const child3Map = new Map(); + + child1Rows.forEach((row) => { + const node = { + ...pickVisibleFields(row, child1VisibleFields), + children: [], + }; + child1Map.set(row.id, node); + + const rootNode = rootMap.get(row.orgRootId); + if (rootNode) { + rootNode.children.push(node); + } + }); + + child2Rows.forEach((row) => { + const node = { + ...pickVisibleFields(row, child2VisibleFields), + children: [], + }; + child2Map.set(row.id, node); + + const parent = child1Map.get(row.orgChild1Id); + if (parent) { + parent.children.push(node); + } + }); + + child3Rows.forEach((row) => { + const node = { + ...pickVisibleFields(row, child3VisibleFields), + children: [], + }; + child3Map.set(row.id, node); + + const parent = child2Map.get(row.orgChild2Id); + if (parent) { + parent.children.push(node); + } + }); + + child4Rows.forEach((row) => { + const node = { + ...pickVisibleFields(row, child4VisibleFields), + }; + + const parent = child3Map.get(row.orgChild3Id); + if (parent) { + if (!Array.isArray(parent.children)) { + parent.children = []; + } + parent.children.push(node); + } + }); + } + + responseData = Array.from(rootMap.values()); + + // สำหรับ Organization: เปลี่ยนชื่อ ancestorDNA เป็น id + responseData = responseData.map((root: any) => { + const renamed = this.renameAncestorDnaToId(root); + if (Array.isArray(renamed.children)) { + renamed.children = renamed.children.map((child1: any) => { + const renamedChild1 = this.renameAncestorDnaToId(child1); + if (Array.isArray(renamedChild1.children)) { + renamedChild1.children = renamedChild1.children.map((child2: any) => { + const renamedChild2 = this.renameAncestorDnaToId(child2); + if (Array.isArray(renamedChild2.children)) { + renamedChild2.children = renamedChild2.children.map((child3: any) => { + const renamedChild3 = this.renameAncestorDnaToId(child3); + if (Array.isArray(renamedChild3.children)) { + renamedChild3.children = renamedChild3.children.map((child4: any) => + this.renameAncestorDnaToId(child4), + ); + } + return renamedChild3; + }); + } + return renamedChild2; + }); + } + return renamedChild1; + }); + } + return renamed; + }); + + responseTotal = responseData.length; + } // save api history after query success const history = { @@ -195,6 +1207,6 @@ export class ApiWebServiceController extends Controller { // return flattenedItem; // }); - return new HttpSuccess({ data: data, total }); + return new HttpSuccess({ data: responseData, total: responseTotal }); } } diff --git a/src/controllers/AuthRoleController.ts b/src/controllers/AuthRoleController.ts index 4159c5ec..13f5ce04 100644 --- a/src/controllers/AuthRoleController.ts +++ b/src/controllers/AuthRoleController.ts @@ -123,18 +123,25 @@ export class AuthRoleController extends Controller { // เช็คว่าถ้ามีค่า current_holderId ให้ลบ key สิทธิ์ใน redis if (posMaster.current_holderId) { - const redisClient = await this.redis.createClient({ - host: REDIS_HOST, - port: REDIS_PORT, - }); + let redisClient; + try { + redisClient = await this.redis.createClient({ + host: REDIS_HOST, + port: REDIS_PORT, + }); - redisClient.del("role_" + posMaster.current_holderId, (err: Error, response: Response) => { - if (err) throw err; - }); + redisClient.del("role_" + posMaster.current_holderId, (err: Error) => { + if (err) console.error("Redis delete role error:", err); + }); - redisClient.del("menu_" + posMaster.current_holderId, (err: Error, response: Response) => { - if (err) throw err; - }); + redisClient.del("menu_" + posMaster.current_holderId, (err: Error) => { + if (err) console.error("Redis delete menu error:", err); + }); + } finally { + if (redisClient) { + redisClient.quit(); + } + } } return new HttpSuccess(); @@ -260,20 +267,45 @@ export class AuthRoleController extends Controller { return newAttr; }); const before = structuredClone(record); - await Promise.all([ - this.authRoleRepo.save(record, { data: req }), - setLogDataDiff(req, { before, after: record }), - ...newAttrs.map((attr) => this.authRoleAttrRepo.save(attr)), - ]); - const redisClient = await this.redis.createClient({ - host: REDIS_HOST, - port: REDIS_PORT, - }); + const queryRunner = AppDataSource.createQueryRunner(); + await queryRunner.connect(); + await queryRunner.startTransaction(); - await redisClient.flushdb(function (err: any, succeeded: any) { - console.log(succeeded); // will be true if successfull - }); + try { + await queryRunner.manager.save(AuthRole, record); + await Promise.all( + newAttrs.map((attr) => queryRunner.manager.save(AuthRoleAttr, attr)) + ); + await queryRunner.commitTransaction(); + + setLogDataDiff(req, { before, after: record }); + } catch (error) { + await queryRunner.rollbackTransaction(); + console.error("Error saving auth role:", error); + throw new HttpError( + HttpStatusCode.INTERNAL_SERVER_ERROR, + "เกิดข้อผิดพลาดในการบันทึกข้อมูลบทบาท กรุณาลองใหม่ในภายหลัง" + ); + } finally { + await queryRunner.release(); + } + + let redisClient; + try { + redisClient = await this.redis.createClient({ + host: REDIS_HOST, + port: REDIS_PORT, + }); + + await redisClient.flushdb(function (err: any, succeeded: any) { + console.log(succeeded); // will be true if successfull + }); + } finally { + if (redisClient) { + redisClient.quit(); + } + } return new HttpSuccess(); } diff --git a/src/controllers/CommandController.ts b/src/controllers/CommandController.ts index 27d2671c..1ea05633 100644 --- a/src/controllers/CommandController.ts +++ b/src/controllers/CommandController.ts @@ -41,9 +41,9 @@ import { removeProfileInOrganize, setLogDataDiff, checkReturnCommandType, - checkExceptCommandType, checkCommandType, removePostMasterAct, + logPositionIsSelectedChange, } from "../interfaces/utils"; import { Position } from "../entities/Position"; import { PosMaster } from "../entities/PosMaster"; @@ -104,6 +104,12 @@ import { LeaveType } from "../entities/LeaveType"; import { KeycloakAttributeService } from "../services/KeycloakAttributeService"; import { reOrderCommandRecivesAndDelete } from "../services/CommandService"; import { RetirementService } from "../services/RetirementService"; +import { ExecuteOfficerProfileService } from "../services/ExecuteOfficerProfileService"; +import { ExecuteSalaryService } from "../services/ExecuteSalaryService"; +import { ExecuteSalaryCurrentService } from "../services/ExecuteSalaryCurrentService"; +import { ExecuteSalaryEmployeeCurrentService } from "../services/ExecuteSalaryEmployeeCurrentService"; +import { ExecuteSalaryLeaveService } from "../services/ExecuteSalaryLeaveService"; +import { ExecuteSalaryEmployeeLeaveService } from "../services/ExecuteSalaryEmployeeLeaveService"; import { promisify } from "util"; const REDIS_HOST = process.env.REDIS_HOST; const REDIS_PORT = process.env.REDIS_PORT; @@ -205,7 +211,13 @@ export class CommandController extends Controller { child4: null, }; if (request.user.role.includes("STAFF")) { - _data = await new permission().PermissionOrgList(request, "COMMAND"); + // #2523 STAFF + isDirector ให้ล้อสิทธิ์เหมือน CHILD + if (!isDirector) { + _data = await new permission().PermissionOrgList(request, "COMMAND"); + + } else { + _data = await new permission().PermissionIsDirectorOrgList(request, "COMMAND", isDirector); + } } if (isDirector || _data.privilege == "OWNER") { const profiles = await this.profileRepository @@ -336,9 +348,11 @@ export class CommandController extends Controller { .andWhere( new Brackets((qb) => { qb.where( - keyword != null && keyword != "" ? "command.commandNo LIKE :baseKeyword" : "1=1", + keyword != null && keyword != "" + ? "TRIM(CONCAT(COALESCE(command.shortName, ''), ' ', command.commandNo, '/', command.commandYear + 543)) LIKE :keyword" + : "1=1", { - baseKeyword: `%${baseKeyword}%`, + keyword: `%${keyword}%`, }, ) .orWhere(keyword != null && keyword != "" ? "command.issue LIKE :keyword" : "1=1", { @@ -365,7 +379,7 @@ export class CommandController extends Controller { const data = commands.map((_data) => ({ id: _data.id, - commandNo: _data.commandNo, + commandNo: `${_data.shortName ?? ""} ${_data.commandNo}`.trim(), commandYear: _data.commandYear, commandAffectDate: _data.commandAffectDate, commandExcecuteDate: _data.commandExcecuteDate, @@ -515,6 +529,7 @@ export class CommandController extends Controller { const _command = { id: command.id, status: command.status, + shortName: command.shortName ?? "", commandNo: command.commandNo, commandYear: command.commandYear, issue: command.issue, @@ -565,6 +580,34 @@ export class CommandController extends Controller { } const data = new Command(); Object.assign(data, { ...command, ...requestBody }); + + // ถ้าเป็น officer (isOfficer == true) ดึง orgRoot.shortName มาใช้ + const userProfile = await this.profileRepository.findOne({ + where: { keycloak: request.user.sub }, + relations: { + current_holders: { + orgRevision: true, + orgRoot: true, + orgChild1: true, + }, + }, + }); + + if (userProfile) { + const currentHolder = userProfile.current_holders?.find( + (x: any) => + x.orgRevision?.orgRevisionIsDraft === false && + x.orgRevision?.orgRevisionIsCurrent === true, + ); + + if (currentHolder && currentHolder.orgChild1?.isOfficer) { + data.shortName = + requestBody.isBangkok && requestBody.isBangkok === "BANGKOK" + ? "กทม." + : currentHolder.orgRoot?.orgRootShortName ?? "สนป."; + } + } + data.lastUpdateUserId = request.user.sub; data.lastUpdateFullName = request.user.name; data.lastUpdatedAt = new Date(); @@ -2373,9 +2416,9 @@ export class CommandController extends Controller { ? "" : Extension.ToThaiNumber(Extension.ToThaiYear(command.commandYear).toString()), commandExcecuteDate: - command.commandExcecuteDate == null + command.commandAffectDate == null ? "" - : Extension.ToThaiNumber(Extension.ToThaiFullDate2(command.commandExcecuteDate)), + : Extension.ToThaiNumber(Extension.ToThaiFullDate2(command.commandAffectDate)), operators: operators.length > 0 ? operators.map((x) => ({ @@ -2580,7 +2623,9 @@ export class CommandController extends Controller { const now = new Date(); let command = new Command(); let commandCode: string = ""; + let commandSysId: string = ""; let _null: any = null; + let userProfile: any = null; if ( requestBody.commandId != undefined && requestBody.commandId != null && @@ -2598,6 +2643,7 @@ export class CommandController extends Controller { if (!_command) { throw new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบคำสั่งนี้ในระบบ"); } + commandSysId = _command.commandType.commandSysId; commandCode = _command.commandType.code; command = _command; } else { @@ -2612,6 +2658,7 @@ export class CommandController extends Controller { if (!commandType) { throw new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบประเภทคำสั่งนี้ในระบบ"); } + commandSysId = commandType.commandSysId; commandCode = commandType.code; command.detailHeader = commandType.detailHeader; command.detailBody = commandType.detailBody; @@ -2640,6 +2687,37 @@ export class CommandController extends Controller { command.lastUpdateUserId = request.user.sub; command.lastUpdateFullName = request.user.name; command.lastUpdatedAt = now; + + // Query profile ครั้งเดียว ใช้ร่วมกันทั้ง shortName และ CommandOperator + userProfile = await this.profileRepository.findOne({ + where: { keycloak: request.user.sub }, + relations: { + posLevel: true, + posType: true, + current_holders: { + orgRevision: true, + orgRoot: true, + orgChild1: true, + orgChild2: true, + orgChild3: true, + orgChild4: true, + }, + }, + }); + + // เช็คถ้าไม่ใช่ กสจ. ดึง root.shortName มาปั๊ม + if (userProfile) { + const currentHolder = userProfile.current_holders?.find( + (x: any) => + x.orgRevision?.orgRevisionIsDraft === false && + x.orgRevision?.orgRevisionIsCurrent === true, + ); + + if (currentHolder && !currentHolder.orgChild1?.isOfficer) { + command.shortName = currentHolder.orgRoot?.orgRootShortName ?? null; + } + } + await this.commandRepository.save(command); } // insert commandOperator @@ -2648,24 +2726,28 @@ export class CommandController extends Controller { }); if (!checkCommandOperator) { if (request.user.sub) { - const profile = await this.profileRepository.findOne({ - where: { keycloak: request.user.sub }, - relations: { - posLevel: true, - posType: true, - current_holders: { - orgRevision: true, - orgRoot: true, - orgChild1: true, - orgChild2: true, - orgChild3: true, - orgChild4: true, + // ใช้ userProfile ที่ query ไปแล้วถ้ามี ถ้าไม่มีค่อย query ใหม่ + let profile = userProfile; + if (!profile) { + profile = await this.profileRepository.findOne({ + where: { keycloak: request.user.sub }, + relations: { + posLevel: true, + posType: true, + current_holders: { + orgRevision: true, + orgRoot: true, + orgChild1: true, + orgChild2: true, + orgChild3: true, + orgChild4: true, + }, }, - }, - }); + }); + } if (profile) { const currentHolder = profile!.current_holders?.find( - (x) => + (x: any) => x.orgRevision?.orgRevisionIsDraft === false && x.orgRevision?.orgRevisionIsCurrent === true, ); @@ -2721,7 +2803,7 @@ export class CommandController extends Controller { const path = commandTypePath(commandCode); if (path == null) throw new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบประเภทคำสั่งนี้ในระบบ"); - if (!["C-PM-26", "C-PM-25"].includes(commandCode)) { + if (commandSysId && commandSysId.toLocaleUpperCase().trim() !== "DISCIPLINE") { await new CallAPI() .PostData(request, path, { refIds: requestBody.persons.filter((x) => x.refId != null).map((x) => x.refId), @@ -3571,6 +3653,14 @@ export class CommandController extends Controller { return new HttpSuccess(); } + /** + * API สร้าง ProfileSalary ข้าราชการ + อัปเดตตำแหน่งปัจจุบัน (เปลี่ยนตำแหน่ง) + * + * Thin wrapper — เรียก ExecuteSalaryCurrentService (C-PM-03, 04, 05, 06, 07, 39, 47) + * ทั้ง endpoint นี้และ consumer ใน rabbitmq ใช้ service ตัวเดียวกัน (Linear Flow) + * + * @summary API สร้าง ProfileSalary ข้าราชการ + เปลี่ยนตำแหน่ง + */ @Post("excexute/salary-current") public async newSalaryAndUpdateCurrent( @Request() req: RequestWithUser, @@ -3587,8 +3677,12 @@ export class CommandController extends Controller { positionArea?: string | null; positionType: string | null; positionLevel: string | null; + positionTypeId?: string | null; + positionLevelId?: string | null; posmasterId: string; positionId: string; + posExecutiveId?: string | null; + positionField?: string | null; commandId?: string | null; orgRoot?: string | null; orgChild1?: string | null; @@ -3608,167 +3702,10 @@ export class CommandController extends Controller { }[]; }, ) { - let _posNumCodeSit: string = ""; - let _posNumCodeSitAbb: string = ""; - const _command = await this.commandRepository.findOne({ - where: { id: body.data.find((x) => x.commandId)?.commandId ?? "" }, + await new ExecuteSalaryCurrentService().executeSalaryCurrent(body.data, { + user: { sub: req.user.sub, name: req.user.name }, + req, }); - if (_command) { - if (_command?.isBangkok?.toLocaleUpperCase() == "OFFICE") { - const orgRootDeputy = await this.orgRootRepository.findOne({ - where: { - isDeputy: true, - orgRevision: { - orgRevisionIsCurrent: true, - orgRevisionIsDraft: false, - }, - }, - relations: ["orgRevision"], - }); - _posNumCodeSit = orgRootDeputy ? orgRootDeputy?.orgRootName : "สำนักปลัดกรุงเทพมหานคร"; - _posNumCodeSitAbb = orgRootDeputy ? orgRootDeputy?.orgRootShortName : "สนป."; - } else if (_command?.isBangkok?.toLocaleUpperCase() == "BANGKOK") { - _posNumCodeSit = "กรุงเทพมหานคร"; - _posNumCodeSitAbb = "กทม."; - } else { - let _profileAdmin = await this.profileRepository.findOne({ - where: { - keycloak: _command?.createdUserId.toString(), - current_holders: { - orgRevision: { - orgRevisionIsCurrent: true, - orgRevisionIsDraft: false, - }, - }, - }, - relations: ["current_holders", "current_holders.orgRevision", "current_holders.orgRoot"], - }); - _posNumCodeSit = - _profileAdmin?.current_holders.find((x) => x.orgRoot.orgRootName)?.orgRoot.orgRootName ?? - ""; - _posNumCodeSitAbb = - _profileAdmin?.current_holders.find((x) => x.orgRoot.orgRootShortName)?.orgRoot - .orgRootShortName ?? ""; - } - } - await Promise.all( - body.data.map(async (item) => { - const profile: any = await this.profileRepository.findOneBy({ id: item.profileId }); - if (!profile) { - throw new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบข้อมูลทะเบียนประวัตินี้"); - } - let _null: any = null; - const dest_item = await this.salaryRepo.findOne({ - where: { profileId: item.profileId }, - order: { order: "DESC" }, - }); - const before = null; - const data = new ProfileSalary(); - - const meta = { - order: dest_item == null ? 1 : dest_item.order + 1, - createdUserId: req.user.sub, - createdFullName: req.user.name, - lastUpdateUserId: req.user.sub, - lastUpdateFullName: req.user.name, - createdAt: new Date(), - lastUpdatedAt: new Date(), - }; - data.posNumCodeSit = _posNumCodeSit; - data.posNumCodeSitAbb = _posNumCodeSitAbb; - Object.assign(data, { ...item, ...meta }); - const history = new ProfileSalaryHistory(); - Object.assign(history, { ...data, id: undefined }); - await this.salaryRepo.save(data, { data: req }); - setLogDataDiff(req, { before, after: data }); - history.commandId = item.commandId ?? _null; - history.profileSalaryId = data.id; - await this.salaryHistoryRepo.save(history, { data: req }); - - const posMaster = await this.posMasterRepository.findOne({ - where: { id: item.posmasterId }, - relations: ["orgRoot", "orgChild1", "orgChild2", "orgChild3", "orgChild4"], - }); - if (posMaster == null) - throw new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบข้อมูลตำแหน่งนี้"); - - const posMasterOld = await this.posMasterRepository.findOne({ - where: { - current_holderId: item.profileId, - orgRevisionId: posMaster.orgRevisionId, - }, - }); - if (posMasterOld != null) { - posMasterOld.current_holderId = null; - posMasterOld.lastUpdatedAt = new Date(); - } - - const positionOld = await this.positionRepository.findOne({ - where: { - posMasterId: posMasterOld?.id, - positionIsSelected: true, - }, - }); - if (positionOld != null) { - positionOld.positionIsSelected = false; - await this.positionRepository.save(positionOld); - } - - const checkPosition = await this.positionRepository.find({ - where: { - posMasterId: item.posmasterId, - positionIsSelected: true, - }, - }); - if (checkPosition.length > 0) { - const clearPosition = checkPosition.map((positions) => ({ - ...positions, - positionIsSelected: false, - })); - await this.positionRepository.save(clearPosition); - } - - posMaster.current_holderId = item.profileId; - posMaster.lastUpdatedAt = new Date(); - // posMaster.conditionReason = _null; - // posMaster.isCondition = false; - if (posMasterOld != null) { - await this.posMasterRepository.save(posMasterOld); - await CreatePosMasterHistoryOfficer(posMasterOld.id, req); - } - await this.posMasterRepository.save(posMaster); - - const positionNew = await this.positionRepository.findOne({ - where: { - id: item.positionId, - posMasterId: item.posmasterId, - }, - relations: ["posExecutive"], - }); - // ถ้าไม่ใช่ตำแหน่งนั่งทับ (isSit = false) ถึงจะอัพเดทตำแหน่งในทะเบียนประวัติ - if (positionNew != null) { - positionNew.positionIsSelected = true; - // อัพเดท org และ posMasterNo ตลอดไม่ต้องดัก isSit - profile.posMasterNo = getPosMasterNo(posMaster); - profile.org = getOrgFullName(posMaster); - if (!posMaster.isSit) { - profile.posLevelId = positionNew.posLevelId; - profile.posTypeId = positionNew.posTypeId; - 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.amountSpecial = item.amountSpecial ?? null; - await this.profileRepository.save(profile); - await this.positionRepository.save(positionNew); - } - await CreatePosMasterHistoryOfficer(posMaster.id, req); - }), - ); - return new HttpSuccess(); } @@ -3806,165 +3743,21 @@ export class CommandController extends Controller { }[]; }, ) { - let _posNumCodeSit: string = ""; - let _posNumCodeSitAbb: string = ""; - const _command = await this.commandRepository.findOne({ - where: { id: body.data.find((x) => x.commandId)?.commandId ?? "" }, + await new ExecuteSalaryEmployeeCurrentService().executeSalaryEmployeeCurrent(body.data, { + user: { sub: req.user.sub, name: req.user.name }, + req, }); - if (_command) { - if (_command?.isBangkok?.toLocaleUpperCase() == "OFFICE") { - const orgRootDeputy = await this.orgRootRepository.findOne({ - where: { - isDeputy: true, - orgRevision: { - orgRevisionIsCurrent: true, - orgRevisionIsDraft: false, - }, - }, - relations: ["orgRevision"], - }); - _posNumCodeSit = orgRootDeputy ? orgRootDeputy?.orgRootName : "สำนักปลัดกรุงเทพมหานคร"; - _posNumCodeSitAbb = orgRootDeputy ? orgRootDeputy?.orgRootShortName : "สนป."; - } else if (_command?.isBangkok?.toLocaleUpperCase() == "BANGKOK") { - _posNumCodeSit = "กรุงเทพมหานคร"; - _posNumCodeSitAbb = "กทม."; - } else { - let _profileAdmin = await this.profileRepository.findOne({ - where: { - keycloak: _command?.createdUserId.toString(), - current_holders: { - orgRevision: { - orgRevisionIsCurrent: true, - orgRevisionIsDraft: false, - }, - }, - }, - relations: ["current_holders", "current_holders.orgRevision", "current_holders.orgRoot"], - }); - _posNumCodeSit = - _profileAdmin?.current_holders.find((x) => x.orgRoot.orgRootName)?.orgRoot.orgRootName ?? - ""; - _posNumCodeSitAbb = - _profileAdmin?.current_holders.find((x) => x.orgRoot.orgRootShortName)?.orgRoot - .orgRootShortName ?? ""; - } - } - await Promise.all( - body.data.map(async (item) => { - const profile: any = await this.profileEmployeeRepository.findOneBy({ id: item.profileId }); - if (!profile) { - throw new HttpError(HttpStatus.BAD_REQUEST, "ไม่พบ profile ดังกล่าว"); - } - - const dest_item = await this.salaryRepo.findOne({ - where: { profileEmployeeId: item.profileId }, - order: { order: "DESC" }, - }); - const before = null; - const data = new ProfileSalary(); - data.posNumCodeSit = _posNumCodeSit; - data.posNumCodeSitAbb = _posNumCodeSitAbb; - const meta = { - order: dest_item == null ? 1 : dest_item.order + 1, - createdUserId: req.user.sub, - createdFullName: req.user.name, - lastUpdateUserId: req.user.sub, - lastUpdateFullName: req.user.name, - createdAt: new Date(), - lastUpdatedAt: new Date(), - }; - - Object.assign(data, { - ...item, - ...meta, - profileEmployeeId: item.profileId, - profileId: undefined, - }); - const history = new ProfileSalaryHistory(); - Object.assign(history, { ...data, id: undefined }); - - await this.salaryRepo.save(data, { data: req }); - setLogDataDiff(req, { before, after: data }); - history.profileSalaryId = data.id; - await this.salaryHistoryRepo.save(history, { data: req }); - - const posMaster = await this.employeePosMasterRepository.findOne({ - where: { id: item.posmasterId }, - relations: ["orgRoot"], - }); - if (posMaster == null) - throw new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบข้อมูลตำแหน่งนี้"); - - const posMasterOld = await this.employeePosMasterRepository.findOne({ - where: { - current_holderId: item.profileId, - orgRevisionId: posMaster.orgRevisionId, - }, - }); - if (posMasterOld != null) { - posMasterOld.current_holderId = null; - posMasterOld.lastUpdatedAt = new Date(); - } - // if (posMasterOld != null) posMasterOld.next_holderId = null; - - const positionOld = await this.employeePositionRepository.findOne({ - where: { - posMasterId: posMasterOld?.id, - positionIsSelected: true, - }, - }); - if (positionOld != null) { - positionOld.positionIsSelected = false; - await this.employeePositionRepository.save(positionOld); - } - - const checkPosition = await this.employeePositionRepository.find({ - where: { - posMasterId: item.posmasterId, - positionIsSelected: true, - }, - }); - if (checkPosition.length > 0) { - const clearPosition = checkPosition.map((positions) => ({ - ...positions, - positionIsSelected: false, - })); - await this.employeePositionRepository.save(clearPosition); - } - - posMaster.current_holderId = item.profileId; - posMaster.lastUpdatedAt = new Date(); - posMaster.next_holderId = null; - if (posMasterOld != null) { - await this.employeePosMasterRepository.save(posMasterOld); - await CreatePosMasterHistoryEmployee(posMasterOld.id, req); - } - await this.employeePosMasterRepository.save(posMaster); - const positionNew = await this.employeePositionRepository.findOne({ - where: { - id: item.positionId, - posMasterId: item.posmasterId, - }, - }); - if (positionNew != null) { - positionNew.positionIsSelected = true; - profile.posLevelId = positionNew.posLevelId; - profile.posTypeId = positionNew.posTypeId; - profile.position = positionNew.positionName; - profile.employeeOc = posMaster?.orgRoot?.orgRootName ?? null; - profile.positionEmployeePositionId = positionNew.positionName; - profile.amount = item.amount ?? null; - profile.amountSpecial = item.amountSpecial ?? null; - await this.profileEmployeeRepository.save(profile); - await this.employeePositionRepository.save(positionNew); - } - await CreatePosMasterHistoryEmployee(posMaster.id, req); - }), - ); - return new HttpSuccess(); } + /** + * API สร้าง ProfileSalary ข้าราชการ + handle leave/กลับเข้าราชการ + * + * Thin wrapper — เรียก ExecuteSalaryLeaveService (C-PM-08, 09, 17, 18, 41, 48) + * ทั้ง endpoint นี้และ consumer ใน rabbitmq ใช้ service ตัวเดียวกัน (Linear Flow) + * + * @summary API สร้าง ProfileSalary ข้าราชการ + leave/กลับเข้าราชการ + */ @Post("excexute/salary-leave") public async newSalaryAndUpdateLeave( @Request() req: RequestWithUser, @@ -3984,6 +3777,8 @@ export class CommandController extends Controller { isLeave: boolean; leaveReason?: string | null; dateLeave?: Date | null; + posExecutiveId?: string | null; + positionField?: string | null; commandId?: string | null; isGovernment?: boolean | null; orgRoot?: string | null; @@ -4001,6 +3796,7 @@ export class CommandController extends Controller { commandCode?: string | null; commandName?: string | null; remark: string | null; + positionId?: string | null; positionTypeNew?: string | null; positionLevelNew?: string | null; positionNameNew?: string | null; @@ -4018,365 +3814,10 @@ export class CommandController extends Controller { }[]; }, ) { - const roleKeycloak = await this.roleKeycloakRepo.findOne({ - where: { name: Like("USER") }, + await new ExecuteSalaryLeaveService().executeSalaryLeave(body.data, { + user: { sub: req.user.sub, name: req.user.name }, + req, }); - let _posNumCodeSit: string = ""; - let _posNumCodeSitAbb: string = ""; - const _command = await this.commandRepository.findOne({ - relations: ["commandType"], - where: { id: body.data.find((x) => x.commandId)?.commandId ?? "" }, - }); - if (_command) { - if (_command?.isBangkok?.toLocaleUpperCase() == "OFFICE") { - const orgRootDeputy = await this.orgRootRepository.findOne({ - where: { - isDeputy: true, - orgRevision: { - orgRevisionIsCurrent: true, - orgRevisionIsDraft: false, - }, - }, - relations: ["orgRevision"], - }); - _posNumCodeSit = orgRootDeputy ? orgRootDeputy?.orgRootName : "สำนักปลัดกรุงเทพมหานคร"; - _posNumCodeSitAbb = orgRootDeputy ? orgRootDeputy?.orgRootShortName : "สนป."; - } else if (_command?.isBangkok?.toLocaleUpperCase() == "BANGKOK") { - _posNumCodeSit = "กรุงเทพมหานคร"; - _posNumCodeSitAbb = "กทม."; - } else { - let _profileAdmin = await this.profileRepository.findOne({ - where: { - keycloak: _command?.createdUserId.toString(), - current_holders: { - orgRevision: { - orgRevisionIsCurrent: true, - orgRevisionIsDraft: false, - }, - }, - }, - relations: ["current_holders", "current_holders.orgRevision", "current_holders.orgRoot"], - }); - _posNumCodeSit = - _profileAdmin?.current_holders.find((x) => x.orgRoot.orgRootName)?.orgRoot.orgRootName ?? - ""; - _posNumCodeSitAbb = - _profileAdmin?.current_holders.find((x) => x.orgRoot.orgRootShortName)?.orgRoot - .orgRootShortName ?? ""; - } - } - const today = new Date().setHours(0, 0, 0, 0); - await Promise.all( - body.data.map(async (item) => { - const profile = await this.profileRepository.findOne({ - where: { id: item.profileId }, - // relations: ["roleKeycloaks"], - relations: { - roleKeycloaks: true, - posType: true, - posLevel: true, - }, - }); - if (!profile) { - throw new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบข้อมูลทะเบียนประวัตินี้"); - } - //ลบตำแหน่งที่รักษาการแทน - const code = _command?.commandType?.code; - if (code && ["C-PM-08", "C-PM-17", "C-PM-18"].includes(code)) { - removePostMasterAct(profile.id); - } - //ออกคำสั่งยกเลิกลาออก ลบเฉพาะคนที่ขอยกเลิกลาออก - else if (item.resignId && code && ["C-PM-41"].includes(code)) { - const commandResign = await this.commandReciveRepository.findOne({ - where: { refId: item.resignId }, - relations: { command: true }, - }); - const executeDate = commandResign - ? new Date(commandResign.command.commandExcecuteDate).setHours(0, 0, 0, 0) - : today; - if ( - commandResign && - _command.status !== "REPORTED" && - (_command.status !== "WAITING" || today < executeDate) - ) { - await reOrderCommandRecivesAndDelete(commandResign!.id); - } - } - let _commandYear = item.commandYear; - if (item.commandYear) { - _commandYear = item.commandYear > 2500 ? item.commandYear : item.commandYear + 543; - } - const returnWork = await checkReturnCommandType(String(item.commandId)); - const dest_item = await this.salaryRepo.findOne({ - where: { profileId: item.profileId }, - order: { order: "DESC" }, - }); - const before = null; - const data = new ProfileSalary(); - data.posNumCodeSit = _posNumCodeSit; - data.posNumCodeSitAbb = _posNumCodeSitAbb; - data.dateGovernment = item.commandDateAffect ?? new Date(); - data.order = dest_item == null ? 1 : dest_item.order + 1; - const meta = { - createdUserId: req.user.sub, - createdFullName: req.user.name, - lastUpdateUserId: req.user.sub, - lastUpdateFullName: req.user.name, - createdAt: new Date(), - lastUpdatedAt: new Date(), - }; - if (!returnWork) { - Object.assign(data, { ...item, ...meta }); - const history = new ProfileSalaryHistory(); - Object.assign(history, { ...data, id: undefined }); - await this.salaryRepo.save(data, { data: req }); - setLogDataDiff(req, { before, after: data }); - history.profileSalaryId = data.id; - await this.salaryHistoryRepo.save(history, { data: req }); - } - const _null: any = null; - profile.isLeave = item.isLeave; - profile.leaveReason = item.leaveReason ?? _null; - profile.dateLeave = item.dateLeave ?? _null; - profile.lastUpdateUserId = req.user.sub; - profile.lastUpdateFullName = req.user.name; - profile.lastUpdatedAt = new Date(); - const clearProfile = await checkCommandType(String(item.commandId)); - - //ปั๊มประวัติก่อนลบตำแหน่ง - const curRevision = await this.orgRevisionRepo.findOne({ - where: { orgRevisionIsCurrent: true, orgRevisionIsDraft: false }, - }); - let orgRootRef = null; - let orgChild1Ref = null; - let orgChild2Ref = null; - let orgChild3Ref = null; - let orgChild4Ref = null; - if (curRevision) { - const curPosMaster = await this.posMasterRepository.findOne({ - where: { - current_holderId: profile.id, - orgRevisionId: curRevision.id, - }, - relations: { - orgRoot: true, - orgChild1: true, - orgChild2: true, - orgChild3: true, - orgChild4: true, - }, - }); - orgRootRef = curPosMaster?.orgRoot ?? null; - orgChild1Ref = curPosMaster?.orgChild1 ?? null; - orgChild2Ref = curPosMaster?.orgChild2 ?? null; - orgChild3Ref = curPosMaster?.orgChild3 ?? null; - orgChild4Ref = curPosMaster?.orgChild4 ?? null; - if (curPosMaster && clearProfile.LeaveType != "RETIRE_OUT_EMP") { - await CreatePosMasterHistoryOfficer(curPosMaster.id, req, "DELETE"); - } - } - - //ลบตำแหน่ง - if (item.isLeave == true) { - await removeProfileInOrganize(profile.id, "OFFICER"); - } - if (clearProfile.status) { - if (profile.keycloak != null && profile.keycloak != "" && profile.isDelete === false) { - const delUserKeycloak = await deleteUser(profile.keycloak); - if (delUserKeycloak) { - // Task #228 - // profile.keycloak = _null; - profile.roleKeycloaks = []; - profile.isActive = false; - profile.isDelete = true; - } - } - profile.leaveCommandId = item.commandId ?? _null; - profile.leaveCommandNo = `${item.commandNo}/${_commandYear}`; - profile.leaveRemark = clearProfile.leaveRemark ?? _null; - profile.leaveDate = item.commandDateAffect ?? _null; - profile.leaveType = clearProfile.LeaveType ?? _null; - //ออกจากราชการ ไม่ต้องลบตำแหน่งในทะเบียน (issue #1516) - // profile.position = _null; - // profile.posTypeId = _null; - // profile.posLevelId = _null; - } - - if (item.isGovernment == true) { - if (returnWork) { - //ปลดตำแหน่งเดิมที่ไม่ถูกปลดออกจากกิ่งครั้งเมื่อออกคำสั่งพักราชการหรือออกราชการไว้ - await removeProfileInOrganize(profile.id, "OFFICER"); - //ปั๊มตำแหน่งใหม่ - const posMaster = await this.posMasterRepository.findOne({ - where: { id: item.posmasterId?.toString() }, - }); - - if (posMaster) { - const checkPosition = await this.positionRepository.find({ - where: { - posMasterId: posMaster.id, - positionIsSelected: true, - }, - }); - if (checkPosition.length > 0) { - const clearPosition = checkPosition.map((positions) => ({ - ...positions, - positionIsSelected: false, - })); - await this.positionRepository.save(clearPosition); - } - posMaster.current_holderId = profile.id; - posMaster.lastUpdatedAt = new Date(); - // posMaster.conditionReason = _null; - // posMaster.isCondition = false; - await this.posMasterRepository.save(posMaster); - const positionNew = await this.positionRepository.findOne({ - where: { - posMasterId: posMaster.id, - }, - }); - if (positionNew) { - positionNew.positionIsSelected = true; - await this.positionRepository.save(positionNew, { data: req }); - } - await CreatePosMasterHistoryOfficer(posMaster.id, req); - } - const newMapProfileSalary = { - profileId: profile.id, - commandId: item.commandId, - positionName: item.positionNameNew ?? null, - positionType: item.posTypeNameNew ?? null, - positionLevel: item.posLevelNameNew ?? null, - amount: item.amount ? item.amount : null, - positionSalaryAmount: item.positionSalaryAmount ? item.positionSalaryAmount : null, - amountSpecial: item.amountSpecial ? item.amountSpecial : null, - mouthSalaryAmount: item.mouthSalaryAmount ? item.mouthSalaryAmount : null, - posNo: item.posNoNew, - posNoAbb: item.posNoAbbNew, - orgRoot: item.orgRootNew, - orgChild1: item.orgChild1New, - orgChild2: item.orgChild2New, - orgChild3: item.orgChild3New, - orgChild4: item.orgChild4New, - isGovernment: item.isGovernment, - commandNo: item.commandNo, - commandYear: item.commandYear, - commandDateAffect: item.commandDateAffect, - commandDateSign: item.commandDateSign, - commandCode: item.commandCode, - commandName: item.commandName, - remark: item.remark, - }; - Object.assign(data, { ...newMapProfileSalary, ...meta }); - const history = new ProfileSalaryHistory(); - Object.assign(history, { ...data, id: undefined }); - await this.salaryRepo.save(data); - history.profileSalaryId = data.id; - await this.salaryHistoryRepo.save(history); - profile.leaveReason = _null; - profile.leaveCommandId = _null; - profile.leaveCommandNo = _null; - profile.leaveRemark = _null; - profile.leaveDate = _null; - profile.leaveType = _null; - profile.position = item.positionNameNew ?? _null; - profile.posTypeId = item.positionTypeNew ?? _null; - profile.posLevelId = item.positionLevelNew ?? _null; - } - let userKeycloakId; - const checkUser = await getUserByUsername(profile.citizenId); - //ถ้ายังไม่มี user keycloak ให้สร้างใหม่ - if (checkUser.length == 0) { - let password = profile.citizenId; - if (profile.birthDate != null) { - // const gregorianYear = profile.birthDate.getFullYear() + 543; - - // const formattedDate = - // profile.birthDate.toISOString().slice(8, 10) + - // profile.birthDate.toISOString().slice(5, 7) + - // gregorianYear; - // password = formattedDate; - const _date = new Date(profile.birthDate.toDateString()) - .getDate() - .toString() - .padStart(2, "0"); - const _month = (new Date(profile.birthDate.toDateString()).getMonth() + 1) - .toString() - .padStart(2, "0"); - const _year = new Date(profile.birthDate.toDateString()).getFullYear() + 543; - password = `${_date}${_month}${_year}`; - } - userKeycloakId = await createUser(profile.citizenId, password, { - firstName: profile.firstName, - lastName: profile.lastName, - }); - const list = await getRoles(); - let result = false; - if (Array.isArray(list) && userKeycloakId) { - result = await addUserRoles( - userKeycloakId, - list - .filter((v) => v.name === "USER") - .map((x) => ({ - id: x.id, - name: x.name, - })), - ); - } - profile.roleKeycloaks = result && roleKeycloak ? [roleKeycloak] : []; - profile.keycloak = - userKeycloakId && typeof userKeycloakId === "string" ? userKeycloakId : ""; - } - //ถ้ามีอยู่แล้วให้ใช้อันเดิม - else { - const rolesData = await getRoleMappings(checkUser[0].id); - if (rolesData) { - const _roleKeycloak = await this.roleKeycloakRepo.find({ - where: { name: In(rolesData.map((x: any) => x.name)) }, - }); - profile.roleKeycloaks = - _roleKeycloak && _roleKeycloak.length > 0 ? _roleKeycloak : []; - } - profile.keycloak = checkUser[0].id; - } - profile.amount = item.amount ?? _null; - profile.amountSpecial = item.amountSpecial ?? _null; - profile.isActive = true; - profile.isDelete = false; - } - await this.profileRepository.save(profile); - - // if (profile.id) { - // await this.keycloakAttributeService.clearOrgDnaAttributes( - // [profile.id], - // "PROFILE", - // ); - // } - - // update user attribute in keycloak - await updateUserAttributes(profile.keycloak ?? "", { - profileId: [profile.id], - prefix: [profile.prefix || ""], - }); - - // Task #2190 - if (code && ["C-PM-17", "C-PM-18"].includes(code)) { - let organizeName = ""; - if (orgRootRef) { - const names = [ - orgChild4Ref?.orgChild4Name, - orgChild3Ref?.orgChild3Name, - orgChild2Ref?.orgChild2Name, - orgChild1Ref?.orgChild1Name, - orgRootRef?.orgRootName, - ].filter(Boolean); - organizeName = names.join(" "); - } - - } - }), - ); - return new HttpSuccess(); } @@ -4420,212 +3861,10 @@ export class CommandController extends Controller { }[]; }, ) { - let _posNumCodeSit: string = ""; - let _posNumCodeSitAbb: string = ""; - const _command = await this.commandRepository.findOne({ - where: { id: body.data.find((x) => x.commandId)?.commandId ?? "" }, - relations: { commandType: true }, + await new ExecuteSalaryEmployeeLeaveService().executeSalaryEmployeeLeave(body.data, { + user: { sub: req.user.sub, name: req.user.name }, + req, }); - if (_command) { - if (_command?.isBangkok?.toLocaleUpperCase() == "OFFICE") { - const orgRootDeputy = await this.orgRootRepository.findOne({ - where: { - isDeputy: true, - orgRevision: { - orgRevisionIsCurrent: true, - orgRevisionIsDraft: false, - }, - }, - relations: ["orgRevision"], - }); - _posNumCodeSit = orgRootDeputy ? orgRootDeputy?.orgRootName : "สำนักปลัดกรุงเทพมหานคร"; - _posNumCodeSitAbb = orgRootDeputy ? orgRootDeputy?.orgRootShortName : "สนป."; - } else if (_command?.isBangkok?.toLocaleUpperCase() == "BANGKOK") { - _posNumCodeSit = "กรุงเทพมหานคร"; - _posNumCodeSitAbb = "กทม."; - } else { - let _profileAdmin = await this.profileRepository.findOne({ - where: { - keycloak: _command?.createdUserId.toString(), - current_holders: { - orgRevision: { - orgRevisionIsCurrent: true, - orgRevisionIsDraft: false, - }, - }, - }, - relations: ["current_holders", "current_holders.orgRevision", "current_holders.orgRoot"], - }); - _posNumCodeSit = - _profileAdmin?.current_holders.find((x) => x.orgRoot.orgRootName)?.orgRoot.orgRootName ?? - ""; - _posNumCodeSitAbb = - _profileAdmin?.current_holders.find((x) => x.orgRoot.orgRootShortName)?.orgRoot - .orgRootShortName ?? ""; - } - } - const today = new Date().setHours(0, 0, 0, 0); - await Promise.all( - body.data.map(async (item) => { - const profile = await this.profileEmployeeRepository.findOne({ - where: { id: item.profileId }, - // relations: ["roleKeycloaks"], - relations: { - roleKeycloaks: true, - posType: true, - posLevel: true, - }, - }); - if (!profile) { - throw new HttpError(HttpStatus.BAD_REQUEST, "ไม่พบ profile ดังกล่าว"); - } - const code = _command?.commandType?.code; - //ออกคำสั่งยกเลิกลาออก ลบเฉพาะคนที่ขอยกเลิกลาออก - if (item.resignId && code && ["C-PM-42"].includes(code)) { - const commandResign = await this.commandReciveRepository.findOne({ - where: { refId: item.resignId }, - relations: { command: true }, - }); - const executeDate = commandResign - ? new Date(commandResign.command.commandExcecuteDate).setHours(0, 0, 0, 0) - : today; - if ( - commandResign && - _command.status !== "REPORTED" && - (_command.status !== "WAITING" || today < executeDate) - ) { - await reOrderCommandRecivesAndDelete(commandResign!.id); - } - } - let _commandYear = item.commandYear; - if (item.commandYear) { - _commandYear = item.commandYear > 2500 ? item.commandYear : item.commandYear + 543; - } - const dest_item = await this.salaryRepo.findOne({ - where: { profileEmployeeId: item.profileId }, - order: { order: "DESC" }, - }); - const before = null; - const data = new ProfileSalary(); - data.posNumCodeSit = _posNumCodeSit; - data.posNumCodeSitAbb = _posNumCodeSitAbb; - const meta = { - order: dest_item == null ? 1 : dest_item.order + 1, - createdUserId: req.user.sub, - createdFullName: req.user.name, - lastUpdateUserId: req.user.sub, - lastUpdateFullName: req.user.name, - createdAt: new Date(), - lastUpdatedAt: new Date(), - }; - - Object.assign(data, { - ...item, - ...meta, - profileEmployeeId: item.profileId, - profileId: undefined, - }); - const history = new ProfileSalaryHistory(); - Object.assign(history, { ...data, id: undefined }); - data.dateGovernment = item.commandDateAffect ?? meta.createdAt; - await this.salaryRepo.save(data, { data: req }); - setLogDataDiff(req, { before, after: data }); - history.profileSalaryId = data.id; - await this.salaryHistoryRepo.save(history, { data: req }); - - const _null: any = null; - profile.isLeave = item.isLeave; - profile.leaveReason = item.leaveReason ?? _null; - profile.dateLeave = item.dateLeave ?? _null; - profile.lastUpdateUserId = req.user.sub; - profile.lastUpdateFullName = req.user.name; - profile.lastUpdatedAt = new Date(); - // บันทึกประวัติก่อนลบตำแหน่ง - const clearProfile = await checkCommandType(String(item.commandId)); - const curRevision = await this.orgRevisionRepo.findOne({ - where: { orgRevisionIsCurrent: true, orgRevisionIsDraft: false }, - }); - let orgRootRef = null; - let orgChild1Ref = null; - let orgChild2Ref = null; - let orgChild3Ref = null; - let orgChild4Ref = null; - if (curRevision) { - const curPosMaster = await this.employeePosMasterRepository.findOne({ - where: { - current_holderId: profile.id, - orgRevisionId: curRevision.id, - }, - relations: { - orgRoot: true, - orgChild1: true, - orgChild2: true, - orgChild3: true, - orgChild4: true, - }, - }); - orgRootRef = curPosMaster?.orgRoot ?? null; - orgChild1Ref = curPosMaster?.orgChild1 ?? null; - orgChild2Ref = curPosMaster?.orgChild2 ?? null; - orgChild3Ref = curPosMaster?.orgChild3 ?? null; - orgChild4Ref = curPosMaster?.orgChild4 ?? null; - if (curPosMaster) { - await CreatePosMasterHistoryEmployee(curPosMaster.id, req, "DELETE"); - } - } - - // ลบตำแหน่ง - if (item.isLeave == true) { - await removeProfileInOrganize(profile.id, "EMPLOYEE"); - } - - if (clearProfile.status) { - if (profile.keycloak != null && profile.keycloak != "" && profile.isDelete === false) { - const delUserKeycloak = await deleteUser(profile.keycloak); - if (delUserKeycloak) { - // Task #228 - // profile.keycloak = _null; - profile.roleKeycloaks = []; - profile.isActive = false; - profile.isDelete = true; - } - } - profile.leaveCommandId = item.commandId ?? _null; - profile.leaveCommandNo = `${item.commandNo}/${_commandYear}`; - profile.leaveRemark = clearProfile.leaveRemark ?? _null; - profile.leaveDate = item.commandDateAffect ?? _null; - profile.leaveType = clearProfile.LeaveType ?? _null; - //ออกจากราชการ ไม่ต้องลบตำแหน่งในทะเบียน (issue #1516) - // profile.position = _null; - // profile.posTypeId = _null; - // profile.posLevelId = _null; - } - await this.profileEmployeeRepository.save(profile); - - // if (profile.id) { - // await this.keycloakAttributeService.clearOrgDnaAttributes( - // [profile.id], - // "PROFILE_EMPLOYEE", - // ); - // } - - // Task #2190 - if (code && ["C-PM-23", "C-PM-43"].includes(code)) { - let organizeName = ""; - if (orgRootRef) { - const names = [ - orgChild4Ref?.orgChild4Name, - orgChild3Ref?.orgChild3Name, - orgChild2Ref?.orgChild2Name, - orgChild1Ref?.orgChild1Name, - orgRootRef?.orgRootName, - ].filter(Boolean); - organizeName = names.join(" "); - } - } - }), - ); - return new HttpSuccess(); } @@ -4671,221 +3910,10 @@ export class CommandController extends Controller { }[]; }, ) { - let _posNumCodeSit: string = ""; - let _posNumCodeSitAbb: string = ""; - const _command = await this.commandRepository.findOne({ - relations: ["commandType"], - where: { id: body.data.find((x) => x.commandId)?.commandId ?? "" }, + await new ExecuteSalaryService().executeSalary(body.data, { + user: { sub: req.user.sub, name: req.user.name }, + req, }); - if (_command) { - if (_command?.isBangkok?.toLocaleUpperCase() == "OFFICE") { - const orgRootDeputy = await this.orgRootRepository.findOne({ - where: { - isDeputy: true, - orgRevision: { - orgRevisionIsCurrent: true, - orgRevisionIsDraft: false, - }, - }, - relations: ["orgRevision"], - }); - _posNumCodeSit = orgRootDeputy ? orgRootDeputy?.orgRootName : "สำนักปลัดกรุงเทพมหานคร"; - _posNumCodeSitAbb = orgRootDeputy ? orgRootDeputy?.orgRootShortName : "สนป."; - } else if (_command?.isBangkok?.toLocaleUpperCase() == "BANGKOK") { - _posNumCodeSit = "กรุงเทพมหานคร"; - _posNumCodeSitAbb = "กทม."; - } else { - let _profileAdmin = await this.profileRepository.findOne({ - where: { - keycloak: _command?.createdUserId.toString(), - current_holders: { - orgRevision: { - orgRevisionIsCurrent: true, - orgRevisionIsDraft: false, - }, - }, - }, - relations: ["current_holders", "current_holders.orgRevision", "current_holders.orgRoot"], - }); - _posNumCodeSit = - _profileAdmin?.current_holders.find((x) => x.orgRoot.orgRootName)?.orgRoot.orgRootName ?? - ""; - _posNumCodeSitAbb = - _profileAdmin?.current_holders.find((x) => x.orgRoot.orgRootShortName)?.orgRoot - .orgRootShortName ?? ""; - } - } - await Promise.all( - body.data.map(async (item) => { - const profile: any = await this.profileRepository.findOne({ - where: { id: item.profileId }, - // relations: ["roleKeycloaks"], - relations: { - roleKeycloaks: true, - posType: true, - posLevel: true, - }, - }); - if (!profile) { - throw new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบข้อมูลทะเบียนประวัตินี้"); - } - const posMaster: any = await this.posMasterRepository.findOne({ - where: { - current_holderId: item.profileId, - orgRevision: { - orgRevisionIsCurrent: true, - orgRevisionIsDraft: false, - }, - }, - relations: { - orgRevision: true, - orgRoot: true, - orgChild1: true, - orgChild2: true, - orgChild3: true, - orgChild4: true, - }, - }); - - const orgRevisionRef = posMaster ? posMaster.id : null; - const orgRootRef = orgRevisionRef?.orgRoot ?? null; - const orgChild1Ref = orgRevisionRef?.orgChild1 ?? null; - const orgChild2Ref = orgRevisionRef?.orgChild2 ?? null; - const orgChild3Ref = orgRevisionRef?.orgChild3 ?? null; - const orgChild4Ref = orgRevisionRef?.orgChild4 ?? null; - - //ลบตำแหน่งที่รักษาการแทน - const code = _command?.commandType?.code; - if (code && ["C-PM-13"].includes(code)) { - removePostMasterAct(profile.id); - } - - let _commandYear = item.commandYear; - if (item.commandYear) { - _commandYear = item.commandYear > 2500 ? item.commandYear : item.commandYear + 543; - } - const dest_item = await this.salaryRepo.findOne({ - where: { profileId: item.profileId }, - order: { order: "DESC" }, - }); - const before = null; - const data = new ProfileSalary(); - data.posNumCodeSit = _posNumCodeSit; - data.posNumCodeSitAbb = _posNumCodeSitAbb; - const meta = { - order: dest_item == null ? 1 : dest_item.order + 1, - createdUserId: req.user.sub, - createdFullName: req.user.name, - lastUpdateUserId: req.user.sub, - lastUpdateFullName: req.user.name, - createdAt: new Date(), - lastUpdatedAt: new Date(), - }; - if (item.isLeave != undefined && item.isLeave == true) { - await CreatePosMasterHistoryOfficer(orgRevisionRef, req, "DELETE"); - await removeProfileInOrganize(profile.id, "OFFICER"); - } - const clearProfile = await checkCommandType(String(item.commandId)); - const _null: any = null; - if (clearProfile.status) { - if (profile.keycloak != null && profile.keycloak != "" && profile.isDelete === false) { - const delUserKeycloak = await deleteUser(profile.keycloak); - if (delUserKeycloak) { - // Task #228 - // profile.keycloak = _null; - profile.roleKeycloaks = []; - profile.isActive = false; - profile.isDelete = true; - } - } - profile.isLeave = item.isLeave; - profile.leaveCommandId = item.commandId ?? _null; - profile.leaveCommandNo = `${item.commandNo}/${_commandYear}`; - profile.leaveRemark = clearProfile.leaveRemark ?? _null; - profile.leaveDate = item.commandDateAffect ?? _null; - profile.leaveType = clearProfile.LeaveType ?? _null; - //ออกจากราชการ ไม่ต้องลบตำแหน่งในทะเบียน (issue #1516) - // profile.position = _null; - // profile.posTypeId = _null; - // profile.posLevelId = _null; - profile.leaveReason = item.leaveReason ?? _null; - profile.dateLeave = item.dateLeave ?? _null; - profile.amount = item.amount ?? _null; - profile.amountSpecial = item.amountSpecial ?? _null; - await this.profileRepository.save(profile, { data: req }); - - // if (profile.id) { - // await this.keycloakAttributeService.clearOrgDnaAttributes( - // [profile.id], - // "PROFILE", - // ); - // } - } - Object.assign(data, { ...item, ...meta }); - const history = new ProfileSalaryHistory(); - Object.assign(history, { ...data, id: undefined }); - - await this.salaryRepo.save(data, { data: req }); - setLogDataDiff(req, { before, after: data }); - history.profileSalaryId = data.id; - await this.salaryHistoryRepo.save(history, { data: req }); - - if (_command) { - /* - const command = await this.commandRepository.findOne({ - where: { id: item.commandId }, - relations: ["commandType"], - }); - */ - if (["C-PM-15", "C-PM-16"].includes(_command.commandType.code)) { - // ประวัติคำสั่งให้ช่วยราชการ - const dataAssis = new ProfileAssistance(); - - const metaAssis = { - profileId: item.profileId, - agency: item.officerOrg, - dateStart: item.dateStart, - dateEnd: item.dateEnd, - commandNo: `${item.commandNo}/${_commandYear}`, - commandName: item.commandName, - refId: item.refId, - refCommandDate: new Date(), - commandId: item.commandId, - createdUserId: req.user.sub, - createdFullName: req.user.name, - lastUpdateUserId: req.user.sub, - lastUpdateFullName: req.user.name, - createdAt: new Date(), - lastUpdatedAt: new Date(), - status: _command.commandType.code == "C-PM-15" ? "PENDING" : "DONE", - }; - - Object.assign(dataAssis, metaAssis); - const historyAssis = new ProfileAssistanceHistory(); - Object.assign(historyAssis, { ...dataAssis, id: undefined }); - - await this.assistanceRepository.save(dataAssis); - historyAssis.profileAssistanceId = dataAssis.id; - await this.assistanceHistoryRepository.save(historyAssis); - } - // Task #2190 - else if (_command.commandType.code == "C-PM-13") { - let organizeName = ""; - if (orgRootRef) { - const names = [ - orgChild4Ref?.orgChild4Name, - orgChild3Ref?.orgChild3Name, - orgChild2Ref?.orgChild2Name, - orgChild1Ref?.orgChild1Name, - orgRootRef?.orgRootName, - ].filter(Boolean); - organizeName = names.join(" "); - } - } - } - }), - ); - return new HttpSuccess(); } @@ -5221,6 +4249,7 @@ export class CommandController extends Controller { date: item.commandDateAffect, refCommandDate: item.commandDateSign, refCommandNo: `${item.commandNo}/${item.commandYear}`, + refCommandId: item.commandId, createdUserId: req.user.sub, createdFullName: req.user.name, lastUpdateUserId: req.user.sub, @@ -5254,20 +4283,10 @@ export class CommandController extends Controller { _profile.lastUpdateFullName = req.user.name; _profile.lastUpdatedAt = new Date(); if (item.isLeave == true) { - const exceptClear = await checkExceptCommandType(String(item.commandId)); - if (exceptClear.status) { - _profile.leaveReason = item.leaveReason ?? _null; - _profile.leaveCommandId = item.commandId ?? _null; - _profile.leaveCommandNo = `${item.commandNo}/${_commandYear}`; - _profile.leaveRemark = exceptClear.leaveRemark ?? _null; - _profile.leaveDate = item.commandDateAffect ?? _null; - _profile.leaveType = exceptClear.LeaveType ?? _null; - } else { - if (orgRevisionRef) { - await CreatePosMasterHistoryOfficer(orgRevisionRef.id, req, "DELETE"); - } - await removeProfileInOrganize(_profile.id, "OFFICER"); + if (orgRevisionRef) { + await CreatePosMasterHistoryOfficer(orgRevisionRef.id, req, "DELETE"); } + await removeProfileInOrganize(_profile.id, "OFFICER"); } const clearProfile = await checkCommandType(String(item.commandId)); if (clearProfile.status) { @@ -5446,32 +4465,22 @@ export class CommandController extends Controller { _profile.lastUpdateFullName = req.user.name; _profile.lastUpdatedAt = new Date(); if (item.isLeave == true) { - const exceptClear = await checkExceptCommandType(String(item.commandId)); - if (exceptClear.status) { - _profile.leaveReason = item.leaveReason ?? _null; - _profile.leaveCommandId = item.commandId ?? _null; - _profile.leaveCommandNo = `${item.commandNo}/${_commandYear}`; - _profile.leaveRemark = exceptClear.leaveRemark ?? _null; - _profile.leaveDate = item.commandDateAffect ?? _null; - _profile.leaveType = exceptClear.LeaveType ?? _null; - } else { - // บันทึกประวัติก่อนลบตำแหน่ง - const curRevision = await this.orgRevisionRepo.findOne({ - where: { orgRevisionIsCurrent: true, orgRevisionIsDraft: false }, + // บันทึกประวัติก่อนลบตำแหน่ง + const curRevision = await this.orgRevisionRepo.findOne({ + where: { orgRevisionIsCurrent: true, orgRevisionIsDraft: false }, + }); + if (curRevision) { + const curPosMaster = await this.employeePosMasterRepository.findOne({ + where: { + current_holderId: _profile.id, + orgRevisionId: curRevision.id, + }, }); - if (curRevision) { - const curPosMaster = await this.employeePosMasterRepository.findOne({ - where: { - current_holderId: _profile.id, - orgRevisionId: curRevision.id, - }, - }); - if (curPosMaster) { - await CreatePosMasterHistoryEmployee(curPosMaster.id, req, "DELETE"); - } + if (curPosMaster) { + await CreatePosMasterHistoryEmployee(curPosMaster.id, req, "DELETE"); } - await removeProfileInOrganize(_profile.id, "EMPLOYEE"); } + await removeProfileInOrganize(_profile.id, "EMPLOYEE"); } const clearProfile = await checkCommandType(String(item.commandId)); if (clearProfile.status) { @@ -5764,6 +4773,7 @@ export class CommandController extends Controller { date: item.commandDateAffect, refCommandDate: item.commandDateSign, refCommandNo: `${item.commandNo}/${item.commandYear}`, + refCommandId: item.commandId, profileEmployeeId: item.profileId, profileId: undefined, }); @@ -5791,32 +4801,22 @@ export class CommandController extends Controller { _profile.lastUpdateFullName = req.user.name; _profile.lastUpdatedAt = new Date(); if (item.isLeave == true) { - const exceptClear = await checkExceptCommandType(String(item.commandId)); - if (exceptClear.status) { - _profile.leaveReason = item.leaveReason ?? _null; - _profile.leaveCommandId = item.commandId ?? _null; - _profile.leaveCommandNo = `${item.commandNo}/${_commandYear}`; - _profile.leaveRemark = exceptClear.leaveRemark ?? _null; - _profile.leaveDate = item.commandDateAffect ?? _null; - _profile.leaveType = exceptClear.LeaveType ?? _null; - } else { - // บันทึกประวัติก่อนลบตำแหน่ง - const curRevision = await this.orgRevisionRepo.findOne({ - where: { orgRevisionIsCurrent: true, orgRevisionIsDraft: false }, + // บันทึกประวัติก่อนลบตำแหน่ง + const curRevision = await this.orgRevisionRepo.findOne({ + where: { orgRevisionIsCurrent: true, orgRevisionIsDraft: false }, + }); + if (curRevision) { + const curPosMaster = await this.employeePosMasterRepository.findOne({ + where: { + current_holderId: _profile.id, + orgRevisionId: curRevision.id, + }, }); - if (curRevision) { - const curPosMaster = await this.employeePosMasterRepository.findOne({ - where: { - current_holderId: _profile.id, - orgRevisionId: curRevision.id, - }, - }); - if (curPosMaster) { - await CreatePosMasterHistoryEmployee(curPosMaster.id, req, "DELETE"); - } + if (curPosMaster) { + await CreatePosMasterHistoryEmployee(curPosMaster.id, req, "DELETE"); } - await removeProfileInOrganize(_profile.id, "EMPLOYEE"); } + await removeProfileInOrganize(_profile.id, "EMPLOYEE"); } const clearProfile = await checkCommandType(String(item.commandId)); if (clearProfile.status) { @@ -6316,6 +5316,10 @@ export class CommandController extends Controller { return new HttpSuccess(); } + /** + * API สร้างทะเบียนประวัติข้าราชการ หลังออกคำสั่งบรรจุสอบ หรือ รับโอน + * @summary API สร้างทะเบียนประวัติข้าราชการ หลังออกคำสั่งบรรจุสอบ หรือ รับโอน + */ @Post("excexute/create-officer-profile") public async CreateOfficeProfileExcecute( @Request() req: RequestWithUser, @@ -6329,6 +5333,13 @@ export class CommandController extends Controller { bodyPosition?: { posmasterId: string; positionId: string; + positionName: string; + posTypeId: string; + posLevelId: string; + posExecutiveId: string | null; + positionField: string | null; + positionExecutiveField: string | null; + positionArea: string | null; } | null; bodyMarry?: { marry?: boolean | null; @@ -6355,793 +5366,10 @@ export class CommandController extends Controller { }[]; }, ) { - const roleKeycloak = await this.roleKeycloakRepo.findOne({ - where: { name: Like("USER") }, + await new ExecuteOfficerProfileService().executeCreateOfficerProfile(body.data, { + user: { sub: req.user.sub, name: req.user.name }, + req, }); - const list = await getRoles(); - if (!Array.isArray(list)) throw new Error("Failed. Cannot get role(s) data from the server."); - let _posNumCodeSit: string = ""; - let _posNumCodeSitAbb: string = ""; - const _command = await this.commandRepository.findOne({ - where: { id: body.data.find((x) => x.bodySalarys?.commandId)?.bodySalarys?.commandId ?? "" }, - }); - if (_command) { - if (_command?.isBangkok?.toLocaleUpperCase() == "OFFICE") { - const orgRootDeputy = await this.orgRootRepository.findOne({ - where: { - isDeputy: true, - orgRevision: { - orgRevisionIsCurrent: true, - orgRevisionIsDraft: false, - }, - }, - relations: ["orgRevision"], - }); - _posNumCodeSit = orgRootDeputy ? orgRootDeputy?.orgRootName : "สำนักปลัดกรุงเทพมหานคร"; - _posNumCodeSitAbb = orgRootDeputy ? orgRootDeputy?.orgRootShortName : "สนป."; - } else if (_command?.isBangkok?.toLocaleUpperCase() == "BANGKOK") { - _posNumCodeSit = "กรุงเทพมหานคร"; - _posNumCodeSitAbb = "กทม."; - } else { - let _profileAdmin = await this.profileRepository.findOne({ - where: { - keycloak: _command?.createdUserId.toString(), - current_holders: { - orgRevision: { - orgRevisionIsCurrent: true, - orgRevisionIsDraft: false, - }, - }, - }, - relations: ["current_holders", "current_holders.orgRevision", "current_holders.orgRoot"], - }); - _posNumCodeSit = - _profileAdmin?.current_holders.find((x) => x.orgRoot.orgRootName)?.orgRoot.orgRootName ?? - ""; - _posNumCodeSitAbb = - _profileAdmin?.current_holders.find((x) => x.orgRoot.orgRootShortName)?.orgRoot - .orgRootShortName ?? ""; - } - } - const before = null; - const meta = { - createdUserId: req.user.sub, - createdFullName: req.user.name, - lastUpdateUserId: req.user.sub, - lastUpdateFullName: req.user.name, - createdAt: new Date(), - lastUpdatedAt: new Date(), - }; - await Promise.all( - body.data.map(async (item) => { - const _null: any = null; - if (item.bodyProfile.posLevelId === "") item.bodyProfile.posLevelId = null; - if (item.bodyProfile.posTypeId === "") item.bodyProfile.posTypeId = null; - if ( - item.bodyProfile.posLevelId && - !(await this.posLevelRepo.findOneBy({ id: item.bodyProfile.posLevelId })) - ) { - throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูลระดับตำแหน่งนี้"); - } - if ( - item.bodyProfile.posTypeId && - !(await this.posTypeRepo.findOneBy({ id: item.bodyProfile.posTypeId })) - ) { - throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูลประเภทตำแหน่งนี้"); - } - - let registrationProvinceId = await this.provinceRepo.findOneBy({ - id: item.bodyProfile.registrationProvinceId ?? "", - }); - let registrationDistrictId = await this.districtRepo.findOneBy({ - id: item.bodyProfile.registrationDistrictId ?? "", - }); - let registrationSubDistrictId = await this.subDistrictRepo.findOneBy({ - id: item.bodyProfile.registrationSubDistrictId ?? "", - }); - let currentProvinceId = await this.provinceRepo.findOneBy({ - id: item.bodyProfile.currentProvinceId ?? "", - }); - let currentDistrictId = await this.districtRepo.findOneBy({ - id: item.bodyProfile.currentDistrictId ?? "", - }); - let currentSubDistrictId = await this.subDistrictRepo.findOneBy({ - id: item.bodyProfile.currentSubDistrictId ?? "", - }); - - let _dateRetire = - item.bodyProfile.birthDate == null - ? _null - : calculateRetireDate(item.bodyProfile.birthDate); - let _dateRetireLaw = - item.bodyProfile.birthDate == null - ? _null - : calculateRetireLaw(item.bodyProfile.birthDate); - - let userKeycloakId: any; - let result: any; - const checkUser = await getUserByUsername(item.bodyProfile.citizenId); - if (checkUser.length == 0) { - let password = item.bodyProfile.citizenId; - if (item.bodyProfile.birthDate != null) { - const _date = new Date(item.bodyProfile.birthDate.toDateString()) - .getDate() - .toString() - .padStart(2, "0"); - const _month = (new Date(item.bodyProfile.birthDate.toDateString()).getMonth() + 1) - .toString() - .padStart(2, "0"); - const _year = new Date(item.bodyProfile.birthDate.toDateString()).getFullYear() + 543; - password = `${_date}${_month}${_year}`; - } - userKeycloakId = await createUser(item.bodyProfile.citizenId, password, { - firstName: item.bodyProfile.firstName, - lastName: item.bodyProfile.lastName, - }); - result = await addUserRoles( - userKeycloakId, - list - .filter((v) => v.name === "USER") - .map((x) => ({ - id: x.id, - name: x.name, - })), - ); - } else { - userKeycloakId = checkUser[0].id; - const rolesData = await getRoleMappings(userKeycloakId); - if (rolesData) { - const _delRole = rolesData.map((x: any) => ({ - id: x.id, - name: x.name, - })); - await removeUserRoles(userKeycloakId, _delRole); - } - result = await addUserRoles( - userKeycloakId, - list - .filter((v) => v.name === "USER") - .map((x) => ({ - id: x.id, - name: x.name, - })), - ); - } - - let profile: any = await this.profileRepository.findOne({ - where: { citizenId: item.bodyProfile.citizenId /*, isActive: true */ }, - relations: ["roleKeycloaks", "profileInsignias", "profileAvatars"], - }); - let _oldInsigniaIds: string[] = []; - let _oldSalaries: any[] = []; - //ลูกจ้างประจำ หรือ บุคคลภายนอก - if (!profile) { - //กรณีลูกจ้างประจำมาสอบเป็นข้าราชการ ต้อง update สถานะโปรไฟล์เดิม - let profileEmployee: any = await this.profileEmployeeRepository.findOne({ - where: { citizenId: item.bodyProfile.citizenId }, - relations: ["profileInsignias", "roleKeycloaks"], - }); - if (profileEmployee) { - const _order = await this.salaryRepo.findOne({ - where: { profileEmployeeId: profileEmployee.id }, - order: { order: "DESC" }, - }); - const profileEmpSalary = new ProfileSalary(); - profileEmpSalary.posNumCodeSit = _posNumCodeSit; - profileEmpSalary.posNumCodeSitAbb = _posNumCodeSitAbb; - profileEmpSalary.order = _order == null ? 1 : _order.order + 1; - Object.assign(profileEmpSalary, { - ...item.bodySalarys, - ...meta, - profileEmployeeId: profileEmployee.id, - profileId: undefined, - }); - const history = new ProfileSalaryHistory(); - Object.assign(history, { ...profileEmpSalary, id: undefined }); - profileEmpSalary.dateGovernment = item.bodySalarys?.commandDateAffect ?? meta.createdAt; - (profileEmpSalary.profileId = _null), - await this.salaryRepo.save(profileEmpSalary, { data: req }); - setLogDataDiff(req, { before, after: profileEmpSalary }); - history.profileSalaryId = profileEmpSalary.id; - await this.salaryHistoryRepo.save(history, { data: req }); - - if (profileEmployee.profileInsignias.length > 0) { - _oldInsigniaIds = profileEmployee.profileInsignias?.map((x: any) => x.id) ?? []; - } - await removeProfileInOrganize(profileEmployee.id, "EMPLOYEE"); - if (profileEmployee.keycloak != null) { - // const delUserKeycloak = await deleteUser(profileEmployee.keycloak); - // if (delUserKeycloak) { - // Task #228 - // profileEmployee.keycloak = _null; - profileEmployee.roleKeycloaks = []; - profileEmployee.isActive = false; - // } - } - profileEmployee.isLeave = true; - profileEmployee.leaveReason = "บรรจุข้าราชการ"; - profileEmployee.lastUpdateUserId = req.user.sub; - profileEmployee.lastUpdateFullName = req.user.name; - profileEmployee.lastUpdatedAt = new Date(); - await this.profileEmployeeRepository.save(profileEmployee); - setLogDataDiff(req, { before, after: profileEmployee }); - } - profile = Object.assign({ ...item.bodyProfile, ...meta }); - profile.dateRetire = _dateRetire; - profile.dateRetireLaw = _dateRetireLaw; - profile.roleKeycloaks = result && roleKeycloak ? [roleKeycloak] : []; - profile.keycloak = - userKeycloakId && typeof userKeycloakId === "string" ? userKeycloakId : ""; - profile.registrationAddress = item.bodyProfile.registrationAddress; - profile.registrationProvinceId = registrationProvinceId - ? registrationProvinceId.id - : _null; - profile.registrationDistrictId = registrationDistrictId - ? registrationDistrictId.id - : _null; - profile.registrationSubDistrictId = registrationSubDistrictId - ? registrationSubDistrictId.id - : _null; - profile.registrationZipCode = item.bodyProfile.registrationZipCode; - profile.currentAddress = item.bodyProfile.currentAddress; - profile.currentProvinceId = currentProvinceId ? currentProvinceId.id : _null; - profile.currentDistrictId = currentDistrictId ? currentDistrictId.id : _null; - profile.currentSubDistrictId = currentSubDistrictId ? currentSubDistrictId.id : _null; - profile.currentZipCode = item.bodyProfile.currentZipCode; - profile.email = item.bodyProfile.email; - profile.dateStart = item.bodyProfile.dateStart; - profile.amount = item.bodyProfile.amount ?? null; - profile.amountSpecial = item.bodyProfile.amountSpecial ?? null; - profile.isProbation = item.bodyProfile.isProbation; - //เพิ่มใหม่จากรับโอน - profile.prefix = item.bodyProfile.prefix ?? null; - profile.prefixMain = item.bodyProfile.prefix ?? null; - profile.firstName = item.bodyProfile.firstName ?? null; - profile.lastName = item.bodyProfile.lastName ?? null; - profile.birthDate = item.bodyProfile.birthDate ?? null; - profile.gender = item.bodyProfile.gender ?? null; - profile.relationship = item.bodyProfile.relationship ?? null; - profile.religion = item.bodyProfile.religion ?? null; - profile.ethnicity = item.bodyProfile.ethnicity; - profile.nationality = item.bodyProfile.nationality ?? null; - profile.bloodGroup = item.bodyProfile.bloodGroup ?? null; - profile.phone = item.bodyProfile.phone ?? null; - - await this.profileRepository.save(profile); - // update user attribute in keycloak - await updateUserAttributes(profile.keycloak ?? "", { - profileId: [profile.id], - prefix: [profile.prefix || ""], - }); - setLogDataDiff(req, { before, after: profile }); - } - //ขรก.ในระบบ หรือ ขรก.ในระบบที่สถานะพ้นจากราชการ - else { - //สร้างโปรไฟล์ใหม่ ถ้าสถานะพ้นราชการ คำสั่งโอนออกหรือคำสั่งขอลาออก - if ( - profile.isLeave && - ["PLACEMENT_TRANSFER", "RETIRE_RESIGN"].includes(profile.leaveType) - ) { - //ดึง profileSalary เดิม - _oldSalaries = await this.salaryRepo.find({ - where: { profileId: profile.id }, - order: { order: "ASC" }, - }); - if (profile.profileInsignias.length > 0) { - _oldInsigniaIds = profile.profileInsignias?.map((x: any) => x.id) ?? []; - } - profile = Object.assign({ ...item.bodyProfile, ...meta }); - profile.dateRetire = _dateRetire; - profile.dateRetireLaw = _dateRetireLaw; - profile.roleKeycloaks = result && roleKeycloak ? [roleKeycloak] : []; - profile.keycloak = - userKeycloakId && typeof userKeycloakId === "string" ? userKeycloakId : ""; - profile.registrationAddress = item.bodyProfile.registrationAddress; - profile.registrationProvinceId = registrationProvinceId - ? registrationProvinceId.id - : _null; - profile.registrationDistrictId = registrationDistrictId - ? registrationDistrictId.id - : _null; - profile.registrationSubDistrictId = registrationSubDistrictId - ? registrationSubDistrictId.id - : _null; - profile.registrationZipCode = item.bodyProfile.registrationZipCode; - profile.currentAddress = item.bodyProfile.currentAddress; - profile.currentProvinceId = currentProvinceId ? currentProvinceId.id : _null; - profile.currentDistrictId = currentDistrictId ? currentDistrictId.id : _null; - profile.currentSubDistrictId = currentSubDistrictId ? currentSubDistrictId.id : _null; - profile.currentZipCode = item.bodyProfile.currentZipCode; - profile.email = item.bodyProfile.email; - profile.dateStart = item.bodyProfile.dateStart; - profile.amount = item.bodyProfile.amount ?? null; - profile.amountSpecial = item.bodyProfile.amountSpecial ?? null; - profile.isProbation = item.bodyProfile.isProbation; - profile.prefix = item.bodyProfile.prefix ?? null; - profile.prefixMain = item.bodyProfile.prefix ?? null; - profile.firstName = item.bodyProfile.firstName ?? null; - profile.lastName = item.bodyProfile.lastName ?? null; - profile.birthDate = item.bodyProfile.birthDate ?? null; - profile.gender = item.bodyProfile.gender ?? null; - profile.relationship = item.bodyProfile.relationship ?? null; - profile.religion = item.bodyProfile.religion ?? null; - profile.ethnicity = item.bodyProfile.ethnicity; - profile.nationality = item.bodyProfile.nationality ?? null; - profile.bloodGroup = item.bodyProfile.bloodGroup ?? null; - profile.phone = item.bodyProfile.phone ?? null; - await this.profileRepository.save(profile); - setLogDataDiff(req, { before, after: profile }); - } else { - profile.roleKeycloaks = result && roleKeycloak ? [roleKeycloak] : []; - profile.keycloak = - userKeycloakId && typeof userKeycloakId === "string" ? userKeycloakId : ""; - profile.isProbation = item.bodyProfile.isProbation; - profile.isLeave = item.bodyProfile.isLeave; - profile.isRetirement = false; - profile.isActive = true; - profile.isDelete = false; - profile.dateLeave = _null; - profile.dateRetire = _dateRetire; - profile.dateRetireLaw = _dateRetireLaw; - profile.registrationAddress = item.bodyProfile.registrationAddress; - profile.registrationProvinceId = registrationProvinceId - ? registrationProvinceId.id - : _null; - profile.registrationDistrictId = registrationDistrictId - ? registrationDistrictId.id - : _null; - profile.registrationSubDistrictId = registrationSubDistrictId - ? registrationSubDistrictId.id - : _null; - profile.registrationZipCode = item.bodyProfile.registrationZipCode; - profile.currentAddress = item.bodyProfile.currentAddress; - profile.currentProvinceId = currentProvinceId ? currentProvinceId.id : _null; - profile.currentDistrictId = currentDistrictId ? currentDistrictId.id : _null; - profile.currentSubDistrictId = currentSubDistrictId ? currentSubDistrictId.id : _null; - profile.currentZipCode = item.bodyProfile.currentZipCode; - profile.email = item.bodyProfile.email; - profile.telephoneNumber = item.bodyProfile.telephoneNumber; - profile.phone = item.bodyProfile.phone; - profile.dateStart = item.bodyProfile.dateStart; - profile.amount = item.bodyProfile.amount ?? null; - profile.amountSpecial = item.bodyProfile.amountSpecial ?? null; - profile.leaveCommandId = _null; - profile.leaveCommandNo = _null; - profile.leaveRemark = _null; - profile.leaveDate = _null; - profile.leaveType = _null; - profile.leaveReason = _null; - profile.lastUpdateUserId = req.user.sub; - profile.lastUpdateFullName = req.user.name; - profile.lastUpdatedAt = new Date(); - //เพิ่มใหม่จากรับโอน - profile.prefix = - item.bodyProfile.prefix && item.bodyProfile.prefix != "" - ? item.bodyProfile.prefix - : profile.prefix; - profile.prefixMain = item.bodyProfile.prefix ?? null; - profile.firstName = - item.bodyProfile.firstName && item.bodyProfile.firstName != "" - ? item.bodyProfile.firstName - : profile.firstName; - profile.lastName = - item.bodyProfile.lastName && item.bodyProfile.lastName != "" - ? item.bodyProfile.lastName - : profile.lastName; - profile.birthDate = item.bodyProfile.birthDate - ? item.bodyProfile.birthDate - : profile.birthDate; - profile.gender = - item.bodyProfile.gender && item.bodyProfile.gender != "" - ? item.bodyProfile.gender - : profile.gender; - profile.relationship = - item.bodyProfile.relationship && item.bodyProfile.relationship != "" - ? item.bodyProfile.relationship - : profile.relationship; - profile.religion = - item.bodyProfile.religion && item.bodyProfile.religion != "" - ? item.bodyProfile.religion - : profile.religion; - profile.ethnicity = - item.bodyProfile.ethnicity && item.bodyProfile.ethnicity != "" - ? item.bodyProfile.ethnicity - : profile.ethnicity; - profile.nationality = - item.bodyProfile.nationality && item.bodyProfile.nationality != "" - ? item.bodyProfile.nationality - : profile.nationality; - profile.bloodGroup = - item.bodyProfile.bloodGroup && item.bodyProfile.bloodGroup != "" - ? item.bodyProfile.bloodGroup - : profile.bloodGroup; - profile.phone = - item.bodyProfile.phone && item.bodyProfile.phone != "" - ? item.bodyProfile.phone - : profile.phone; - await this.profileRepository.save(profile); - setLogDataDiff(req, { before, after: profile }); - } - } - - if (profile && profile.id) { - //Educations - if (item.bodyEducations && item.bodyEducations.length > 0) { - await Promise.all( - item.bodyEducations.map(async (education) => { - const profileEdu = new ProfileEducation(); - Object.assign(profileEdu, { ...education, ...meta }); - const eduHistory = new ProfileEducationHistory(); - Object.assign(eduHistory, { ...profileEdu, id: undefined }); - profileEdu.profileId = profile.id; - const educationLevel = await this.profileEducationRepo.findOne({ - select: ["id", "level", "profileId"], - where: { profileId: profile.id, isDeleted: false }, - order: { level: "DESC" }, - }); - profileEdu.level = educationLevel == null ? 1 : educationLevel.level + 1; - await this.profileEducationRepo.save(profileEdu, { data: req }); - setLogDataDiff(req, { before, after: profileEdu }); - eduHistory.profileEducationId = profileEdu.id; - await this.profileEducationHistoryRepo.save(eduHistory, { data: req }); - }), - ); - } - //Certificates - if (item.bodyCertificates && item.bodyCertificates.length > 0) { - await Promise.all( - item.bodyCertificates.map(async (cer) => { - const profileCer = new ProfileCertificate(); - Object.assign(profileCer, { ...cer, ...meta }); - const cerHistory = new ProfileCertificateHistory(); - Object.assign(cerHistory, { ...profileCer, id: undefined }); - profileCer.profileId = profile.id; - await this.certificateRepo.save(profileCer, { data: req }); - setLogDataDiff(req, { before, after: profileCer }); - cerHistory.profileCertificateId = profileCer.id; - await this.certificateHistoryRepo.save(cerHistory, { data: req }); - }), - ); - } - //FamilyCouple - if (item.bodyMarry != null) { - const profileCouple = new ProfileFamilyCouple(); - const data = { - profileId: profile.id, - couple: item.bodyMarry.marry, - couplePrefix: item.bodyMarry.marryPrefix, - coupleFirstName: item.bodyMarry.marryFirstName, - coupleLastName: item.bodyMarry.marryLastName, - coupleCareer: item.bodyMarry.marryOccupation, - coupleLive: true, - }; - Object.assign(profileCouple, { ...data, ...meta }); - const coupleHistory = new ProfileFamilyCoupleHistory(); - Object.assign(coupleHistory, { ...profileCouple, id: undefined }); - profileCouple.profileId = profile.id; - await this.profileFamilyCoupleRepo.save(profileCouple, { data: req }); - setLogDataDiff(req, { before, after: profileCouple }); - coupleHistory.profileFamilyCoupleId = profileCouple.id; - await this.profileFamilyCoupleHistoryRepo.save(coupleHistory, { data: req }); - } - //FamilyFather - if (item.bodyFather != null) { - const profileFather = new ProfileFamilyFather(); - const data = { - profileId: profile.id, - fatherPrefix: item.bodyFather.fatherPrefix, - fatherFirstName: item.bodyFather.fatherFirstName, - fatherLastName: item.bodyFather.fatherLastName, - fatherCareer: item.bodyFather.fatherOccupation, - fatherLive: true, - }; - Object.assign(profileFather, { ...data, ...meta }); - const fatherHistory = new ProfileFamilyFatherHistory(); - Object.assign(fatherHistory, { ...profileFather, id: undefined }); - profileFather.profileId = profile.id; - await this.profileFamilyFatherRepo.save(profileFather, { data: req }); - setLogDataDiff(req, { before, after: profileFather }); - fatherHistory.profileFamilyFatherId = profileFather.id; - await this.profileFamilyFatherHistoryRepo.save(fatherHistory, { data: req }); - } - //FamilyMother - if (item.bodyMother != null) { - const profileMother = new ProfileFamilyMother(); - const data = { - profileId: profile.id, - motherPrefix: item.bodyMother.motherPrefix, - motherFirstName: item.bodyMother.motherFirstName, - motherLastName: item.bodyMother.motherLastName, - motherCareer: item.bodyMother.motherOccupation, - motherLive: true, - }; - Object.assign(profileMother, { ...data, ...meta }); - const motherHistory = new ProfileFamilyMotherHistory(); - Object.assign(motherHistory, { ...profileMother, id: undefined }); - profileMother.profileId = profile.id; - await this.profileFamilyMotherRepo.save(profileMother, { data: req }); - setLogDataDiff(req, { before, after: profileMother }); - motherHistory.profileFamilyMotherId = profileMother.id; - await this.profileFamilyMotherHistoryRepo.save(motherHistory, { data: req }); - } - //Salary - //insert profileSalary อันเก่า กรณีพ้นราชการแล้วกลับมาบรรจุ - if (_oldSalaries.length > 0) { - await Promise.all( - _oldSalaries.map(async (oldSal) => { - const profileSal: any = new ProfileSalary(); - Object.assign(profileSal, { ...oldSal, ...meta }); - const salaryHistory = new ProfileSalaryHistory(); - Object.assign(salaryHistory, { ...profileSal, id: undefined }); - profileSal.profileId = profile.id; - await this.salaryRepo.save(profileSal, { data: req }); - setLogDataDiff(req, { before, after: profileSal }); - salaryHistory.profileSalaryId = profileSal.id; - await this.salaryHistoryRepo.save(salaryHistory, { data: req }); - }), - ); - } - //insert item.bodySalarys ต่อจากที่ insert เดิมไปแล้ว - if (item.bodySalarys && item.bodySalarys != null) { - const dest_item = await this.salaryRepo.findOne({ - where: { profileId: profile.id }, - order: { order: "DESC" }, - }); - const profileSal: any = new ProfileSalary(); - profileSal.posNumCodeSit = _posNumCodeSit; - profileSal.posNumCodeSitAbb = _posNumCodeSitAbb; - Object.assign(profileSal, { ...item.bodySalarys, ...meta }); - const salaryHistory = new ProfileSalaryHistory(); - Object.assign(salaryHistory, { ...profileSal, id: undefined }); - profileSal.order = dest_item == null ? 1 : dest_item.order + 1; - profileSal.profileId = profile.id; - profileSal.dateGovernment = item.bodySalarys.commandDateAffect ?? meta.createdAt; - profileSal.amount = item.bodySalarys.amount ?? null; - profileSal.amountSpecial = item.bodySalarys.amountSpecial ?? null; - profileSal.positionSalaryAmount = item.bodySalarys.positionSalaryAmount ?? null; - profileSal.mouthSalaryAmount = item.bodySalarys.mouthSalaryAmount ?? null; - await this.salaryRepo.save(profileSal, { data: req }); - setLogDataDiff(req, { before, after: profileSal }); - salaryHistory.profileSalaryId = profileSal.id; - await this.salaryHistoryRepo.save(salaryHistory, { data: req }); - } - //Position - if (item.bodyPosition && item.bodyPosition != null) { - // STEP 1: หา posMaster ที่จะใช้งานตาม id ที่ส่งมา (อาจเป็นตำแหน่งเก่าหรือใหม่ก็ได้) - let posMaster = await this.posMasterRepository.findOne({ - where: { - id: item.bodyPosition.posmasterId, - }, - relations: { - orgRevision: true, - orgRoot: true, - orgChild1: true, - orgChild2: true, - orgChild3: true, - orgChild4: true, - }, - }); - - // เช็คว่า posMaster ที่หามาอยู่ในโครงสร้างปัจจุบันหรือไม่ - const isCurrent = - posMaster?.orgRevision?.orgRevisionIsCurrent === true && - posMaster?.orgRevision?.orgRevisionIsDraft === false; - - // ถ้าไม่อยู่ในโครงสร้างปัจจุบัน ให้หาตัวใหม่จาก ancestorDNA - if (!isCurrent && posMaster?.ancestorDNA) { - posMaster = await this.posMasterRepository.findOne({ - where: { - ancestorDNA: posMaster.ancestorDNA, - orgRevision: { - orgRevisionIsCurrent: true, - orgRevisionIsDraft: false, - }, - }, - relations: { - orgRevision: true, - orgRoot: true, - orgChild1: true, - orgChild2: true, - orgChild3: true, - orgChild4: true, - }, - }); - } - - if (posMaster == null) - throw new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบข้อมูลตำแหน่งนี้"); - - // STEP 2: เคลียร์ข้อมูลตำแหน่งเก่าที่ครองอยู่ ในโครงสร้างปัจจุบัน - const posMasterOld = await this.posMasterRepository.findOne({ - where: { - current_holderId: profile.id, - orgRevisionId: posMaster.orgRevisionId, - }, - }); - if (posMasterOld != null) { - // เคลียร์คนครองเก่าออกจากตำแหน่งเดิม - posMasterOld.current_holderId = null; - posMasterOld.lastUpdatedAt = new Date(); - } - - // หา position เก่าที่เลือกไว้ แล้วเคลียร์การเลือก - const positionOld = await this.positionRepository.findOne({ - where: { - posMasterId: posMasterOld?.id, - positionIsSelected: true, - }, - }); - if (positionOld != null) { - positionOld.positionIsSelected = false; - await this.positionRepository.save(positionOld); - } - - // STEP 3: เคลียร์ position ที่เลือกไว้อื่นๆ ใน posMaster ตัวใหม่ - const checkPosition = await this.positionRepository.find({ - where: { - posMasterId: posMaster.id, - positionIsSelected: true, - }, - }); - if (checkPosition.length > 0) { - const clearPosition = checkPosition.map((positions) => ({ - ...positions, - positionIsSelected: false, - })); - await this.positionRepository.save(clearPosition); - } - - // STEP 4: กำหนดคนครองใหม่ให้กับ posMaster - posMaster.current_holderId = profile.id; - posMaster.lastUpdatedAt = new Date(); - // posMaster.conditionReason = _null; - // posMaster.isCondition = false; - if (posMasterOld != null) { - await this.posMasterRepository.save(posMasterOld); - await CreatePosMasterHistoryOfficer(posMasterOld.id, req); - } - await this.posMasterRepository.save(posMaster); - - // STEP 5: กำหนด position ใหม่ - // เช็คว่า posMaster เปลี่ยนจากเก่าเป็นใหม่หรือไม่ - const originalPosMasterId = item.bodyPosition.posmasterId; - const isPosMasterChanged = originalPosMasterId !== posMaster.id; - - let positionNew = null; - - if (isPosMasterChanged) { - // posMaster เปลี่ยน ต้องหา position ใหม่จากคุณสมบัติของ position เก่า - // 1. หา position เก่าจาก id ที่ส่งมา - const positionOld = await this.positionRepository.findOne({ - where: { id: item.bodyPosition.positionId }, - }); - - if (positionOld) { - // 2. ใช้ posTypeId + posLevelId + positionName หา position ใหม่ใน posMaster ตัวใหม่ - positionNew = await this.positionRepository.findOne({ - where: { - posMasterId: posMaster.id, // ใช้ posMaster ตัวใหม่ - posTypeId: positionOld.posTypeId, - posLevelId: positionOld.posLevelId, - positionName: positionOld.positionName, - }, - }); - } - } else { - // posMaster ไม่เปลี่ยน - ใช้วิธีเดิม - positionNew = await this.positionRepository.findOne({ - where: { - id: item.bodyPosition.positionId, - posMasterId: posMaster.id, - }, - relations: ["posExecutive"], - }); - } - - // ถ้าไม่ใช่ตำแหน่งนั่งทับ (isSit = false) ถึงจะอัพเดทตำแหน่งในทะเบียนประวัติ - if (positionNew != null) { - positionNew.positionIsSelected = true; - // อัพเดท org และ posMasterNo ตลอดไม่ต้องดัก isSit - profile.posMasterNo = getPosMasterNo(posMaster); - profile.org = getOrgFullName(posMaster); - if (!posMaster.isSit) { - profile.posLevelId = positionNew.posLevelId; - profile.posTypeId = positionNew.posTypeId; - 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(); - } - await this.profileRepository.save(profile, { data: req }); - setLogDataDiff(req, { before, after: profile }); - await this.positionRepository.save(positionNew, { data: req }); - } - // await CreatePosMasterHistoryOfficer(posMaster.id, req); - await CreatePosMasterHistoryOfficer(posMaster.id, req, null, { - positionId: positionNew?.id, - }); - } - // Insignia - if (_oldInsigniaIds.length > 0) { - const _insignias = await this.insigniaRepo.find({ - where: { id: In(_oldInsigniaIds), isDeleted: false }, - order: { createdAt: "ASC" }, - }); - for (const oldInsignia of _insignias) { - const newInsigniaData: CreateProfileInsignia = { - profileId: profile.id, - year: oldInsignia.year, - no: oldInsignia.no, - volume: oldInsignia.volume, - section: oldInsignia.section, - page: oldInsignia.page, - receiveDate: oldInsignia.receiveDate, - insigniaId: oldInsignia.insigniaId, - dateAnnounce: oldInsignia.dateAnnounce, - issue: oldInsignia.issue, - volumeNo: oldInsignia.volumeNo, - refCommandDate: oldInsignia.refCommandDate, - refCommandNo: oldInsignia.refCommandNo, - note: oldInsignia.note, - isUpload: oldInsignia.isUpload, - }; - const insignia = new ProfileInsignia(); - Object.assign(insignia, { ...newInsigniaData, ...meta }); - const history = new ProfileInsigniaHistory(); - Object.assign(history, { ...insignia, id: undefined }); - await this.insigniaRepo.save(insignia, { data: req }); - setLogDataDiff(req, { before, after: insignia }); - history.profileInsigniaId = insignia.id; - await this.insigniaHistoryRepo.save(history, { data: req }); - } - } - // เพิ่มรูปภาพโปรไฟล์ - if (item.bodyProfile.objectRefId) { - const _profileAvatar = new ProfileAvatar(); - Object.assign(_profileAvatar, { - ...meta, - profileId: profile.id, - profileEmployeeId: undefined, - }); - if (profile.profileAvatars && profile.profileAvatars.length > 0) { - await Promise.all( - profile.profileAvatars.map(async (item: any) => { - item.isActive = false; - await this.avatarRepository.save(item); - }), - ); - } - await this.avatarRepository.save(_profileAvatar); - let avatar = `ทะเบียนประวัติ/โปรไฟล์/${profile.id}`; - let fileName = `profile-${_profileAvatar.id}`; - _profileAvatar.isActive = true; - _profileAvatar.avatar = avatar; - _profileAvatar.avatarName = fileName; - await this.avatarRepository.save(_profileAvatar, { data: req }); - profile.avatar = avatar; - profile.avatarName = fileName; - await this.profileRepository.save(profile, { data: req }); - const checkAvatar = await this.avatarRepository.findOne({ - where: { avatar: avatar, avatarName: fileName }, - }); - if (checkAvatar && checkAvatar.profileId == null) { - checkAvatar.profileId = profile.id; - await this.avatarRepository.save(checkAvatar); - } - //duplicate รูปภาพโปรไฟล์โดยอิงจากรูปภาพเดิม - await new CallAPI() - .PostData(req, `/salary/file/avatar/${item.bodyProfile.objectRefId}`, { - prefix: avatar, - fileName: fileName, - }) - .then(() => {}) - .catch(() => {}); - } - } - }), - ); return new HttpSuccess(); } @@ -7497,8 +5725,10 @@ export class CommandController extends Controller { const _year = new Date(profile.birthDate.toDateString()).getFullYear() + 543; password = `${_date}${_month}${_year}`; } + // กรอง "." ออกจาก firstName ก่อนส่งไป keycloak + const sanitizedFirstName = profile.firstName?.replace(/\./g, "") ?? ""; const userKeycloakId = await createUser(profile.citizenId, password, { - firstName: profile.firstName, + firstName: sanitizedFirstName, lastName: profile.lastName, // email: profile.email, }); diff --git a/src/controllers/CommandOperatorController.ts b/src/controllers/CommandOperatorController.ts index 1a461ab3..18393f53 100644 --- a/src/controllers/CommandOperatorController.ts +++ b/src/controllers/CommandOperatorController.ts @@ -9,7 +9,7 @@ import { Path, Request, Response, - Get + Get, } from "tsoa"; import { LessThan, MoreThan } from "typeorm"; import { AppDataSource } from "../database/data-source"; @@ -37,9 +37,7 @@ export class CommandOperatorController extends Controller { * @param commandId คีย์คำสั่ง */ @Get("{commandId}") - async getCommandOperatorByCommandId( - @Path() commandId: string - ) { + async getCommandOperatorByCommandId(@Path() commandId: string) { const command = await this.commandRepo.findOne({ where: { id: commandId }, select: { id: true }, @@ -61,10 +59,7 @@ export class CommandOperatorController extends Controller { * @param operatorId คีย์เจ้าหน้าที่ดำเนินการ */ @Get("swap/{direction}/{operatorId}") - async swapCommandOperator( - @Path() direction: string, - @Path() operatorId: string, - ) { + async swapCommandOperator(@Path() direction: string, @Path() operatorId: string) { const source = await this.commandOperatorRepo.findOne({ where: { id: operatorId }, }); @@ -106,10 +101,7 @@ export class CommandOperatorController extends Controller { source.orderNo = dest.orderNo; dest.orderNo = temp; - await Promise.all([ - this.commandOperatorRepo.save(source), - this.commandOperatorRepo.save(dest), - ]); + await Promise.all([this.commandOperatorRepo.save(source), this.commandOperatorRepo.save(dest)]); return new HttpSuccess(); } @@ -141,35 +133,29 @@ export class CommandOperatorController extends Controller { const nextOrderNo = (lastOrderNo?.orderNo ?? 1) + 1; const now = new Date(); - const operator = Object.assign( - new CommandOperator(), - { - ...body, - commandId: command.id, - orderNo: nextOrderNo, - createdUserId: request.user.sub, - createdFullName: request.user.name, - createdAt: now, - lastUpdateUserId: request.user.sub, - lastUpdateFullName: request.user.name, - lastUpdatedAt: now, - } - ); + const operator = Object.assign(new CommandOperator(), { + ...body, + commandId: command.id, + orderNo: nextOrderNo, + createdUserId: request.user.sub, + createdFullName: request.user.name, + createdAt: now, + lastUpdateUserId: request.user.sub, + lastUpdateFullName: request.user.name, + lastUpdatedAt: now, + }); await this.commandOperatorRepo.save(operator); return new HttpSuccess(); } - /** + /** * API ลบเจ้าหน้าที่ดำเนินการที่คำสั่ง * @summary API ลบเจ้าหน้าที่ดำเนินการที่คำสั่ง * @param commandId คีย์คำสั่ง * @param operatorId คีย์เจ้าหน้าที่ดำเนินการ */ @Delete("{commandId}/{operatorId}") - public async deleteCommandOperator( - @Path() commandId: string, - @Path() operatorId: string, - ) { + public async deleteCommandOperator(@Path() commandId: string, @Path() operatorId: string) { const queryRunner = AppDataSource.createQueryRunner(); await queryRunner.connect(); await queryRunner.startTransaction(); @@ -215,10 +201,9 @@ export class CommandOperatorController extends Controller { return new HttpSuccess(true); } catch (error) { await queryRunner.rollbackTransaction(); - throw error; + console.error("Delete command operator error:", error); } finally { await queryRunner.release(); } } - } diff --git a/src/controllers/DevTestController.ts b/src/controllers/DevTestController.ts new file mode 100644 index 00000000..e3edfaa5 --- /dev/null +++ b/src/controllers/DevTestController.ts @@ -0,0 +1,576 @@ +import { + Controller, + Post, + Put, + Patch, + Delete, + Route, + Security, + Tags, + Body, + Path, + Request, + Response, + Get, + Query, +} from "tsoa"; +import { AppDataSource } from "../database/data-source"; +import HttpStatus from "../interfaces/http-status"; +import HttpSuccess from "../interfaces/http-success"; +import HttpStatusCode from "../interfaces/http-status"; +import HttpError from "../interfaces/http-error"; +import { Command } from "../entities/Command"; +import { Brackets, LessThan, MoreThan, Double, In, Between, IsNull, Not, Any } from "typeorm"; +import { CommandType } from "../entities/CommandType"; +import { Profile, CreateProfileAllFields } from "../entities/Profile"; +import { RequestWithUser, RequestWithUserWebService } from "../middlewares/user"; +import { OrgRevision } from "../entities/OrgRevision"; +import { ProfileEmployee } from "../entities/ProfileEmployee"; +import { PosMaster } from "../entities/PosMaster"; +import permission from "../interfaces/permission"; +import { viewCurrentTenureOfficer } from "../entities/view/viewCurrentTenureOfficer"; +import { CommandController } from "./CommandController"; +import Extension from "../interfaces/extension"; +import { viewRegistryOfficer } from "../entities/view/viewRegistryOfficer"; +import { viewRegistryEmployee } from "../entities/view/viewRegistryEmployee"; +import { Registry } from "../entities/Registry"; +import { RegistryEmployee } from "../entities/RegistryEmployee"; +import { TenurePositionOfficer } from "../entities/TenurePositionOfficer"; +import { PosMasterAssign, PosMasterAssignDTO } from "../entities/PosMasterAssign"; +import { PermissionProfile } from "../entities/PermissionProfile"; +import { OrgRoot } from "../entities/OrgRoot"; +import { MetaWorkflow } from "../entities/MetaWorkflow"; +import { MetaState } from "../entities/MetaState"; +import { MetaStateOperator } from "../entities/MetaStateOperator"; +import { Workflow } from "../entities/Workflow"; +import { State } from "../entities/State"; +import { StateOperator } from "../entities/StateOperator"; +import { StateOperatorUser } from "../entities/StateOperatorUser"; +import { + commandTypePath, + calculateGovAge, + calculateAge, + calculateRetireDate, + calculateRetireLaw, + removeProfileInOrganize, + setLogDataDiff, +} from "../interfaces/utils"; +import CallAPI from "../interfaces/call-api"; +import { PostRetireToExprofile } from "./ExRetirementController" +import { Position } from "../entities/Position"; +import { PosLevel } from "../entities/PosLevel"; +import { TenureLevelOfficer } from "../entities/TenureLevelOfficer"; +import { TenurePositionEmployee } from "../entities/TenurePositionEmployee"; +import { TenureLevelEmployee } from "../entities/TenureLevelEmployee"; +import { TenurePositionExecutiveOfficer } from "../entities/TenurePositionExecutiveOfficer"; + +@Route("api/v1/org/DevTest") +@Tags("DevTest") +@Security("bearerAuth") +@Response( + HttpStatusCode.INTERNAL_SERVER_ERROR, + "เกิดข้อผิดพลาด ไม่สามารถแสดงรายการได้ กรุณาลองใหม่ในภายหลัง", +) +export class DevTestController extends Controller { + private commandRepository = AppDataSource.getRepository(Command); + private commandTypeRepository = AppDataSource.getRepository(CommandType); + private orgRevisionRepo = AppDataSource.getRepository(OrgRevision); + private orgRootRepo = AppDataSource.getRepository(OrgRoot); + private posMasterRepo = AppDataSource.getRepository(PosMaster); + private profileRepo = AppDataSource.getRepository(Profile); + private profileEmpRepo = AppDataSource.getRepository(ProfileEmployee); + private registryRepo = AppDataSource.getRepository(Registry); + private registryEmployeeRepo = AppDataSource.getRepository(RegistryEmployee); + private posMasterAssignRepository = AppDataSource.getRepository(PosMasterAssign); + private permissionProfilesRepository = AppDataSource.getRepository(PermissionProfile); + private profileEmployeeRepo = AppDataSource.getRepository(ProfileEmployee); + private metaWorkflowRepo = AppDataSource.getRepository(MetaWorkflow); + private metaStateRepo = AppDataSource.getRepository(MetaState); + private metaStateOperatorRepo = AppDataSource.getRepository(MetaStateOperator); + private workflowRepo = AppDataSource.getRepository(Workflow); + private stateRepo = AppDataSource.getRepository(State); + private stateOperatorRepo = AppDataSource.getRepository(StateOperator); + private stateOperatorUserRepo = AppDataSource.getRepository(StateOperatorUser); + private positionRepository = AppDataSource.getRepository(Position); + private positionOfficerRepo = AppDataSource.getRepository(TenurePositionOfficer); + private positionEmployeeRepo = AppDataSource.getRepository(TenurePositionEmployee); + private levelOfficerRepo = AppDataSource.getRepository(TenureLevelOfficer); + private levelEmployeeRepo = AppDataSource.getRepository(TenureLevelEmployee); + private positionExecutiveOfficerRepo = AppDataSource.getRepository( + TenurePositionExecutiveOfficer, + ); + + @Patch("tick-officer-registry") + public async calculateOfficerPosition( + @Request() req: RequestWithUser, + @Body() + body: { + profileIds: string[]; + }, + ) { + + console.log("1.") + /** + * =============================== + * PREPARE DATA + * =============================== + */ + const profile = await this.profileRepo.find({ + where: { id: In(body.profileIds) }, + relations: { + posLevel: true, + posType: true, + }, + }); + + if (!profile.length) return; + + const [{ today }] = await AppDataSource.query( + "SELECT CURRENT_DATE() as today", + ); + + const orgRevision = await this.orgRevisionRepo.findOne({ + select: ["id"], + where: { + orgRevisionIsDraft: false, + orgRevisionIsCurrent: true, + }, + }); + + /** + * =============================== + * TRANSACTION + * =============================== + */ + const queryRunner = AppDataSource.createQueryRunner(); + await queryRunner.connect(); + await queryRunner.startTransaction(); + console.log("2.") + try { + /** + * =============================== + * RESULT BUFFERS (SAVE ARRAY) + * =============================== + */ + const positionOfficerBulk: any[] = []; + const levelOfficerBulk: any[] = []; + const executiveOfficerBulk: any[] = []; + console.log("3.") + /** + * =============================== + * MAIN LOOP (SINGLE LOOP) + * =============================== + */ + for (const x of profile) { + const currentDate = + x.isLeave && x.leaveDate + ? Extension.toDateOnlyString(x.leaveDate) + : today; + /** + * ==================================== + * PARALLEL STORED PROCEDURES + * ==================================== + */ + const [ + positionResult, + levelResult, + executiveResult, + ] = await Promise.all([ + AppDataSource.query("CALL GetProfileSalaryPosition(?, ?)", [ + x.id, + currentDate, + ]), + AppDataSource.query("CALL GetProfileSalaryLevel(?, ?)", [ + x.id, + currentDate, + ]), + AppDataSource.query("CALL GetProfileSalaryExecutive(?, ?)", [ + x.id, + currentDate, + ]), + ]); + console.log("4.",x.id) + /** + * ==================================== + * POSITION + * ==================================== + */ + const posRows = positionResult?.[0] ?? []; + const posMap = + posRows.length > 1 + ? posRows.slice(1).map((r: any, i: number) => ({ + days_diff: Number(r.days_diff) || 0, + positionName: posRows[i]?.positionName, + })) + : []; + + const posCal = posMap + .filter((p:any) => p.positionName === x.position) + .reduce( + (a:any, c:any) => ({ + days_diff: a.days_diff + c.days_diff, + positionName: c.positionName, + }), + { days_diff: 0, positionName: null }, + ); + + positionOfficerBulk.push({ + profileId: x.id, + positionName: posCal.positionName, + days_diff: posCal.days_diff, + Years: Math.floor(posCal.days_diff / 365.2524), + Months: Math.floor((posCal.days_diff / 30.4375) % 12), + Days: Math.floor(posCal.days_diff % 30.4375), + }); + console.log("5.",x.id) + /** + * ==================================== + * 2️⃣ POSITION LEVEL + * ==================================== + */ + const lvlRows = levelResult?.[0] ?? []; + const lvlMap = + lvlRows.length > 1 + ? lvlRows.slice(1).map((r: any, i: number) => ({ + days_diff: Number(r.days_diff) || 0, + positionType: lvlRows[i]?.positionType, + positionLevel: lvlRows[i]?.positionLevel, + positionCee: lvlRows[i]?.positionCee, + })) + : []; + + const lvlCal = lvlMap + .filter( + (l:any) => + l.positionLevel === x.posLevel?.posLevelName && + l.positionType === x.posType?.posTypeName, + ) + .reduce( + (a:any, c:any) => ({ + days_diff: a.days_diff + c.days_diff, + positionType: c.positionType, + positionLevel: c.positionLevel, + positionCee: c.positionCee, + }), + { + days_diff: 0, + positionType: null, + positionLevel: null, + positionCee: null, + }, + ); + + levelOfficerBulk.push({ + profileId: x.id, + positionType: lvlCal.positionType, + positionLevel: lvlCal.positionLevel, + positionCee: lvlCal.positionCee, + days_diff: lvlCal.days_diff, + Years: x.posLevel ? (lvlCal.days_diff / 365.2524).toFixed(4) : 0, + Months: x.posLevel ? ((lvlCal.days_diff / 30.4375) % 12).toFixed(4) : 0, + Days: x.posLevel ? (lvlCal.days_diff % 30.4375).toFixed(4) : 0, + }); + console.log("6.",x.id) + /** + * ==================================== + * 3️⃣ POSITION EXECUTIVE + * ==================================== + */ + const exeRows = executiveResult?.[0] ?? []; + const exeMap = + exeRows.length > 1 + ? exeRows.slice(1).map((r: any, i: number) => ({ + days_diff: Number(r.days_diff) || 0, + positionExecutive: exeRows[i]?.positionExecutive, + })) + : []; + + const position = await this.positionRepository.findOne({ + where: { + positionIsSelected: true, + posMaster: { + orgRevisionId: orgRevision?.id, + current_holderId: x.id, + }, + }, + order: { createdAt: "DESC" }, + relations: { + posExecutive: true, + }, + }); + + const exeName = position?.posExecutive?.posExecutiveName; + + const exeCal = exeMap + .filter((e:any) => exeName && e.positionExecutive === exeName) + .reduce( + (a:any, c:any) => ({ + days_diff: a.days_diff + c.days_diff, + positionExecutive: c.positionExecutive, + }), + { days_diff: 0, positionExecutive: null }, + ); + + executiveOfficerBulk.push({ + profileId: x.id, + positionExecutiveName: exeCal.positionExecutive, + days_diff: exeCal.days_diff, + Years: (exeCal.days_diff / 365.2524).toFixed(4), + Months: ((exeCal.days_diff / 30.4375) % 12).toFixed(4), + Days: (exeCal.days_diff % 30.4375).toFixed(4), + }); + } + console.log("7.") + /** + * =============================== + * CLEAR ALL DATA AND SAVE ARRAY (BULK) + * =============================== + */ + await queryRunner.manager + .createQueryBuilder() + .delete() + .from(this.positionOfficerRepo.target) + .execute(); + + await queryRunner.manager + .createQueryBuilder() + .delete() + .from(this.levelOfficerRepo.target) + .execute(); + + await queryRunner.manager + .createQueryBuilder() + .delete() + .from(this.positionExecutiveOfficerRepo.target) + .execute(); + console.log("8.") + await queryRunner.manager.save(this.positionOfficerRepo.target, positionOfficerBulk); + await queryRunner.manager.save(this.levelOfficerRepo.target, levelOfficerBulk); + await queryRunner.manager.save(this.positionExecutiveOfficerRepo.target,executiveOfficerBulk); + console.log("9.") + /** + * =============================== + * REGISTRY OFFICER (SYNC VIEW) + * =============================== + */ + const allRegis = await queryRunner.manager + .getRepository(viewRegistryOfficer) + .createQueryBuilder("registryOfficer") + .where("registryOfficer.profileId IN (:...profileIds)", { + profileIds: new Set(profile.map((p) => p.id)) + }) + .getMany(); + + const mapRegistryData = allRegis.map((x) => ({ + ...x, + isProbation: Boolean(x.isProbation), + isLeave: Boolean(x.isLeave), + isRetirement: Boolean(x.isRetirement), + Educations: x.Educations ? JSON.stringify(x.Educations) : "", + })); + console.log("10.") + + await queryRunner.manager + .createQueryBuilder() + .delete() + .from(this.registryRepo.target) + .execute(); + + if (mapRegistryData.length > 0) { + await queryRunner.manager.save(this.registryRepo.target, mapRegistryData); + } + console.log("11.") + /** + * =============================== + * COMMIT + * =============================== + */ + await queryRunner.commitTransaction(); + } catch (error) { + await queryRunner.rollbackTransaction(); + throw error; + } finally { + await queryRunner.release(); + } + } + + @Post("getDNA") + public async GetData( + @Request() req: RequestWithUser + ){ + let _data: any = { + root: null, + child1: null, + child2: null, + child3: null, + child4: null, + }; + + _data = await new permission().PermissionOrgList(req, "COMMAND"); + return new HttpSuccess(_data); + } + + @Post("calculateGovAge") + public async calculateGovAge( + @Request() req: RequestWithUser, + @Body() + body: { + profileId: string; + }, + ){ + return new HttpSuccess(await calculateGovAge(body.profileId, "OFFICER")); + } + + /** + * @summary Test Job กวาดออกคำสั่ง ทำงานทุกๆตี2 + */ + @Post("cronjobCommand") + async CronjobCommand() { + const commandController = new CommandController(); + await commandController.cronjobCommand(); + } + + /** + * @summary payload & Endpoint ออกคำสั่ง + */ + @Put("path-excec/{id}") + async Bright( + @Path() id: string, + @Request() request: RequestWithUser, + ) { + const command = await this.commandRepository.findOne({ + where: { id: id }, + relations: ["commandType", "commandRecives", "commandSends", "commandSends.commandSendCCs"], + }); + if (!command) { + throw new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบข้อมูลคำสั่งนี้"); + } + const path = commandTypePath(command.commandType.code); + return new HttpSuccess({ + path: path + "/excecute", + refIds: command.commandRecives + .filter((x) => x.refId != null) + .map((x) => ({ + refId: x.refId, + commandNo: command.commandNo, + commandYear: command.commandYear, + commandId: command.id, + remark: command.positionDetail, + amount: x.amount, + amountSpecial: x.amountSpecial, + positionSalaryAmount: x.positionSalaryAmount, + mouthSalaryAmount: x.mouthSalaryAmount, + commandCode: command.commandType.commandCode, + commandName: command.commandType.name, + commandDateAffect: command.commandExcecuteDate, + commandDateSign: command.commandAffectDate, + })), + }); + } + + /** + * API รายละเอียดรายการคำสั่ง tab4 แนบท้าย + * @summary API รายละเอียดรายการคำสั่ง tab4 แนบท้าย + * @param {string} id Id คำสั่ง + * @param {string} profileId profileId + */ + @Get("tab4/attachment/{id}/{profileId}") + async GetByIdTab4Attachment( + @Path() id: string, + @Path() profileId: string, + @Request() request: RequestWithUser + ) { + await new permission().PermissionGet(request, "COMMAND"); + + let profile: Profile | ProfileEmployee | null = null; + profile = await this.profileRepo.findOne({ where: { id: profileId } }); + if (!profile) { + profile = await this.profileEmpRepo.findOne({ where: { id: profileId } }); + if (!profile) + throw new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบข้อมูลบุคคลากรนี้"); + } + + const command = await this.commandRepository.findOne({ + where: { id }, + relations: ["commandType", "commandRecives"], + }); + if (!command) { + throw new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบข้อมูลคำสั่งนี้"); + } + + let _command: any = []; + const path = commandTypePath(command.commandType.code); + if (path == null) throw new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบประเภทคำสั่งนี้ในระบบ"); + await new CallAPI() + .PostData(request, path + "/attachment", { + refIds: command.commandRecives + .filter((x) => + x.refId != null && + x.profileId != null && x.profileId == profileId + ) + .map((x) => ({ + refId: x.refId, + Sequence: x.order, + CitizenId: x.citizenId, + Prefix: x.prefix, + FirstName: x.firstName, + LastName: x.lastName, + Amount: x.amount, + PositionSalaryAmount: x.positionSalaryAmount, + MouthSalaryAmount: x.mouthSalaryAmount, + RemarkHorizontal: x.remarkHorizontal, + RemarkVertical: x.remarkVertical, + CommandYear: command.commandYear, + CommandExcecuteDate: command.commandExcecuteDate, + })), + }) + .then(async (res) => { + _command = res; + }) + .catch(() => {}); + + let issue = + command.isBangkok == "OFFICE" + ? "สำนักปลัดกรุงเทพมหานคร" + : command.isBangkok == "BANGKOK" + ? "กรุงเทพมหานคร" + : null; + if (issue == null) { + const orgRevisionActive = await this.orgRevisionRepo.findOne({ + where: { orgRevisionIsCurrent: true, orgRevisionIsDraft: false }, + relations: ["posMasters", "posMasters.orgRoot"], + }); + if (orgRevisionActive != null) { + const profile = await this.profileRepo.findOne({ + where: { + keycloak: command.createdUserId.toString(), + }, + }); + if (profile != null) { + issue = + orgRevisionActive?.posMasters?.filter((x) => x.current_holderId == profile.id)[0] + ?.orgRoot?.orgRootName || null; + } + } + } + if (issue == null) issue = "..................................."; + return new HttpSuccess({ + template: command.commandType.fileAttachment, + reportName: "xlsx-report", + data: { + data: _command, + issuerOrganizationName: issue, + commandNo: command.commandNo == null ? "" : Extension.ToThaiNumber(command.commandNo), + commandYear: + command.commandYear == null + ? "" + : Extension.ToThaiNumber(Extension.ToThaiYear(command.commandYear).toString()), + commandExcecuteDate: + command.commandExcecuteDate == null + ? "" + : Extension.ToThaiNumber(Extension.ToThaiFullDate2(command.commandExcecuteDate)), + }, + }); + } + +} diff --git a/src/controllers/EmployeePositionController.ts b/src/controllers/EmployeePositionController.ts index 6ead5522..5809841c 100644 --- a/src/controllers/EmployeePositionController.ts +++ b/src/controllers/EmployeePositionController.ts @@ -39,8 +39,7 @@ import { RequestWithUser } from "../middlewares/user"; import permission from "../interfaces/permission"; import { setLogDataDiff } from "../interfaces/utils"; import { - CreatePosMasterHistoryEmployee, - CreatePosMasterHistoryOfficer, + CreatePosMasterHistoryEmployee } from "../services/PositionService"; import { PosMasterEmployeeHistory } from "../entities/PosMasterEmployeeHistory"; import { KeycloakAttributeService } from "../services/KeycloakAttributeService"; @@ -1058,11 +1057,11 @@ export class EmployeePositionController extends Controller { let checkChildConditions: any = {}; let keywordAsInt: any; let searchShortName = "1=1"; - let searchShortName0 = `CONCAT(orgRoot.orgRootShortName," ",posMaster.posMasterNo)`; - let searchShortName1 = `CONCAT(orgChild1.orgChild1ShortName," ",posMaster.posMasterNo)`; - let searchShortName2 = `CONCAT(orgChild2.orgChild2ShortName," ",posMaster.posMasterNo)`; - let searchShortName3 = `CONCAT(orgChild3.orgChild3ShortName," ",posMaster.posMasterNo)`; - let searchShortName4 = `CONCAT(orgChild4.orgChild4ShortName," ",posMaster.posMasterNo)`; + let searchShortName0 = `CONCAT_WS(' ',orgRoot.orgRootShortName,NULLIF(posMaster.posMasterNoPrefix,''),posMaster.posMasterNo,NULLIF(posMaster.posMasterNoSuffix,''))`; + let searchShortName1 = `CONCAT_WS(' ',orgChild1.orgChild1ShortName,NULLIF(posMaster.posMasterNoPrefix,''),posMaster.posMasterNo,NULLIF(posMaster.posMasterNoSuffix,''))`; + let searchShortName2 = `CONCAT_WS(' ',orgChild2.orgChild2ShortName,NULLIF(posMaster.posMasterNoPrefix,''),posMaster.posMasterNo,NULLIF(posMaster.posMasterNoSuffix,''))`; + let searchShortName3 = `CONCAT_WS(' ',orgChild3.orgChild3ShortName,NULLIF(posMaster.posMasterNoPrefix,''),posMaster.posMasterNo,NULLIF(posMaster.posMasterNoSuffix,''))`; + let searchShortName4 = `CONCAT_WS(' ',orgChild4.orgChild4ShortName,NULLIF(posMaster.posMasterNoPrefix,''),posMaster.posMasterNo,NULLIF(posMaster.posMasterNoSuffix,''))`; let _data = await new permission().PermissionOrgList(request, "SYS_ORG_EMP"); if (body.type === 0) { typeCondition = { @@ -1072,7 +1071,7 @@ export class EmployeePositionController extends Controller { checkChildConditions = { orgChild1Id: IsNull(), }; - searchShortName = `CONCAT(orgRoot.orgRootShortName," ",posMaster.posMasterNo) like '%${body.keyword}%'`; + searchShortName = `CONCAT_WS(' ',orgRoot.orgRootShortName,NULLIF(posMaster.posMasterNoPrefix,''),posMaster.posMasterNo,NULLIF(posMaster.posMasterNoSuffix,'')) like '%${body.keyword}%'`; } else { } } else if (body.type === 1) { @@ -1083,7 +1082,7 @@ export class EmployeePositionController extends Controller { checkChildConditions = { orgChild2Id: IsNull(), }; - searchShortName = `CONCAT(orgChild1.orgChild1ShortName," ",posMaster.posMasterNo) like '%${body.keyword}%'`; + searchShortName = `CONCAT_WS(' ',orgChild1.orgChild1ShortName,NULLIF(posMaster.posMasterNoPrefix,''),posMaster.posMasterNo,NULLIF(posMaster.posMasterNoSuffix,'')) like '%${body.keyword}%'`; } else { } } else if (body.type === 2) { @@ -1094,7 +1093,7 @@ export class EmployeePositionController extends Controller { checkChildConditions = { orgChild3Id: IsNull(), }; - searchShortName = `CONCAT(orgChild2.orgChild2ShortName," ",posMaster.posMasterNo) like '%${body.keyword}%'`; + searchShortName = `CONCAT_WS(' ',orgChild2.orgChild2ShortName,NULLIF(posMaster.posMasterNoPrefix,''),posMaster.posMasterNo,NULLIF(posMaster.posMasterNoSuffix,'')) like '%${body.keyword}%'`; } else { } } else if (body.type === 3) { @@ -1105,14 +1104,14 @@ export class EmployeePositionController extends Controller { checkChildConditions = { orgChild4Id: IsNull(), }; - searchShortName = `CONCAT(orgChild3.orgChild3ShortName," ",posMaster.posMasterNo) like '%${body.keyword}%'`; + searchShortName = `CONCAT_WS(' ',orgChild3.orgChild3ShortName,NULLIF(posMaster.posMasterNoPrefix,''),posMaster.posMasterNo,NULLIF(posMaster.posMasterNoSuffix,'')) like '%${body.keyword}%'`; } else { } } else if (body.type === 4) { typeCondition = { orgChild4Id: body.id, }; - searchShortName = `CONCAT(orgChild4.orgChild4ShortName," ",posMaster.posMasterNo) like '%${body.keyword}%'`; + searchShortName = `CONCAT_WS(' ',orgChild4.orgChild4ShortName,NULLIF(posMaster.posMasterNoPrefix,''),posMaster.posMasterNo,NULLIF(posMaster.posMasterNoSuffix,'')) like '%${body.keyword}%'`; } let findPosition: any; let masterId = new Array(); @@ -1140,10 +1139,8 @@ export class EmployeePositionController extends Controller { select: ["posMasterId"], }); masterId = masterId.concat(findPosition.map((position: any) => position.posMasterId)); - keywordAsInt = body.keyword == null ? null : parseInt(body.keyword, 10); - if (isNaN(keywordAsInt)) { - keywordAsInt = "P@ssw0rd!z"; - } + const numericMatch = body.keyword == null ? null : body.keyword.match(/\d+/); + keywordAsInt = numericMatch ? parseInt(numericMatch[0], 10) : null; masterId = [...new Set(masterId)]; } @@ -1158,7 +1155,7 @@ export class EmployeePositionController extends Controller { ...(body.keyword && (masterId.length > 0 ? { id: In(masterId) } - : { posMasterNo: Like(`%${body.keyword}%`) })), + : /^\d+$/.test(body.keyword) ? { posMasterNo: keywordAsInt } : { posMasterNo: Like(`%${body.keyword}%`) })), }, ]; @@ -2379,26 +2376,47 @@ export class EmployeePositionController extends Controller { dataMaster.positions.forEach(async (position) => { if (position.id === requestBody.position) { position.positionIsSelected = true; - const profile = await this.profileRepository.findOne({ - where: { id: requestBody.profileId }, - }); - if (profile != null) { - const _null: any = null; - profile.posLevelId = position?.posLevelId ?? _null; - profile.posTypeId = position?.posTypeId ?? _null; - profile.position = position?.positionName ?? _null; - await this.profileRepository.save(profile); - } } else { position.positionIsSelected = false; } await this.employeePositionRepository.save(position); }); - + const _null: any = null; + const before = null; dataMaster.isSit = requestBody.isSit; - dataMaster.current_holderId = requestBody.profileId; dataMaster.lastUpdatedAt = new Date(); - // dataMaster.next_holderId = requestBody.profileId; + + //เช็คถ้า revision ปัจจุบันให้ปั๊มที่ profile + const chkRevision = await this.orgRevisionRepository.findOne({ + where: { id: dataMaster.orgRevisionId }, + }); + if (chkRevision?.orgRevisionIsCurrent) { + const _profile = await this.profileRepository.findOne({ + where: { id: requestBody.profileId }, + }); + if (_profile) { + let _position = await this.employeePositionRepository.findOne({ + where: { id: requestBody.position, posMasterId: requestBody.posMaster }, + }); + if (_position) { + + // ถ้าไม่ใช่ตำแหน่งนั่งทับ (isSit = false) ถึงจะอัพเดทตำแหน่งในทะเบียนประวัติ + if (!dataMaster.isSit) { + _profile.position = _position.positionName; + _profile.posTypeId = _position.posTypeId; + _profile.posLevelId = _position.posLevelId; + } + await this.profileRepository.save(_profile); + setLogDataDiff(request, { before, after: _profile }); + } + } + dataMaster.current_holderId = requestBody.profileId; + dataMaster.next_holderId = _null; + } else { + dataMaster.next_holderId = requestBody.profileId; + dataMaster.current_holderId = _null; + } + await this.employeePosMasterRepository.save(dataMaster); await CreatePosMasterHistoryEmployee(dataMaster.id, request); diff --git a/src/controllers/EmployeeTempPositionController.ts b/src/controllers/EmployeeTempPositionController.ts index e5229e67..ec17bef5 100644 --- a/src/controllers/EmployeeTempPositionController.ts +++ b/src/controllers/EmployeeTempPositionController.ts @@ -777,11 +777,11 @@ export class EmployeeTempPositionController extends Controller { let checkChildConditions: any = {}; let keywordAsInt: any; let searchShortName = "1=1"; - let searchShortName0 = `CONCAT(orgRoot.orgRootShortName," ",posMaster.posMasterNo)`; - let searchShortName1 = `CONCAT(orgChild1.orgChild1ShortName," ",posMaster.posMasterNo)`; - let searchShortName2 = `CONCAT(orgChild2.orgChild2ShortName," ",posMaster.posMasterNo)`; - let searchShortName3 = `CONCAT(orgChild3.orgChild3ShortName," ",posMaster.posMasterNo)`; - let searchShortName4 = `CONCAT(orgChild4.orgChild4ShortName," ",posMaster.posMasterNo)`; + let searchShortName0 = `CONCAT(orgRoot.orgRootShortName,' ',posMaster.posMasterNo)`; + let searchShortName1 = `CONCAT(orgChild1.orgChild1ShortName,' ',posMaster.posMasterNo)`; + let searchShortName2 = `CONCAT(orgChild2.orgChild2ShortName,' ',posMaster.posMasterNo)`; + let searchShortName3 = `CONCAT(orgChild3.orgChild3ShortName,' ',posMaster.posMasterNo)`; + let searchShortName4 = `CONCAT(orgChild4.orgChild4ShortName,' ',posMaster.posMasterNo)`; let _data = await new permission().PermissionOrgList(request, "SYS_ORG_TEMP"); if (body.type === 0) { typeCondition = { @@ -791,7 +791,7 @@ export class EmployeeTempPositionController extends Controller { checkChildConditions = { orgChild1Id: IsNull(), }; - searchShortName = `CONCAT(orgRoot.orgRootShortName," ",posMaster.posMasterNo) like '%${body.keyword}%'`; + searchShortName = `CONCAT(orgRoot.orgRootShortName,' ',posMaster.posMasterNo) like '%${body.keyword}%'`; } else { } } else if (body.type === 1) { @@ -802,7 +802,7 @@ export class EmployeeTempPositionController extends Controller { checkChildConditions = { orgChild2Id: IsNull(), }; - searchShortName = `CONCAT(orgChild1.orgChild1ShortName," ",posMaster.posMasterNo) like '%${body.keyword}%'`; + searchShortName = `CONCAT(orgChild1.orgChild1ShortName,' ',posMaster.posMasterNo) like '%${body.keyword}%'`; } else { } } else if (body.type === 2) { @@ -813,7 +813,7 @@ export class EmployeeTempPositionController extends Controller { checkChildConditions = { orgChild3Id: IsNull(), }; - searchShortName = `CONCAT(orgChild2.orgChild2ShortName," ",posMaster.posMasterNo) like '%${body.keyword}%'`; + searchShortName = `CONCAT(orgChild2.orgChild2ShortName,' ',posMaster.posMasterNo) like '%${body.keyword}%'`; } else { } } else if (body.type === 3) { @@ -824,14 +824,14 @@ export class EmployeeTempPositionController extends Controller { checkChildConditions = { orgChild4Id: IsNull(), }; - searchShortName = `CONCAT(orgChild3.orgChild3ShortName," ",posMaster.posMasterNo) like '%${body.keyword}%'`; + searchShortName = `CONCAT(orgChild3.orgChild3ShortName,' ',posMaster.posMasterNo) like '%${body.keyword}%'`; } else { } } else if (body.type === 4) { typeCondition = { orgChild4Id: body.id, }; - searchShortName = `CONCAT(orgChild4.orgChild4ShortName," ",posMaster.posMasterNo) like '%${body.keyword}%'`; + searchShortName = `CONCAT(orgChild4.orgChild4ShortName,' ',posMaster.posMasterNo) like '%${body.keyword}%'`; } let findPosition: any; let masterId = new Array(); @@ -859,10 +859,8 @@ export class EmployeeTempPositionController extends Controller { select: ["posMasterTempId"], }); masterId = masterId.concat(findPosition.map((position: any) => position.posMasterTempId)); - keywordAsInt = body.keyword == null ? null : parseInt(body.keyword, 10); - if (isNaN(keywordAsInt)) { - keywordAsInt = "P@ssw0rd!z"; - } + const numericMatch = body.keyword == null ? null : body.keyword.match(/\d+/); + keywordAsInt = numericMatch ? parseInt(numericMatch[0], 10) : null; masterId = [...new Set(masterId)]; } @@ -877,7 +875,7 @@ export class EmployeeTempPositionController extends Controller { ...(body.keyword && (masterId.length > 0 ? { id: In(masterId) } - : { posMasterNo: Like(`%${body.keyword}%`) })), + : /^\d+$/.test(body.keyword) ? { posMasterNo: keywordAsInt } : { posMasterNo: Like(`%${body.keyword}%`) })), }, ]; let query = AppDataSource.getRepository(EmployeeTempPosMaster) diff --git a/src/controllers/OrgChild1Controller.ts b/src/controllers/OrgChild1Controller.ts index 6e44af05..6817d6fd 100644 --- a/src/controllers/OrgChild1Controller.ts +++ b/src/controllers/OrgChild1Controller.ts @@ -74,6 +74,11 @@ export class OrgChild1Controller { DIVISION_CODE: orgChild1.DIVISION_CODE, SECTION_CODE: orgChild1.SECTION_CODE, JOB_CODE: orgChild1.JOB_CODE, + ROOT_CODE: orgChild1.ROOT_CODE, + CHILD1_CODE: orgChild1.CHILD1_CODE, + CHILD2_CODE: orgChild1.CHILD2_CODE, + CHILD3_CODE: orgChild1.CHILD3_CODE, + CHILD4_CODE: orgChild1.CHILD4_CODE, orgCode: orgChild1.orgRoot.orgRootCode + orgChild1.orgChild1Code, }; return new HttpSuccess(getOrgChild1); @@ -346,6 +351,11 @@ export class OrgChild1Controller { DIVISION_CODE: requestBody.DIVISION_CODE != null ? requestBody.DIVISION_CODE : _null, SECTION_CODE: requestBody.SECTION_CODE != null ? requestBody.SECTION_CODE : _null, JOB_CODE: requestBody.JOB_CODE != null ? requestBody.JOB_CODE : _null, + ROOT_CODE: requestBody.ROOT_CODE != null ? requestBody.ROOT_CODE : _null, + CHILD1_CODE: requestBody.CHILD1_CODE != null ? requestBody.CHILD1_CODE : _null, + CHILD2_CODE: requestBody.CHILD2_CODE != null ? requestBody.CHILD2_CODE : _null, + CHILD3_CODE: requestBody.CHILD3_CODE != null ? requestBody.CHILD3_CODE : _null, + CHILD4_CODE: requestBody.CHILD4_CODE != null ? requestBody.CHILD4_CODE : _null, isOfficer: requestBody.isOfficer, isInformation: requestBody.isInformation, orgChild1PhoneEx: requestBody.orgChild1PhoneEx, diff --git a/src/controllers/OrgChild2Controller.ts b/src/controllers/OrgChild2Controller.ts index 28ce564f..650cc96e 100644 --- a/src/controllers/OrgChild2Controller.ts +++ b/src/controllers/OrgChild2Controller.ts @@ -85,6 +85,11 @@ export class OrgChild2Controller extends Controller { DIVISION_CODE: orgChild2.DIVISION_CODE, SECTION_CODE: orgChild2.SECTION_CODE, JOB_CODE: orgChild2.JOB_CODE, + ROOT_CODE: orgChild2.ROOT_CODE, + CHILD1_CODE: orgChild2.CHILD1_CODE, + CHILD2_CODE: orgChild2.CHILD2_CODE, + CHILD3_CODE: orgChild2.CHILD3_CODE, + CHILD4_CODE: orgChild2.CHILD4_CODE, orgCode: orgChild2.orgRoot.orgRootCode + orgChild2.orgChild2Code, }; return new HttpSuccess(getOrgChild2); @@ -252,6 +257,11 @@ export class OrgChild2Controller extends Controller { DIVISION_CODE: requestBody.DIVISION_CODE != null ? requestBody.DIVISION_CODE : _null, SECTION_CODE: requestBody.SECTION_CODE != null ? requestBody.SECTION_CODE : _null, JOB_CODE: requestBody.JOB_CODE != null ? requestBody.JOB_CODE : _null, + ROOT_CODE: requestBody.ROOT_CODE != null ? requestBody.ROOT_CODE : _null, + CHILD1_CODE: requestBody.CHILD1_CODE != null ? requestBody.CHILD1_CODE : _null, + CHILD2_CODE: requestBody.CHILD2_CODE != null ? requestBody.CHILD2_CODE : _null, + CHILD3_CODE: requestBody.CHILD3_CODE != null ? requestBody.CHILD3_CODE : _null, + CHILD4_CODE: requestBody.CHILD4_CODE != null ? requestBody.CHILD4_CODE : _null, orgChild2PhoneEx: requestBody.orgChild2PhoneEx, orgChild2PhoneIn: requestBody.orgChild2PhoneIn, orgChild2Fax: requestBody.orgChild2Fax, diff --git a/src/controllers/OrgChild3Controller.ts b/src/controllers/OrgChild3Controller.ts index 4ed10804..d8cc5939 100644 --- a/src/controllers/OrgChild3Controller.ts +++ b/src/controllers/OrgChild3Controller.ts @@ -69,6 +69,11 @@ export class OrgChild3Controller { DIVISION_CODE: orgChild3.DIVISION_CODE, SECTION_CODE: orgChild3.SECTION_CODE, JOB_CODE: orgChild3.JOB_CODE, + ROOT_CODE: orgChild3.ROOT_CODE, + CHILD1_CODE: orgChild3.CHILD1_CODE, + CHILD2_CODE: orgChild3.CHILD2_CODE, + CHILD3_CODE: orgChild3.CHILD3_CODE, + CHILD4_CODE: orgChild3.CHILD4_CODE, orgCode: orgChild3.orgRoot.orgRootCode + orgChild3.orgChild3Code, }; return new HttpSuccess(getOrgChild3); @@ -207,6 +212,11 @@ export class OrgChild3Controller { DIVISION_CODE: requestBody.DIVISION_CODE != null ? requestBody.DIVISION_CODE : _null, SECTION_CODE: requestBody.SECTION_CODE != null ? requestBody.SECTION_CODE : _null, JOB_CODE: requestBody.JOB_CODE != null ? requestBody.JOB_CODE : _null, + ROOT_CODE: requestBody.ROOT_CODE != null ? requestBody.ROOT_CODE : _null, + CHILD1_CODE: requestBody.CHILD1_CODE != null ? requestBody.CHILD1_CODE : _null, + CHILD2_CODE: requestBody.CHILD2_CODE != null ? requestBody.CHILD2_CODE : _null, + CHILD3_CODE: requestBody.CHILD3_CODE != null ? requestBody.CHILD3_CODE : _null, + CHILD4_CODE: requestBody.CHILD4_CODE != null ? requestBody.CHILD4_CODE : _null, orgChild3PhoneEx: requestBody.orgChild3PhoneEx, orgChild3PhoneIn: requestBody.orgChild3PhoneIn, orgChild3Fax: requestBody.orgChild3Fax, diff --git a/src/controllers/OrgChild4Controller.ts b/src/controllers/OrgChild4Controller.ts index e18c15f9..4dac9e82 100644 --- a/src/controllers/OrgChild4Controller.ts +++ b/src/controllers/OrgChild4Controller.ts @@ -82,6 +82,11 @@ export class OrgChild4Controller extends Controller { DIVISION_CODE: orgChild4.DIVISION_CODE, SECTION_CODE: orgChild4.SECTION_CODE, JOB_CODE: orgChild4.JOB_CODE, + ROOT_CODE: orgChild4.ROOT_CODE, + CHILD1_CODE: orgChild4.CHILD1_CODE, + CHILD2_CODE: orgChild4.CHILD2_CODE, + CHILD3_CODE: orgChild4.CHILD3_CODE, + CHILD4_CODE: orgChild4.CHILD4_CODE, orgCode: orgChild4.orgRoot.orgRootCode + orgChild4.orgChild4Code, }; return new HttpSuccess(getOrgChild4); @@ -254,6 +259,11 @@ export class OrgChild4Controller extends Controller { DIVISION_CODE: requestBody.DIVISION_CODE != null ? requestBody.DIVISION_CODE : _null, SECTION_CODE: requestBody.SECTION_CODE != null ? requestBody.SECTION_CODE : _null, JOB_CODE: requestBody.JOB_CODE != null ? requestBody.JOB_CODE : _null, + ROOT_CODE: requestBody.ROOT_CODE != null ? requestBody.ROOT_CODE : _null, + CHILD1_CODE: requestBody.CHILD1_CODE != null ? requestBody.CHILD1_CODE : _null, + CHILD2_CODE: requestBody.CHILD2_CODE != null ? requestBody.CHILD2_CODE : _null, + CHILD3_CODE: requestBody.CHILD3_CODE != null ? requestBody.CHILD3_CODE : _null, + CHILD4_CODE: requestBody.CHILD4_CODE != null ? requestBody.CHILD4_CODE : _null, orgChild4PhoneEx: requestBody.orgChild4PhoneEx, orgChild4PhoneIn: requestBody.orgChild4PhoneIn, orgChild4Fax: requestBody.orgChild4Fax, diff --git a/src/controllers/OrgRootController.ts b/src/controllers/OrgRootController.ts index 45bf1436..97f3f6a3 100644 --- a/src/controllers/OrgRootController.ts +++ b/src/controllers/OrgRootController.ts @@ -83,6 +83,11 @@ export class OrgRootController extends Controller { DIVISION_CODE: orgRoot.DIVISION_CODE, SECTION_CODE: orgRoot.SECTION_CODE, JOB_CODE: orgRoot.JOB_CODE, + ROOT_CODE: orgRoot.ROOT_CODE, + CHILD1_CODE: orgRoot.CHILD1_CODE, + CHILD2_CODE: orgRoot.CHILD2_CODE, + CHILD3_CODE: orgRoot.CHILD3_CODE, + CHILD4_CODE: orgRoot.CHILD4_CODE, orgCode: orgRoot.orgRootCode + "00", }; return new HttpSuccess(getOrgRoot); @@ -350,6 +355,11 @@ export class OrgRootController extends Controller { DIVISION_CODE: requestBody.DIVISION_CODE != null ? requestBody.DIVISION_CODE : _null, SECTION_CODE: requestBody.SECTION_CODE != null ? requestBody.SECTION_CODE : _null, JOB_CODE: requestBody.JOB_CODE != null ? requestBody.JOB_CODE : _null, + ROOT_CODE: requestBody.ROOT_CODE != null ? requestBody.ROOT_CODE : _null, + CHILD1_CODE: requestBody.CHILD1_CODE != null ? requestBody.CHILD1_CODE : _null, + CHILD2_CODE: requestBody.CHILD2_CODE != null ? requestBody.CHILD2_CODE : _null, + CHILD3_CODE: requestBody.CHILD3_CODE != null ? requestBody.CHILD3_CODE : _null, + CHILD4_CODE: requestBody.CHILD4_CODE != null ? requestBody.CHILD4_CODE : _null, }); await this.orgRootRepository.save(orgRoot, { data: request }); setLogDataDiff(request, { before, after: orgRoot }); diff --git a/src/controllers/OrganizationController.ts b/src/controllers/OrganizationController.ts index eb91c408..55f5ac93 100644 --- a/src/controllers/OrganizationController.ts +++ b/src/controllers/OrganizationController.ts @@ -214,6 +214,7 @@ export class OrganizationController extends Controller { await sendToQueueOrgDraft(msg); return new HttpSuccess("Draft is being created... Processing in the background."); } catch (error: any) { + console.error("Error creating draft organization:", error); throw error; } } @@ -467,157 +468,157 @@ export class OrganizationController extends Controller { const orgChild1Data = orgRootIds && orgRootIds.length > 0 ? await AppDataSource.getRepository(OrgChild1) - .createQueryBuilder("orgChild1") - .where("orgChild1.orgRootId IN (:...ids)", { ids: orgRootIds }) - // .andWhere( - // _data.child1 != undefined && _data.child1 != null - // ? _data.child1[0] != null - // ? `orgChild1.id IN (:...node)` - // : `orgChild1.id is null` - // : "1=1", - // { - // node: _data.child1, - // }, - // ) - .select([ - "orgChild1.id", - "orgChild1.isOfficer", - "orgChild1.isInformation", - "orgChild1.orgChild1Name", - "orgChild1.orgChild1ShortName", - "orgChild1.orgChild1Code", - "orgChild1.orgChild1Order", - "orgChild1.orgChild1PhoneEx", - "orgChild1.orgChild1PhoneIn", - "orgChild1.orgChild1Fax", - "orgChild1.orgRootId", - "orgChild1.orgChild1Rank", - "orgChild1.orgChild1RankSub", - "orgChild1.DEPARTMENT_CODE", - "orgChild1.DIVISION_CODE", - "orgChild1.SECTION_CODE", - "orgChild1.JOB_CODE", - "orgChild1.responsibility", - ]) - .orderBy("orgChild1.orgChild1Order", "ASC") - .getMany() + .createQueryBuilder("orgChild1") + .where("orgChild1.orgRootId IN (:...ids)", { ids: orgRootIds }) + // .andWhere( + // _data.child1 != undefined && _data.child1 != null + // ? _data.child1[0] != null + // ? `orgChild1.id IN (:...node)` + // : `orgChild1.id is null` + // : "1=1", + // { + // node: _data.child1, + // }, + // ) + .select([ + "orgChild1.id", + "orgChild1.isOfficer", + "orgChild1.isInformation", + "orgChild1.orgChild1Name", + "orgChild1.orgChild1ShortName", + "orgChild1.orgChild1Code", + "orgChild1.orgChild1Order", + "orgChild1.orgChild1PhoneEx", + "orgChild1.orgChild1PhoneIn", + "orgChild1.orgChild1Fax", + "orgChild1.orgRootId", + "orgChild1.orgChild1Rank", + "orgChild1.orgChild1RankSub", + "orgChild1.DEPARTMENT_CODE", + "orgChild1.DIVISION_CODE", + "orgChild1.SECTION_CODE", + "orgChild1.JOB_CODE", + "orgChild1.responsibility", + ]) + .orderBy("orgChild1.orgChild1Order", "ASC") + .getMany() : []; const orgChild1Ids = orgChild1Data.map((orgChild1) => orgChild1.id) || null; const orgChild2Data = orgChild1Ids && orgChild1Ids.length > 0 ? await AppDataSource.getRepository(OrgChild2) - .createQueryBuilder("orgChild2") - .where("orgChild2.orgChild1Id IN (:...ids)", { ids: orgChild1Ids }) - // .andWhere( - // _data.child2 != undefined && _data.child2 != null - // ? _data.child2[0] != null - // ? `orgChild2.id IN (:...node)` - // : `orgChild2.id is null` - // : "1=1", - // { - // node: _data.child2, - // }, - // ) - .select([ - "orgChild2.id", - "orgChild2.orgChild2Name", - "orgChild2.orgChild2ShortName", - "orgChild2.orgChild2Code", - "orgChild2.orgChild2Order", - "orgChild2.orgChild2PhoneEx", - "orgChild2.orgChild2PhoneIn", - "orgChild2.orgChild2Fax", - "orgChild2.orgRootId", - "orgChild2.orgChild2Rank", - "orgChild2.orgChild2RankSub", - "orgChild2.DEPARTMENT_CODE", - "orgChild2.DIVISION_CODE", - "orgChild2.SECTION_CODE", - "orgChild2.JOB_CODE", - "orgChild2.orgChild1Id", - "orgChild2.responsibility", - ]) - .orderBy("orgChild2.orgChild2Order", "ASC") - .getMany() + .createQueryBuilder("orgChild2") + .where("orgChild2.orgChild1Id IN (:...ids)", { ids: orgChild1Ids }) + // .andWhere( + // _data.child2 != undefined && _data.child2 != null + // ? _data.child2[0] != null + // ? `orgChild2.id IN (:...node)` + // : `orgChild2.id is null` + // : "1=1", + // { + // node: _data.child2, + // }, + // ) + .select([ + "orgChild2.id", + "orgChild2.orgChild2Name", + "orgChild2.orgChild2ShortName", + "orgChild2.orgChild2Code", + "orgChild2.orgChild2Order", + "orgChild2.orgChild2PhoneEx", + "orgChild2.orgChild2PhoneIn", + "orgChild2.orgChild2Fax", + "orgChild2.orgRootId", + "orgChild2.orgChild2Rank", + "orgChild2.orgChild2RankSub", + "orgChild2.DEPARTMENT_CODE", + "orgChild2.DIVISION_CODE", + "orgChild2.SECTION_CODE", + "orgChild2.JOB_CODE", + "orgChild2.orgChild1Id", + "orgChild2.responsibility", + ]) + .orderBy("orgChild2.orgChild2Order", "ASC") + .getMany() : []; const orgChild2Ids = orgChild2Data.map((orgChild2) => orgChild2.id) || null; const orgChild3Data = orgChild2Ids && orgChild2Ids.length > 0 ? await AppDataSource.getRepository(OrgChild3) - .createQueryBuilder("orgChild3") - .where("orgChild3.orgChild2Id IN (:...ids)", { ids: orgChild2Ids }) - // .andWhere( - // _data.child3 != undefined && _data.child3 != null - // ? _data.child3[0] != null - // ? `orgChild3.id IN (:...node)` - // : `orgChild3.id is null` - // : "1=1", - // { - // node: _data.child3, - // }, - // ) - .select([ - "orgChild3.id", - "orgChild3.orgChild3Name", - "orgChild3.orgChild3ShortName", - "orgChild3.orgChild3Code", - "orgChild3.orgChild3Order", - "orgChild3.orgChild3PhoneEx", - "orgChild3.orgChild3PhoneIn", - "orgChild3.orgChild3Fax", - "orgChild3.orgRootId", - "orgChild3.orgChild3Rank", - "orgChild3.orgChild3RankSub", - "orgChild3.DEPARTMENT_CODE", - "orgChild3.DIVISION_CODE", - "orgChild3.SECTION_CODE", - "orgChild3.JOB_CODE", - "orgChild3.orgChild2Id", - "orgChild3.responsibility", - ]) - .orderBy("orgChild3.orgChild3Order", "ASC") - .getMany() + .createQueryBuilder("orgChild3") + .where("orgChild3.orgChild2Id IN (:...ids)", { ids: orgChild2Ids }) + // .andWhere( + // _data.child3 != undefined && _data.child3 != null + // ? _data.child3[0] != null + // ? `orgChild3.id IN (:...node)` + // : `orgChild3.id is null` + // : "1=1", + // { + // node: _data.child3, + // }, + // ) + .select([ + "orgChild3.id", + "orgChild3.orgChild3Name", + "orgChild3.orgChild3ShortName", + "orgChild3.orgChild3Code", + "orgChild3.orgChild3Order", + "orgChild3.orgChild3PhoneEx", + "orgChild3.orgChild3PhoneIn", + "orgChild3.orgChild3Fax", + "orgChild3.orgRootId", + "orgChild3.orgChild3Rank", + "orgChild3.orgChild3RankSub", + "orgChild3.DEPARTMENT_CODE", + "orgChild3.DIVISION_CODE", + "orgChild3.SECTION_CODE", + "orgChild3.JOB_CODE", + "orgChild3.orgChild2Id", + "orgChild3.responsibility", + ]) + .orderBy("orgChild3.orgChild3Order", "ASC") + .getMany() : []; const orgChild3Ids = orgChild3Data.map((orgChild3) => orgChild3.id) || null; const orgChild4Data = orgChild3Ids && orgChild3Ids.length > 0 ? await AppDataSource.getRepository(OrgChild4) - .createQueryBuilder("orgChild4") - .where("orgChild4.orgChild3Id IN (:...ids)", { ids: orgChild3Ids }) - // .andWhere( - // _data.child4 != undefined && _data.child4 != null - // ? _data.child4[0] != null - // ? `orgChild4.id IN (:...node)` - // : `orgChild4.id is null` - // : "1=1", - // { - // node: _data.child4, - // }, - // ) - .select([ - "orgChild4.id", - "orgChild4.orgChild4Name", - "orgChild4.orgChild4ShortName", - "orgChild4.orgChild4Code", - "orgChild4.orgChild4Order", - "orgChild4.orgChild4PhoneEx", - "orgChild4.orgChild4PhoneIn", - "orgChild4.orgChild4Fax", - "orgChild4.orgRootId", - "orgChild4.orgChild4Rank", - "orgChild4.orgChild4RankSub", - "orgChild4.DEPARTMENT_CODE", - "orgChild4.DIVISION_CODE", - "orgChild4.SECTION_CODE", - "orgChild4.JOB_CODE", - "orgChild4.orgChild3Id", - "orgChild4.responsibility", - ]) - .orderBy("orgChild4.orgChild4Order", "ASC") - .getMany() + .createQueryBuilder("orgChild4") + .where("orgChild4.orgChild3Id IN (:...ids)", { ids: orgChild3Ids }) + // .andWhere( + // _data.child4 != undefined && _data.child4 != null + // ? _data.child4[0] != null + // ? `orgChild4.id IN (:...node)` + // : `orgChild4.id is null` + // : "1=1", + // { + // node: _data.child4, + // }, + // ) + .select([ + "orgChild4.id", + "orgChild4.orgChild4Name", + "orgChild4.orgChild4ShortName", + "orgChild4.orgChild4Code", + "orgChild4.orgChild4Order", + "orgChild4.orgChild4PhoneEx", + "orgChild4.orgChild4PhoneIn", + "orgChild4.orgChild4Fax", + "orgChild4.orgRootId", + "orgChild4.orgChild4Rank", + "orgChild4.orgChild4RankSub", + "orgChild4.DEPARTMENT_CODE", + "orgChild4.DIVISION_CODE", + "orgChild4.SECTION_CODE", + "orgChild4.JOB_CODE", + "orgChild4.orgChild3Id", + "orgChild4.responsibility", + ]) + .orderBy("orgChild4.orgChild4Order", "ASC") + .getMany() : []; const formattedData = await Promise.all( @@ -1270,13 +1271,13 @@ export class OrganizationController extends Controller { where: orgRevision.orgRevisionIsCurrent && !orgRevision.orgRevisionIsDraft ? { - orgRevisionId: id, - current_holderId: profile.id, - } + orgRevisionId: id, + current_holderId: profile.id, + } : { - orgRevisionId: id, - next_holderId: profile.id, - }, + orgRevisionId: id, + next_holderId: profile.id, + }, }); if (!posMaster) return new HttpSuccess([]); @@ -1746,6 +1747,11 @@ export class OrganizationController extends Controller { "orgRoot.DIVISION_CODE", "orgRoot.SECTION_CODE", "orgRoot.JOB_CODE", + "orgRoot.ROOT_CODE", + "orgRoot.CHILD1_CODE", + "orgRoot.CHILD2_CODE", + "orgRoot.CHILD3_CODE", + "orgRoot.CHILD4_CODE", "orgRoot.responsibility", ]) .orderBy("orgRoot.orgRootOrder", "ASC") @@ -1754,161 +1760,181 @@ export class OrganizationController extends Controller { const orgChild1Data = orgRootIds && orgRootIds.length > 0 ? await AppDataSource.getRepository(OrgChild1) - .createQueryBuilder("orgChild1") - .where("orgChild1.orgRootId IN (:...ids)", { ids: orgRootIds }) - .andWhere( - _data.child1 !== undefined && _data.child1 !== null - ? _data.child1[0] !== null - ? `orgChild1.id IN (:...node)` - : `orgChild1.id is null` - : "1=1", - { - node: _data.child1, - }, - ) - .select([ - "orgChild1.id", - "orgChild1.misId", - "orgChild1.isOfficer", - "orgChild1.isInformation", - "orgChild1.orgChild1Name", - "orgChild1.orgChild1ShortName", - "orgChild1.orgChild1Code", - "orgChild1.orgChild1Order", - "orgChild1.orgChild1PhoneEx", - "orgChild1.orgChild1PhoneIn", - "orgChild1.orgChild1Fax", - "orgChild1.orgRootId", - "orgChild1.orgChild1Rank", - "orgChild1.orgChild1RankSub", - "orgChild1.DEPARTMENT_CODE", - "orgChild1.DIVISION_CODE", - "orgChild1.SECTION_CODE", - "orgChild1.JOB_CODE", - "orgChild1.responsibility", - ]) - .orderBy("orgChild1.orgChild1Order", "ASC") - .getMany() + .createQueryBuilder("orgChild1") + .where("orgChild1.orgRootId IN (:...ids)", { ids: orgRootIds }) + .andWhere( + _data.child1 !== undefined && _data.child1 !== null + ? _data.child1[0] !== null + ? `orgChild1.id IN (:...node)` + : `orgChild1.id is null` + : "1=1", + { + node: _data.child1, + }, + ) + .select([ + "orgChild1.id", + "orgChild1.misId", + "orgChild1.isOfficer", + "orgChild1.isInformation", + "orgChild1.orgChild1Name", + "orgChild1.orgChild1ShortName", + "orgChild1.orgChild1Code", + "orgChild1.orgChild1Order", + "orgChild1.orgChild1PhoneEx", + "orgChild1.orgChild1PhoneIn", + "orgChild1.orgChild1Fax", + "orgChild1.orgRootId", + "orgChild1.orgChild1Rank", + "orgChild1.orgChild1RankSub", + "orgChild1.DEPARTMENT_CODE", + "orgChild1.DIVISION_CODE", + "orgChild1.SECTION_CODE", + "orgChild1.JOB_CODE", + "orgChild1.ROOT_CODE", + "orgChild1.CHILD1_CODE", + "orgChild1.CHILD2_CODE", + "orgChild1.CHILD3_CODE", + "orgChild1.CHILD4_CODE", + "orgChild1.responsibility", + ]) + .orderBy("orgChild1.orgChild1Order", "ASC") + .getMany() : []; const orgChild1Ids = orgChild1Data.map((orgChild1) => orgChild1.id) || null; const orgChild2Data = orgChild1Ids && orgChild1Ids.length > 0 ? await AppDataSource.getRepository(OrgChild2) - .createQueryBuilder("orgChild2") - .where("orgChild2.orgChild1Id IN (:...ids)", { ids: orgChild1Ids }) - .andWhere( - _data.child2 !== undefined && _data.child2 !== null - ? _data.child2[0] !== null - ? `orgChild2.id IN (:...node)` - : `orgChild2.id is null` - : "1=1", - { - node: _data.child2, - }, - ) - .select([ - "orgChild2.id", - "orgChild2.misId", - "orgChild2.orgChild2Name", - "orgChild2.orgChild2ShortName", - "orgChild2.orgChild2Code", - "orgChild2.orgChild2Order", - "orgChild2.orgChild2PhoneEx", - "orgChild2.orgChild2PhoneIn", - "orgChild2.orgChild2Fax", - "orgChild2.orgRootId", - "orgChild2.orgChild2Rank", - "orgChild2.orgChild2RankSub", - "orgChild2.DEPARTMENT_CODE", - "orgChild2.DIVISION_CODE", - "orgChild2.SECTION_CODE", - "orgChild2.JOB_CODE", - "orgChild2.orgChild1Id", - "orgChild2.responsibility", - ]) - .orderBy("orgChild2.orgChild2Order", "ASC") - .getMany() + .createQueryBuilder("orgChild2") + .where("orgChild2.orgChild1Id IN (:...ids)", { ids: orgChild1Ids }) + .andWhere( + _data.child2 !== undefined && _data.child2 !== null + ? _data.child2[0] !== null + ? `orgChild2.id IN (:...node)` + : `orgChild2.id is null` + : "1=1", + { + node: _data.child2, + }, + ) + .select([ + "orgChild2.id", + "orgChild2.misId", + "orgChild2.orgChild2Name", + "orgChild2.orgChild2ShortName", + "orgChild2.orgChild2Code", + "orgChild2.orgChild2Order", + "orgChild2.orgChild2PhoneEx", + "orgChild2.orgChild2PhoneIn", + "orgChild2.orgChild2Fax", + "orgChild2.orgRootId", + "orgChild2.orgChild2Rank", + "orgChild2.orgChild2RankSub", + "orgChild2.DEPARTMENT_CODE", + "orgChild2.DIVISION_CODE", + "orgChild2.SECTION_CODE", + "orgChild2.JOB_CODE", + "orgChild2.ROOT_CODE", + "orgChild2.CHILD1_CODE", + "orgChild2.CHILD2_CODE", + "orgChild2.CHILD3_CODE", + "orgChild2.CHILD4_CODE", + "orgChild2.orgChild1Id", + "orgChild2.responsibility", + ]) + .orderBy("orgChild2.orgChild2Order", "ASC") + .getMany() : []; const orgChild2Ids = orgChild2Data.map((orgChild2) => orgChild2.id) || null; const orgChild3Data = orgChild2Ids && orgChild2Ids.length > 0 ? await AppDataSource.getRepository(OrgChild3) - .createQueryBuilder("orgChild3") - .where("orgChild3.orgChild2Id IN (:...ids)", { ids: orgChild2Ids }) - .andWhere( - _data.child3 !== undefined && _data.child3 !== null - ? _data.child3[0] !== null - ? `orgChild3.id IN (:...node)` - : `orgChild3.id is null` - : "1=1", - { - node: _data.child3, - }, - ) - .select([ - "orgChild3.id", - "orgChild3.misId", - "orgChild3.orgChild3Name", - "orgChild3.orgChild3ShortName", - "orgChild3.orgChild3Code", - "orgChild3.orgChild3Order", - "orgChild3.orgChild3PhoneEx", - "orgChild3.orgChild3PhoneIn", - "orgChild3.orgChild3Fax", - "orgChild3.orgRootId", - "orgChild3.orgChild3Rank", - "orgChild3.orgChild3RankSub", - "orgChild3.DEPARTMENT_CODE", - "orgChild3.DIVISION_CODE", - "orgChild3.SECTION_CODE", - "orgChild3.JOB_CODE", - "orgChild3.orgChild2Id", - "orgChild3.responsibility", - ]) - .orderBy("orgChild3.orgChild3Order", "ASC") - .getMany() + .createQueryBuilder("orgChild3") + .where("orgChild3.orgChild2Id IN (:...ids)", { ids: orgChild2Ids }) + .andWhere( + _data.child3 !== undefined && _data.child3 !== null + ? _data.child3[0] !== null + ? `orgChild3.id IN (:...node)` + : `orgChild3.id is null` + : "1=1", + { + node: _data.child3, + }, + ) + .select([ + "orgChild3.id", + "orgChild3.misId", + "orgChild3.orgChild3Name", + "orgChild3.orgChild3ShortName", + "orgChild3.orgChild3Code", + "orgChild3.orgChild3Order", + "orgChild3.orgChild3PhoneEx", + "orgChild3.orgChild3PhoneIn", + "orgChild3.orgChild3Fax", + "orgChild3.orgRootId", + "orgChild3.orgChild3Rank", + "orgChild3.orgChild3RankSub", + "orgChild3.DEPARTMENT_CODE", + "orgChild3.DIVISION_CODE", + "orgChild3.SECTION_CODE", + "orgChild3.JOB_CODE", + "orgChild3.ROOT_CODE", + "orgChild3.CHILD1_CODE", + "orgChild3.CHILD2_CODE", + "orgChild3.CHILD3_CODE", + "orgChild3.CHILD4_CODE", + "orgChild3.orgChild2Id", + "orgChild3.responsibility", + ]) + .orderBy("orgChild3.orgChild3Order", "ASC") + .getMany() : []; const orgChild3Ids = orgChild3Data.map((orgChild3) => orgChild3.id) || null; const orgChild4Data = orgChild3Ids && orgChild3Ids.length > 0 ? await AppDataSource.getRepository(OrgChild4) - .createQueryBuilder("orgChild4") - .where("orgChild4.orgChild3Id IN (:...ids)", { ids: orgChild3Ids }) - .andWhere( - _data.child4 !== undefined && _data.child4 !== null - ? _data.child4[0] !== null - ? `orgChild4.id IN (:...node)` - : `orgChild4.id is null` - : "1=1", - { - node: _data.child4, - }, - ) - .select([ - "orgChild4.id", - "orgChild4.misId", - "orgChild4.orgChild4Name", - "orgChild4.orgChild4ShortName", - "orgChild4.orgChild4Code", - "orgChild4.orgChild4Order", - "orgChild4.orgChild4PhoneEx", - "orgChild4.orgChild4PhoneIn", - "orgChild4.orgChild4Fax", - "orgChild4.orgRootId", - "orgChild4.orgChild4Rank", - "orgChild4.orgChild4RankSub", - "orgChild4.DEPARTMENT_CODE", - "orgChild4.DIVISION_CODE", - "orgChild4.SECTION_CODE", - "orgChild4.JOB_CODE", - "orgChild4.orgChild3Id", - "orgChild4.responsibility", - ]) - .orderBy("orgChild4.orgChild4Order", "ASC") - .getMany() + .createQueryBuilder("orgChild4") + .where("orgChild4.orgChild3Id IN (:...ids)", { ids: orgChild3Ids }) + .andWhere( + _data.child4 !== undefined && _data.child4 !== null + ? _data.child4[0] !== null + ? `orgChild4.id IN (:...node)` + : `orgChild4.id is null` + : "1=1", + { + node: _data.child4, + }, + ) + .select([ + "orgChild4.id", + "orgChild4.misId", + "orgChild4.orgChild4Name", + "orgChild4.orgChild4ShortName", + "orgChild4.orgChild4Code", + "orgChild4.orgChild4Order", + "orgChild4.orgChild4PhoneEx", + "orgChild4.orgChild4PhoneIn", + "orgChild4.orgChild4Fax", + "orgChild4.orgRootId", + "orgChild4.orgChild4Rank", + "orgChild4.orgChild4RankSub", + "orgChild4.DEPARTMENT_CODE", + "orgChild4.DIVISION_CODE", + "orgChild4.SECTION_CODE", + "orgChild4.JOB_CODE", + "orgChild4.ROOT_CODE", + "orgChild4.CHILD1_CODE", + "orgChild4.CHILD2_CODE", + "orgChild4.CHILD3_CODE", + "orgChild4.CHILD4_CODE", + "orgChild4.orgChild3Id", + "orgChild4.responsibility", + ]) + .orderBy("orgChild4.orgChild4Order", "ASC") + .getMany() : []; const formattedData = await Promise.all( @@ -1929,6 +1955,11 @@ export class OrganizationController extends Controller { DIVISION_CODE: orgRoot.DIVISION_CODE, SECTION_CODE: orgRoot.SECTION_CODE, JOB_CODE: orgRoot.JOB_CODE, + ROOT_CODE: orgRoot.ROOT_CODE, + CHILD1_CODE: orgRoot.CHILD1_CODE, + CHILD2_CODE: orgRoot.CHILD2_CODE, + CHILD3_CODE: orgRoot.CHILD3_CODE, + CHILD4_CODE: orgRoot.CHILD4_CODE, orgTreeOrder: orgRoot.orgRootOrder, orgTreePhoneEx: orgRoot.orgRootPhoneEx, orgTreePhoneIn: orgRoot.orgRootPhoneIn, @@ -1960,6 +1991,11 @@ export class OrganizationController extends Controller { DIVISION_CODE: orgChild1.DIVISION_CODE, SECTION_CODE: orgChild1.SECTION_CODE, JOB_CODE: orgChild1.JOB_CODE, + ROOT_CODE: orgChild1.ROOT_CODE, + CHILD1_CODE: orgChild1.CHILD1_CODE, + CHILD2_CODE: orgChild1.CHILD2_CODE, + CHILD3_CODE: orgChild1.CHILD3_CODE, + CHILD4_CODE: orgChild1.CHILD4_CODE, orgTreeOrder: orgChild1.orgChild1Order, orgRootCode: orgRoot.orgRootCode, orgTreePhoneEx: orgChild1.orgChild1PhoneEx, @@ -2004,6 +2040,11 @@ export class OrganizationController extends Controller { DIVISION_CODE: orgChild2.DIVISION_CODE, SECTION_CODE: orgChild2.SECTION_CODE, JOB_CODE: orgChild2.JOB_CODE, + ROOT_CODE: orgChild2.ROOT_CODE, + CHILD1_CODE: orgChild2.CHILD1_CODE, + CHILD2_CODE: orgChild2.CHILD2_CODE, + CHILD3_CODE: orgChild2.CHILD3_CODE, + CHILD4_CODE: orgChild2.CHILD4_CODE, orgTreeOrder: orgChild2.orgChild2Order, orgRootCode: orgRoot.orgRootCode, orgTreePhoneEx: orgChild2.orgChild2PhoneEx, @@ -2053,6 +2094,11 @@ export class OrganizationController extends Controller { DIVISION_CODE: orgChild3.DIVISION_CODE, SECTION_CODE: orgChild3.SECTION_CODE, JOB_CODE: orgChild3.JOB_CODE, + ROOT_CODE: orgChild3.ROOT_CODE, + CHILD1_CODE: orgChild3.CHILD1_CODE, + CHILD2_CODE: orgChild3.CHILD2_CODE, + CHILD3_CODE: orgChild3.CHILD3_CODE, + CHILD4_CODE: orgChild3.CHILD4_CODE, orgTreeOrder: orgChild3.orgChild3Order, orgRootCode: orgRoot.orgRootCode, orgTreePhoneEx: orgChild3.orgChild3PhoneEx, @@ -2109,6 +2155,11 @@ export class OrganizationController extends Controller { DIVISION_CODE: orgChild4.DIVISION_CODE, SECTION_CODE: orgChild4.SECTION_CODE, JOB_CODE: orgChild4.JOB_CODE, + ROOT_CODE: orgChild4.ROOT_CODE, + CHILD1_CODE: orgChild4.CHILD1_CODE, + CHILD2_CODE: orgChild4.CHILD2_CODE, + CHILD3_CODE: orgChild4.CHILD3_CODE, + CHILD4_CODE: orgChild4.CHILD4_CODE, orgTreeOrder: orgChild4.orgChild4Order, orgRootCode: orgRoot.orgRootCode, orgTreePhoneEx: orgChild4.orgChild4PhoneEx, @@ -2529,6 +2580,7 @@ export class OrganizationController extends Controller { await sendToQueueOrg(msg); return new HttpSuccess(); } catch (error: any) { + console.error("Error publishing draft organization:", error); throw error; } } @@ -3477,18 +3529,18 @@ export class OrganizationController extends Controller { const formattedData_ = rootId === "root" ? [ - { - personID: "", - name: "", - avatar: "", - positionName: "", - positionNum: "", - positionNumInt: null, - departmentName: data.orgRevisionName, - organizationId: data.id, - children: formattedData, - }, - ] + { + personID: "", + name: "", + avatar: "", + positionName: "", + positionNum: "", + positionNumInt: null, + departmentName: data.orgRevisionName, + organizationId: data.id, + children: formattedData, + }, + ] : formattedData; return new HttpSuccess(formattedData_); } @@ -5807,165 +5859,170 @@ export class OrganizationController extends Controller { .leftJoin("orgRoot.posMasters", "posMasters") .leftJoin("posMasters.current_holder", "current_holder") .orderBy("orgRoot.orgRootOrder", "ASC") + .addOrderBy("posMasters.posMasterOrder", "ASC") .getMany(); const orgRootIds = orgRootData.map((orgRoot) => orgRoot.id) || null; const orgChild1Data = orgRootIds && orgRootIds.length > 0 ? await AppDataSource.getRepository(OrgChild1) - .createQueryBuilder("orgChild1") - .select([ - "orgChild1.id", - "orgChild1.orgRootId", - "orgChild1.orgChild1Name", - "orgChild1.orgChild1ShortName", - "orgChild1.orgChild1Code", - "orgChild1.orgChild1Order", - ]) - .addSelect([ - "posMasters.id", - "posMasters.posMasterNo", - "posMasters.orgChild2Id", - "posMasters.isDirector", - ]) - .addSelect([ - "current_holder.prefix", - "current_holder.firstName", - "current_holder.lastName", - ]) - .where("orgChild1.orgRootId IN (:...ids)", { ids: orgRootIds }) - .andWhere( - _data.child1 != undefined && _data.child1 != null - ? _data.child1[0] != null - ? `orgChild1.id IN (:...node)` - : `orgChild1.id is null` - : "1=1", - { - node: _data.child1, - }, - ) - .leftJoin("orgChild1.posMasters", "posMasters") - .leftJoin("posMasters.current_holder", "current_holder") - .orderBy("orgChild1.orgChild1Order", "ASC") - .getMany() + .createQueryBuilder("orgChild1") + .select([ + "orgChild1.id", + "orgChild1.orgRootId", + "orgChild1.orgChild1Name", + "orgChild1.orgChild1ShortName", + "orgChild1.orgChild1Code", + "orgChild1.orgChild1Order", + ]) + .addSelect([ + "posMasters.id", + "posMasters.posMasterNo", + "posMasters.orgChild2Id", + "posMasters.isDirector", + ]) + .addSelect([ + "current_holder.prefix", + "current_holder.firstName", + "current_holder.lastName", + ]) + .where("orgChild1.orgRootId IN (:...ids)", { ids: orgRootIds }) + .andWhere( + _data.child1 != undefined && _data.child1 != null + ? _data.child1[0] != null + ? `orgChild1.id IN (:...node)` + : `orgChild1.id is null` + : "1=1", + { + node: _data.child1, + }, + ) + .leftJoin("orgChild1.posMasters", "posMasters") + .leftJoin("posMasters.current_holder", "current_holder") + .orderBy("orgChild1.orgChild1Order", "ASC") + .addOrderBy("posMasters.posMasterOrder", "ASC") + .getMany() : []; const orgChild1Ids = orgChild1Data.map((orgChild1) => orgChild1.id) || null; const orgChild2Data = orgChild1Ids && orgChild1Ids.length > 0 ? await AppDataSource.getRepository(OrgChild2) - .createQueryBuilder("orgChild2") - .select([ - "orgChild2.id", - "orgChild2.orgChild1Id", - "orgChild2.orgChild2Name", - "orgChild2.orgChild2ShortName", - "orgChild2.orgChild2Code", - "orgChild2.orgChild2Order", - ]) - .addSelect([ - "posMasters.id", - "posMasters.posMasterNo", - "posMasters.orgChild3Id", - "posMasters.isDirector", - ]) - .addSelect([ - "current_holder.prefix", - "current_holder.firstName", - "current_holder.lastName", - ]) - .where("orgChild2.orgChild1Id IN (:...ids)", { ids: orgChild1Ids }) - .andWhere( - _data.child2 != undefined && _data.child2 != null - ? _data.child2[0] != null - ? `orgChild2.id IN (:...node)` - : `orgChild2.id is null` - : "1=1", - { - node: _data.child2, - }, - ) - .leftJoin("orgChild2.posMasters", "posMasters") - .leftJoin("posMasters.current_holder", "current_holder") - .orderBy("orgChild2.orgChild2Order", "ASC") - .getMany() + .createQueryBuilder("orgChild2") + .select([ + "orgChild2.id", + "orgChild2.orgChild1Id", + "orgChild2.orgChild2Name", + "orgChild2.orgChild2ShortName", + "orgChild2.orgChild2Code", + "orgChild2.orgChild2Order", + ]) + .addSelect([ + "posMasters.id", + "posMasters.posMasterNo", + "posMasters.orgChild3Id", + "posMasters.isDirector", + ]) + .addSelect([ + "current_holder.prefix", + "current_holder.firstName", + "current_holder.lastName", + ]) + .where("orgChild2.orgChild1Id IN (:...ids)", { ids: orgChild1Ids }) + .andWhere( + _data.child2 != undefined && _data.child2 != null + ? _data.child2[0] != null + ? `orgChild2.id IN (:...node)` + : `orgChild2.id is null` + : "1=1", + { + node: _data.child2, + }, + ) + .leftJoin("orgChild2.posMasters", "posMasters") + .leftJoin("posMasters.current_holder", "current_holder") + .orderBy("orgChild2.orgChild2Order", "ASC") + .addOrderBy("posMasters.posMasterOrder", "ASC") + .getMany() : []; const orgChild2Ids = orgChild2Data.map((orgChild2) => orgChild2.id) || null; const orgChild3Data = orgChild2Ids && orgChild2Ids.length > 0 ? await AppDataSource.getRepository(OrgChild3) - .createQueryBuilder("orgChild3") - .select([ - "orgChild3.id", - "orgChild3.orgChild2Id", - "orgChild3.orgChild3Name", - "orgChild3.orgChild3ShortName", - "orgChild3.orgChild3Code", - "orgChild3.orgChild3Order", - ]) - .addSelect([ - "posMasters.id", - "posMasters.posMasterNo", - "posMasters.orgChild4Id", - "posMasters.isDirector", - ]) - .addSelect([ - "current_holder.prefix", - "current_holder.firstName", - "current_holder.lastName", - ]) - .where("orgChild3.orgChild2Id IN (:...ids)", { ids: orgChild2Ids }) - .andWhere( - _data.child3 != undefined && _data.child3 != null - ? _data.child3[0] != null - ? `orgChild3.id IN (:...node)` - : `orgChild3.id is null` - : "1=1", - { - node: _data.child3, - }, - ) - .leftJoin("orgChild3.posMasters", "posMasters") - .leftJoin("posMasters.current_holder", "current_holder") - .orderBy("orgChild3.orgChild3Order", "ASC") - .getMany() + .createQueryBuilder("orgChild3") + .select([ + "orgChild3.id", + "orgChild3.orgChild2Id", + "orgChild3.orgChild3Name", + "orgChild3.orgChild3ShortName", + "orgChild3.orgChild3Code", + "orgChild3.orgChild3Order", + ]) + .addSelect([ + "posMasters.id", + "posMasters.posMasterNo", + "posMasters.orgChild4Id", + "posMasters.isDirector", + ]) + .addSelect([ + "current_holder.prefix", + "current_holder.firstName", + "current_holder.lastName", + ]) + .where("orgChild3.orgChild2Id IN (:...ids)", { ids: orgChild2Ids }) + .andWhere( + _data.child3 != undefined && _data.child3 != null + ? _data.child3[0] != null + ? `orgChild3.id IN (:...node)` + : `orgChild3.id is null` + : "1=1", + { + node: _data.child3, + }, + ) + .leftJoin("orgChild3.posMasters", "posMasters") + .leftJoin("posMasters.current_holder", "current_holder") + .orderBy("orgChild3.orgChild3Order", "ASC") + .addOrderBy("posMasters.posMasterOrder", "ASC") + .getMany() : []; const orgChild3Ids = orgChild3Data.map((orgChild3) => orgChild3.id) || null; const orgChild4Data = orgChild3Ids && orgChild3Ids.length > 0 ? await AppDataSource.getRepository(OrgChild4) - .createQueryBuilder("orgChild4") - .select([ - "orgChild4.id", - "orgChild4.orgChild3Id", - "orgChild4.orgChild4Name", - "orgChild4.orgChild4ShortName", - "orgChild4.orgChild4Code", - "orgChild4.orgChild4Order", - ]) - .addSelect(["posMasters.id", "posMasters.posMasterNo", "posMasters.isDirector"]) - .addSelect([ - "current_holder.prefix", - "current_holder.firstName", - "current_holder.lastName", - ]) - .where("orgChild4.orgChild3Id IN (:...ids)", { ids: orgChild3Ids }) - .andWhere( - _data.child4 != undefined && _data.child4 != null - ? _data.child4[0] != null - ? `orgChild4.id IN (:...node)` - : `orgChild4.id is null` - : "1=1", - { - node: _data.child4, - }, - ) - .leftJoin("orgChild4.posMasters", "posMasters") - .leftJoin("posMasters.current_holder", "current_holder") - .orderBy("orgChild4.orgChild4Order", "ASC") - .getMany() + .createQueryBuilder("orgChild4") + .select([ + "orgChild4.id", + "orgChild4.orgChild3Id", + "orgChild4.orgChild4Name", + "orgChild4.orgChild4ShortName", + "orgChild4.orgChild4Code", + "orgChild4.orgChild4Order", + ]) + .addSelect(["posMasters.id", "posMasters.posMasterNo", "posMasters.isDirector"]) + .addSelect([ + "current_holder.prefix", + "current_holder.firstName", + "current_holder.lastName", + ]) + .where("orgChild4.orgChild3Id IN (:...ids)", { ids: orgChild3Ids }) + .andWhere( + _data.child4 != undefined && _data.child4 != null + ? _data.child4[0] != null + ? `orgChild4.id IN (:...node)` + : `orgChild4.id is null` + : "1=1", + { + node: _data.child4, + }, + ) + .leftJoin("orgChild4.posMasters", "posMasters") + .leftJoin("posMasters.current_holder", "current_holder") + .orderBy("orgChild4.orgChild4Order", "ASC") + .addOrderBy("posMasters.posMasterOrder", "ASC") + .getMany() : []; const cannotViewRootPosMaster = @@ -6017,8 +6074,8 @@ export class OrganizationController extends Controller { posMaster: cannotViewRootPosMaster ? [] : filterPosMasters(orgRoot.posMasters, "orgChild1Id").map((x) => - formatPosMaster(x, orgRoot.orgRootShortName, orgRoot.id, 0), - ), + formatPosMaster(x, orgRoot.orgRootShortName, orgRoot.id, 0), + ), children: orgChild1Data .filter((orgChild1) => orgChild1.orgRootId === orgRoot.id) .map((orgChild1) => ({ @@ -6042,8 +6099,8 @@ export class OrganizationController extends Controller { posMaster: cannotViewChild1PosMaster ? [] : filterPosMasters(orgChild1.posMasters, "orgChild2Id").map((x) => - formatPosMaster(x, orgChild1.orgChild1ShortName, orgChild1.id, 1), - ), + formatPosMaster(x, orgChild1.orgChild1ShortName, orgChild1.id, 1), + ), children: orgChild2Data .filter((orgChild2) => orgChild2.orgChild1Id === orgChild1.id) .map((orgChild2) => ({ @@ -6070,8 +6127,8 @@ export class OrganizationController extends Controller { posMaster: cannotViewChild2PosMaster ? [] : filterPosMasters(orgChild2.posMasters, "orgChild3Id").map((x) => - formatPosMaster(x, orgChild2.orgChild2ShortName, orgChild2.id, 2), - ), + formatPosMaster(x, orgChild2.orgChild2ShortName, orgChild2.id, 2), + ), children: orgChild3Data .filter((orgChild3) => orgChild3.orgChild2Id === orgChild2.id) .map((orgChild3) => ({ @@ -6098,8 +6155,8 @@ export class OrganizationController extends Controller { posMaster: cannotViewChild3PosMaster ? [] : filterPosMasters(orgChild3.posMasters, "orgChild4Id").map((x) => - formatPosMaster(x, orgChild3.orgChild3ShortName, orgChild3.id, 3), - ), + formatPosMaster(x, orgChild3.orgChild3ShortName, orgChild3.id, 3), + ), children: orgChild4Data .filter((orgChild4) => orgChild4.orgChild3Id === orgChild3.id) .map((orgChild4) => ({ @@ -6130,10 +6187,10 @@ export class OrganizationController extends Controller { posMaster: cannotViewChild4PosMaster ? [] : orgChild4.posMasters - .filter((x) => x.isDirector === true) - .map((x) => - formatPosMaster(x, orgChild4.orgChild4ShortName, orgChild4.id, 4), - ), + .filter((x) => x.isDirector === true) + .map((x) => + formatPosMaster(x, orgChild4.orgChild4ShortName, orgChild4.id, 4), + ), })), })), })), @@ -6304,159 +6361,159 @@ export class OrganizationController extends Controller { const orgChild1Data = orgRootIds && orgRootIds.length > 0 ? await AppDataSource.getRepository(OrgChild1) - .createQueryBuilder("orgChild1") - .where("orgChild1.orgRootId IN (:...ids)", { ids: orgRootIds }) - .andWhere( - _data.child1 != undefined && _data.child1 != null - ? _data.child1[0] != null - ? `orgChild1.id IN (:...node)` - : `orgChild1.id is ${_data.privilege == "PARENT" ? "not null" : "null"}` - : "1=1", - { - node: _data.child1, - }, - ) - .select([ - "orgChild1.id", - "orgChild1.ancestorDNA", - "orgChild1.orgChild1Name", - "orgChild1.orgChild1ShortName", - "orgChild1.orgChild1Code", - "orgChild1.orgChild1Order", - "orgChild1.orgChild1PhoneEx", - "orgChild1.orgChild1PhoneIn", - "orgChild1.orgChild1Fax", - "orgChild1.orgRootId", - "orgChild1.orgChild1Rank", - "orgChild1.orgChild1RankSub", - "orgChild1.DEPARTMENT_CODE", - "orgChild1.DIVISION_CODE", - "orgChild1.SECTION_CODE", - "orgChild1.JOB_CODE", - "orgChild1.responsibility", - ]) - .orderBy("orgChild1.orgChild1Order", "ASC") - .getMany() + .createQueryBuilder("orgChild1") + .where("orgChild1.orgRootId IN (:...ids)", { ids: orgRootIds }) + .andWhere( + _data.child1 != undefined && _data.child1 != null + ? _data.child1[0] != null + ? `orgChild1.id IN (:...node)` + : `orgChild1.id is ${_data.privilege == "PARENT" ? "not null" : "null"}` + : "1=1", + { + node: _data.child1, + }, + ) + .select([ + "orgChild1.id", + "orgChild1.ancestorDNA", + "orgChild1.orgChild1Name", + "orgChild1.orgChild1ShortName", + "orgChild1.orgChild1Code", + "orgChild1.orgChild1Order", + "orgChild1.orgChild1PhoneEx", + "orgChild1.orgChild1PhoneIn", + "orgChild1.orgChild1Fax", + "orgChild1.orgRootId", + "orgChild1.orgChild1Rank", + "orgChild1.orgChild1RankSub", + "orgChild1.DEPARTMENT_CODE", + "orgChild1.DIVISION_CODE", + "orgChild1.SECTION_CODE", + "orgChild1.JOB_CODE", + "orgChild1.responsibility", + ]) + .orderBy("orgChild1.orgChild1Order", "ASC") + .getMany() : []; const orgChild1Ids = orgChild1Data.map((orgChild1) => orgChild1.id) || null; const orgChild2Data = orgChild1Ids && orgChild1Ids.length > 0 ? await AppDataSource.getRepository(OrgChild2) - .createQueryBuilder("orgChild2") - .where("orgChild2.orgChild1Id IN (:...ids)", { ids: orgChild1Ids }) - .andWhere( - _data.child2 != undefined && _data.child2 != null - ? _data.child2[0] != null - ? `orgChild2.id IN (:...node)` - : `orgChild2.id is null` - : "1=1", - { - node: _data.child2, - }, - ) - .select([ - "orgChild2.id", - "orgChild2.ancestorDNA", - "orgChild2.orgChild2Name", - "orgChild2.orgChild2ShortName", - "orgChild2.orgChild2Code", - "orgChild2.orgChild2Order", - "orgChild2.orgChild2PhoneEx", - "orgChild2.orgChild2PhoneIn", - "orgChild2.orgChild2Fax", - "orgChild2.orgRootId", - "orgChild2.orgChild2Rank", - "orgChild2.orgChild2RankSub", - "orgChild2.DEPARTMENT_CODE", - "orgChild2.DIVISION_CODE", - "orgChild2.SECTION_CODE", - "orgChild2.JOB_CODE", - "orgChild2.orgChild1Id", - "orgChild2.responsibility", - ]) - .orderBy("orgChild2.orgChild2Order", "ASC") - .getMany() + .createQueryBuilder("orgChild2") + .where("orgChild2.orgChild1Id IN (:...ids)", { ids: orgChild1Ids }) + .andWhere( + _data.child2 != undefined && _data.child2 != null + ? _data.child2[0] != null + ? `orgChild2.id IN (:...node)` + : `orgChild2.id is null` + : "1=1", + { + node: _data.child2, + }, + ) + .select([ + "orgChild2.id", + "orgChild2.ancestorDNA", + "orgChild2.orgChild2Name", + "orgChild2.orgChild2ShortName", + "orgChild2.orgChild2Code", + "orgChild2.orgChild2Order", + "orgChild2.orgChild2PhoneEx", + "orgChild2.orgChild2PhoneIn", + "orgChild2.orgChild2Fax", + "orgChild2.orgRootId", + "orgChild2.orgChild2Rank", + "orgChild2.orgChild2RankSub", + "orgChild2.DEPARTMENT_CODE", + "orgChild2.DIVISION_CODE", + "orgChild2.SECTION_CODE", + "orgChild2.JOB_CODE", + "orgChild2.orgChild1Id", + "orgChild2.responsibility", + ]) + .orderBy("orgChild2.orgChild2Order", "ASC") + .getMany() : []; const orgChild2Ids = orgChild2Data.map((orgChild2) => orgChild2.id) || null; const orgChild3Data = orgChild2Ids && orgChild2Ids.length > 0 ? await AppDataSource.getRepository(OrgChild3) - .createQueryBuilder("orgChild3") - .where("orgChild3.orgChild2Id IN (:...ids)", { ids: orgChild2Ids }) - .andWhere( - _data.child3 != undefined && _data.child3 != null - ? _data.child3[0] != null - ? `orgChild3.id IN (:...node)` - : `orgChild3.id is null` - : "1=1", - { - node: _data.child3, - }, - ) - .select([ - "orgChild3.id", - "orgChild3.ancestorDNA", - "orgChild3.orgChild3Name", - "orgChild3.orgChild3ShortName", - "orgChild3.orgChild3Code", - "orgChild3.orgChild3Order", - "orgChild3.orgChild3PhoneEx", - "orgChild3.orgChild3PhoneIn", - "orgChild3.orgChild3Fax", - "orgChild3.orgRootId", - "orgChild3.orgChild3Rank", - "orgChild3.orgChild3RankSub", - "orgChild3.DEPARTMENT_CODE", - "orgChild3.DIVISION_CODE", - "orgChild3.SECTION_CODE", - "orgChild3.JOB_CODE", - "orgChild3.orgChild2Id", - "orgChild3.responsibility", - ]) - .orderBy("orgChild3.orgChild3Order", "ASC") - .getMany() + .createQueryBuilder("orgChild3") + .where("orgChild3.orgChild2Id IN (:...ids)", { ids: orgChild2Ids }) + .andWhere( + _data.child3 != undefined && _data.child3 != null + ? _data.child3[0] != null + ? `orgChild3.id IN (:...node)` + : `orgChild3.id is null` + : "1=1", + { + node: _data.child3, + }, + ) + .select([ + "orgChild3.id", + "orgChild3.ancestorDNA", + "orgChild3.orgChild3Name", + "orgChild3.orgChild3ShortName", + "orgChild3.orgChild3Code", + "orgChild3.orgChild3Order", + "orgChild3.orgChild3PhoneEx", + "orgChild3.orgChild3PhoneIn", + "orgChild3.orgChild3Fax", + "orgChild3.orgRootId", + "orgChild3.orgChild3Rank", + "orgChild3.orgChild3RankSub", + "orgChild3.DEPARTMENT_CODE", + "orgChild3.DIVISION_CODE", + "orgChild3.SECTION_CODE", + "orgChild3.JOB_CODE", + "orgChild3.orgChild2Id", + "orgChild3.responsibility", + ]) + .orderBy("orgChild3.orgChild3Order", "ASC") + .getMany() : []; const orgChild3Ids = orgChild3Data.map((orgChild3) => orgChild3.id) || null; const orgChild4Data = orgChild3Ids && orgChild3Ids.length > 0 ? await AppDataSource.getRepository(OrgChild4) - .createQueryBuilder("orgChild4") - .where("orgChild4.orgChild3Id IN (:...ids)", { ids: orgChild3Ids }) - .andWhere( - _data.child4 != undefined && _data.child4 != null - ? _data.child4[0] != null - ? `orgChild4.id IN (:...node)` - : `orgChild4.id is null` - : "1=1", - { - node: _data.child4, - }, - ) - .select([ - "orgChild4.id", - "orgChild4.ancestorDNA", - "orgChild4.orgChild4Name", - "orgChild4.orgChild4ShortName", - "orgChild4.orgChild4Code", - "orgChild4.orgChild4Order", - "orgChild4.orgChild4PhoneEx", - "orgChild4.orgChild4PhoneIn", - "orgChild4.orgChild4Fax", - "orgChild4.orgRootId", - "orgChild4.orgChild4Rank", - "orgChild4.orgChild4RankSub", - "orgChild4.DEPARTMENT_CODE", - "orgChild4.DIVISION_CODE", - "orgChild4.SECTION_CODE", - "orgChild4.JOB_CODE", - "orgChild4.orgChild3Id", - "orgChild4.responsibility", - ]) - .orderBy("orgChild4.orgChild4Order", "ASC") - .getMany() + .createQueryBuilder("orgChild4") + .where("orgChild4.orgChild3Id IN (:...ids)", { ids: orgChild3Ids }) + .andWhere( + _data.child4 != undefined && _data.child4 != null + ? _data.child4[0] != null + ? `orgChild4.id IN (:...node)` + : `orgChild4.id is null` + : "1=1", + { + node: _data.child4, + }, + ) + .select([ + "orgChild4.id", + "orgChild4.ancestorDNA", + "orgChild4.orgChild4Name", + "orgChild4.orgChild4ShortName", + "orgChild4.orgChild4Code", + "orgChild4.orgChild4Order", + "orgChild4.orgChild4PhoneEx", + "orgChild4.orgChild4PhoneIn", + "orgChild4.orgChild4Fax", + "orgChild4.orgRootId", + "orgChild4.orgChild4Rank", + "orgChild4.orgChild4RankSub", + "orgChild4.DEPARTMENT_CODE", + "orgChild4.DIVISION_CODE", + "orgChild4.SECTION_CODE", + "orgChild4.JOB_CODE", + "orgChild4.orgChild3Id", + "orgChild4.responsibility", + ]) + .orderBy("orgChild4.orgChild4Order", "ASC") + .getMany() : []; // const formattedData = orgRootData.map((orgRoot) => { @@ -8343,13 +8400,13 @@ export class OrganizationController extends Controller { profileId: null, shortName: pos ? [ - pos.orgChild4?.orgChild4ShortName, - pos.orgChild3?.orgChild3ShortName, - pos.orgChild2?.orgChild2ShortName, - pos.orgChild1?.orgChild1ShortName, - pos.orgRoot?.orgRootShortName, - ].find((s: string | undefined) => typeof s === "string" && s.trim().length > 0) ?? - null + pos.orgChild4?.orgChild4ShortName, + pos.orgChild3?.orgChild3ShortName, + pos.orgChild2?.orgChild2ShortName, + pos.orgChild1?.orgChild1ShortName, + pos.orgRoot?.orgRootShortName, + ].find((s: string | undefined) => typeof s === "string" && s.trim().length > 0) ?? + null : null, posMasterNoPrefix: pos.posMasterNoPrefix ?? null, posMasterNo: pos.posMasterNo != null ? String(pos.posMasterNo) : null, @@ -8418,13 +8475,13 @@ export class OrganizationController extends Controller { profileId: null, shortName: pos ? [ - pos.orgChild4?.orgChild4ShortName, - pos.orgChild3?.orgChild3ShortName, - pos.orgChild2?.orgChild2ShortName, - pos.orgChild1?.orgChild1ShortName, - pos.orgRoot?.orgRootShortName, - ].find((s: string | undefined) => typeof s === "string" && s.trim().length > 0) ?? - null + pos.orgChild4?.orgChild4ShortName, + pos.orgChild3?.orgChild3ShortName, + pos.orgChild2?.orgChild2ShortName, + pos.orgChild1?.orgChild1ShortName, + pos.orgRoot?.orgRootShortName, + ].find((s: string | undefined) => typeof s === "string" && s.trim().length > 0) ?? + null : null, posMasterNoPrefix: pos.posMasterNoPrefix ?? null, posMasterNo: pos.posMasterNo != null ? String(pos.posMasterNo) : null, @@ -8492,13 +8549,13 @@ export class OrganizationController extends Controller { profileId: null, shortName: pos ? [ - pos.orgChild4?.orgChild4ShortName, - pos.orgChild3?.orgChild3ShortName, - pos.orgChild2?.orgChild2ShortName, - pos.orgChild1?.orgChild1ShortName, - pos.orgRoot?.orgRootShortName, - ].find((s: string | undefined) => typeof s === "string" && s.trim().length > 0) ?? - null + pos.orgChild4?.orgChild4ShortName, + pos.orgChild3?.orgChild3ShortName, + pos.orgChild2?.orgChild2ShortName, + pos.orgChild1?.orgChild1ShortName, + pos.orgRoot?.orgRootShortName, + ].find((s: string | undefined) => typeof s === "string" && s.trim().length > 0) ?? + null : null, posMasterNoPrefix: pos.posMasterNoPrefix ?? null, posMasterNo: pos.posMasterNo != null ? String(pos.posMasterNo) : null, @@ -8555,8 +8612,9 @@ export class OrganizationController extends Controller { orgChild2Id, orgChild3Id, orgChild4Id, + authRoleId: draftPos.authRoleId, current_holderId: draftPos.next_holderId, - next_holderId: draftPos.next_holderId, + next_holderId: null, isSit: draftPos.isSit, reason: draftPos.reason, isDirector: draftPos.isDirector, @@ -8585,6 +8643,7 @@ export class OrganizationController extends Controller { orgChild3Id, orgChild4Id, current_holderId: draftPos.next_holderId, + next_holderId: null, statusReport: "DONE", }); @@ -8643,14 +8702,14 @@ export class OrganizationController extends Controller { profileId: null, shortName: pmWithRelations ? [ - pmWithRelations.orgChild4?.orgChild4ShortName, - pmWithRelations.orgChild3?.orgChild3ShortName, - pmWithRelations.orgChild2?.orgChild2ShortName, - pmWithRelations.orgChild1?.orgChild1ShortName, - pmWithRelations.orgRoot?.orgRootShortName, - ].find( - (s: string | undefined) => typeof s === "string" && s.trim().length > 0, - ) ?? null + pmWithRelations.orgChild4?.orgChild4ShortName, + pmWithRelations.orgChild3?.orgChild3ShortName, + pmWithRelations.orgChild2?.orgChild2ShortName, + pmWithRelations.orgChild1?.orgChild1ShortName, + pmWithRelations.orgRoot?.orgRootShortName, + ].find( + (s: string | undefined) => typeof s === "string" && s.trim().length > 0, + ) ?? null : null, posMasterNoPrefix: draftPos.posMasterNoPrefix ?? null, posMasterNo: draftPos.posMasterNo != null ? String(draftPos.posMasterNo) : null, @@ -8694,6 +8753,45 @@ export class OrganizationController extends Controller { }; await queryRunner.commitTransaction(); + + // Clear Redis cache after successful publish (only menu and role for menu display) + const redis = require("redis"); + const { promisify } = require("util"); + const redisClient = redis.createClient({ + host: process.env.REDIS_HOST || "localhost", + port: parseInt(process.env.REDIS_PORT || "6379"), + }); + const keysAsync = promisify(redisClient.keys).bind(redisClient); + const delAsync = promisify(redisClient.del).bind(redisClient); + + try { + // Clear only menu and role cache (affects menu display) + const menuRolePatterns = ["menu_*", "role_*"]; + let totalCleared = 0; + + for (const pattern of menuRolePatterns) { + const keys = await keysAsync(pattern); + if (keys.length > 0) { + // Delete in chunks of 1000 to avoid argument limit + const chunkSize = 1000; + for (let i = 0; i < keys.length; i += chunkSize) { + const chunk = keys.slice(i, i + chunkSize); + await delAsync(...chunk); + } + totalCleared += keys.length; + console.log(`[moveDraftToCurrent] Cleared ${keys.length} cache keys for pattern: ${pattern}`); + } + } + console.log(`[moveDraftToCurrent] Total cache cleared: ${totalCleared} keys`); + } catch (err) { + console.error("[moveDraftToCurrent] Error clearing cache:", err); + } finally { + redisClient.quit(); + } + + // Invalidate memory cache + orgStructureCache.invalidate(currentRevisionId); + return new HttpSuccess(summary); } catch (error) { console.error("Error moving draft to current:", error); @@ -8977,7 +9075,16 @@ export class OrganizationController extends Controller { // Fetch draft PosMasters with relations for history tracking const draftPosMasters = await queryRunner.manager.find(PosMaster, { where: { id: In(draftPosMasterIds) }, - relations: ["orgRoot", "orgChild1", "orgChild2", "orgChild3", "orgChild4", "next_holder"], + relations: [ + "orgRoot", + "orgChild1", + "orgChild2", + "orgChild3", + "orgChild4", + "next_holder", + "next_holder.posType", + "next_holder.posLevel", + ], }); // Fetch ALL positions for ALL posMasters in just 2 queries @@ -9115,17 +9222,22 @@ export class OrganizationController extends Controller { org: draftPosMaster ? getOrgFullName(draftPosMaster as PosMaster) ?? _null : _null, }); } - // ถ้าไม่ใช่ตำแหน่งนั่งทับ (isSit = false) ถึงจะอัพเดทตำแหน่งในทะเบียนประวัติ - if (nextHolderId != null && draftPos.positionIsSelected && !draftPosMaster?.isSit) { - const existing = profileUpdates.get(nextHolderId) || {}; - existing.position = draftPos.positionName; - existing.posTypeId = draftPos.posTypeId; - 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 (nextHolderId != null && draftPos.positionIsSelected) { + // ถ้าไม่ใช่ตำแหน่งนั่งทับ (isSit = false) ถึงจะอัพเดทตำแหน่งในทะเบียนประวัติ + if (!draftPosMaster?.isSit) { + const existing = profileUpdates.get(nextHolderId) || {}; + existing.position = draftPos.positionName; + existing.posTypeId = draftPos.posTypeId; + 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) { // Find the selected position from draft positions const selectedPos = @@ -9137,10 +9249,11 @@ export class OrganizationController extends Controller { prefix: draftPosMaster.next_holder?.prefix ?? null, firstName: draftPosMaster.next_holder?.firstName ?? null, lastName: draftPosMaster.next_holder?.lastName ?? null, - position: selectedPos.positionName ?? null, - posType: (selectedPos as any).posType?.posTypeName ?? null, - posLevel: (selectedPos as any).posLevel?.posLevelName ?? null, - posExecutive: (selectedPos as any).posExecutive?.posExecutiveName ?? null, + // isSit = true ดึงค่าจาก profile + position: draftPosMaster?.isSit ? draftPosMaster.next_holder?.position ?? null : selectedPos.positionName ?? null, + posType: draftPosMaster?.isSit ? draftPosMaster.next_holder?.posType?.posTypeName ?? null : (selectedPos as any).posType?.posTypeName ?? null, + posLevel: draftPosMaster?.isSit ? draftPosMaster.next_holder?.posLevel?.posLevelName ?? null : (selectedPos as any).posLevel?.posLevelName ?? null, + posExecutive: draftPosMaster?.isSit ? draftPosMaster.next_holder?.posExecutive ?? null : (selectedPos as any).posExecutive?.posExecutiveName ?? null, profileId: nextHolderId, rootDnaId: draftPosMaster.orgRoot?.ancestorDNA ?? null, child1DnaId: draftPosMaster.orgChild1?.ancestorDNA ?? null, @@ -9192,12 +9305,12 @@ export class OrganizationController extends Controller { child4DnaId: posMaster?.orgChild4?.ancestorDNA ?? null, shortName: posMaster ? [ - posMaster.orgChild4?.orgChild4ShortName, - posMaster.orgChild3?.orgChild3ShortName, - posMaster.orgChild2?.orgChild2ShortName, - posMaster.orgChild1?.orgChild1ShortName, - posMaster.orgRoot?.orgRootShortName, - ].find((s) => typeof s === "string" && s.trim().length > 0) ?? null + posMaster.orgChild4?.orgChild4ShortName, + posMaster.orgChild3?.orgChild3ShortName, + posMaster.orgChild2?.orgChild2ShortName, + posMaster.orgChild1?.orgChild1ShortName, + posMaster.orgRoot?.orgRootShortName, + ].find((s) => typeof s === "string" && s.trim().length > 0) ?? null : null, posMasterNoPrefix: posMaster?.posMasterNoPrefix ?? null, posMasterNo: posMaster?.posMasterNo != null ? String(posMaster.posMasterNo) : null, diff --git a/src/controllers/OrganizationDotnetController.ts b/src/controllers/OrganizationDotnetController.ts index e61bc4d8..8e803569 100644 --- a/src/controllers/OrganizationDotnetController.ts +++ b/src/controllers/OrganizationDotnetController.ts @@ -26,6 +26,7 @@ import { OrgRoot } from "../entities/OrgRoot"; import { Position } from "../entities/Position"; import { PosMaster } from "../entities/PosMaster"; import { PosMasterHistory } from "../entities/PosMasterHistory"; +import { PosMasterEmployeeHistory } from "../entities/PosMasterEmployeeHistory"; import { Profile } from "../entities/Profile"; import { ProfileEducation } from "../entities/ProfileEducation"; import { ProfileEmployee } from "../entities/ProfileEmployee"; @@ -39,7 +40,7 @@ import { calculateRetireLaw } from "../interfaces/utils"; import { RequestWithUser } from "../middlewares/user"; @Route("api/v1/org/dotnet") @Tags("Dotnet") -@Security("bearerAuth") +// @Security("bearerAuth") @Response( HttpStatus.INTERNAL_SERVER_ERROR, "เกิดข้อผิดพลาด ไม่สามารถแสดงรายการได้ กรุณาลองใหม่ในภายหลัง", @@ -57,6 +58,7 @@ export class OrganizationDotnetController extends Controller { private positionRepository = AppDataSource.getRepository(Position); private posMasterRepository = AppDataSource.getRepository(PosMaster); private posMasterHistoryRepository = AppDataSource.getRepository(PosMasterHistory); + private posMasterEmployeeHistoryRepository = AppDataSource.getRepository(PosMasterEmployeeHistory); private empPosMasterRepository = AppDataSource.getRepository(EmployeePosMaster); private insigniaRepo = AppDataSource.getRepository(ProfileInsignia); private employeePosDictRepository = AppDataSource.getRepository(EmployeePosDict); @@ -71,6 +73,7 @@ export class OrganizationDotnetController extends Controller { * */ @Post("check-citizen") + @Security("internalAuth") public async CheckCitizen( @Body() body: { @@ -88,6 +91,7 @@ export class OrganizationDotnetController extends Controller { * */ @Post("search") + @Security("internalAuth") public async SearchProfile( @Body() body: { @@ -302,6 +306,7 @@ export class OrganizationDotnetController extends Controller { * */ @Post("search-employee") + @Security("internalAuth") public async SearchProfileEmployee( @Body() body: { @@ -486,6 +491,7 @@ export class OrganizationDotnetController extends Controller { * @param {string} id Id หน่วยงาน */ @Get("org/{id}") + @Security("internalAuth") async GetOrganizationById(@Path() id: string) { const orgRoot = await this.orgRootRepo.findOne({ where: { id: id }, @@ -495,6 +501,7 @@ export class OrganizationDotnetController extends Controller { } @Get("agency/{id}") + @Security("internalAuth") async GetOrgAgencyById(@Path() id: string) { const orgRoot = await this.orgRootRepo.findOne({ where: { id: id }, @@ -504,6 +511,7 @@ export class OrganizationDotnetController extends Controller { } @Get("go-agency/{id}") + @Security("internalAuth") async GetOrgGoAgencyById(@Path() id: string) { const orgRoot = await this.orgRootRepo.findOne({ where: { id: id }, @@ -519,6 +527,7 @@ export class OrganizationDotnetController extends Controller { * */ @Get("get-profileId") + @Security("bearerAuth") async getProfileInbox(@Request() request: { user: Record }) { let profile: any; //OFF @@ -554,6 +563,7 @@ export class OrganizationDotnetController extends Controller { * @param {string} keycloakId Id keycloak */ @Get("keycloak-old/{keycloakId}") + @Security("internalAuth") async GetProfileByKeycloakIdAsyncOld(@Path() keycloakId: string) { const profile = await this.profileRepo.findOne({ relations: [ @@ -1353,6 +1363,7 @@ export class OrganizationDotnetController extends Controller { } @Get("keycloak/{keycloakId}") + @Security("internalAuth") async GetProfileByKeycloakIdAsync(@Path() keycloakId: string) { /* ========================= * 1. Load profile @@ -1781,6 +1792,7 @@ export class OrganizationDotnetController extends Controller { } @Get("by-keycloak/{keycloakId}") + @Security("internalAuth") async NewGetProfileByKeycloakIdAsync(@Path() keycloakId: string) { /* ========================= * 1. Load profile @@ -2015,6 +2027,7 @@ export class OrganizationDotnetController extends Controller { // เพิ่มที่อยู่ปัจจุบัน + ตำแหน่งหัวหน้า @Get("by-keycloak2/{keycloakId}") + @Security("internalAuth") async NewGetProfileByKeycloak2IdAsync(@Path() keycloakId: string) { /* ========================= * 1. Load profile @@ -2350,6 +2363,151 @@ export class OrganizationDotnetController extends Controller { return new HttpSuccess(mapProfile); } + /** + * API Get Profile For Process Check In + * @summary API Get Profile For Process Check In + * @param {string} keycloakId keycloakId profile + */ + @Get("check-keycloak/{keycloakId}") + @Security("internalAuth") + async GetProfileForProcessCheckInAsync(@Path() keycloakId: string) { + try { + // console.log(`[check-keycloak] START - keycloakId=${keycloakId}`); + + /* ========================= + * 1. Load profile (Officer) + * ========================= */ + const profile = await this.profileRepo.findOne({ + where: { keycloak: keycloakId }, + relations: { + current_holders: { + orgRevision: true, + orgRoot: true, + orgChild1: true, + orgChild2: true, + orgChild3: true, + orgChild4: true, + }, + }, + }); + + // Employee + if (!profile) { + console.log(`[check-keycloak] OFFICER_NOT_FOUND - keycloakId=${keycloakId}, checking EMPLOYEE`); + + const empProfile = await this.profileEmpRepo.findOne({ + where: { keycloak: keycloakId }, + relations: { + current_holders: { + orgRevision: true, + orgRoot: true, + orgChild1: true, + orgChild2: true, + orgChild3: true, + orgChild4: true, + }, + }, + }); + + if (!empProfile) { + console.log(`[check-keycloak] EMPLOYEE_NOT_FOUND - keycloakId=${keycloakId}`); + throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูล"); + } + + const currentHolder = empProfile.current_holders?.find( + (x) => + x.orgRevision?.orgRevisionIsDraft === false && + x.orgRevision?.orgRevisionIsCurrent === true, + ); + + const mapProfile = { + profileType: "EMPLOYEE", + id: empProfile.id, + keycloak: empProfile.keycloak, + prefix: empProfile.prefix, + firstName: empProfile.firstName, + lastName: empProfile.lastName, + citizenId: empProfile.citizenId, + gender: empProfile.gender, + + root: currentHolder?.orgRoot?.orgRootName ?? null, + rootId: currentHolder?.orgRootId ?? null, + rootDnaId: currentHolder?.orgRoot?.ancestorDNA ?? null, + child1: currentHolder?.orgChild1?.orgChild1Name ?? null, + child1Id: currentHolder?.orgChild1Id ?? null, + child1DnaId: currentHolder?.orgChild1?.ancestorDNA ?? null, + child2: currentHolder?.orgChild2?.orgChild2Name ?? null, + child2Id: currentHolder?.orgChild2Id ?? null, + child2DnaId: currentHolder?.orgChild2?.ancestorDNA ?? null, + child3: currentHolder?.orgChild3?.orgChild3Name ?? null, + child3Id: currentHolder?.orgChild3Id ?? null, + child3DnaId: currentHolder?.orgChild3?.ancestorDNA ?? null, + child4: currentHolder?.orgChild4?.orgChild4Name ?? null, + child4Id: currentHolder?.orgChild4Id ?? null, + child4DnaId: currentHolder?.orgChild4?.ancestorDNA ?? null, + }; + + // console.log( + // `[check-keycloak] SUCCESS_EMPLOYEE - keycloakId=${keycloakId}, profileType=EMPLOYEE`, + // ); + + return new HttpSuccess(mapProfile); + } + + // console.log(`[check-keycloak] OFFICER_FOUND - keycloakId=${keycloakId}`); + + /* ========================================= + * 2. current holder (Officer) + * ========================================= */ + const currentHolder = profile.current_holders?.find( + (x) => + x.orgRevision?.orgRevisionIsDraft === false && x.orgRevision?.orgRevisionIsCurrent === true, + ); + + /* ========================================= + * 3. map response + * ========================================= */ + const mapProfile = { + profileType: "OFFICER", + id: profile.id, + keycloak: profile.keycloak, + prefix: profile.prefix, + firstName: profile.firstName, + lastName: profile.lastName, + citizenId: profile.citizenId, + gender: profile.gender, + + root: currentHolder?.orgRoot?.orgRootName ?? null, + rootId: currentHolder?.orgRootId ?? null, + rootDnaId: currentHolder?.orgRoot?.ancestorDNA ?? null, + child1: currentHolder?.orgChild1?.orgChild1Name ?? null, + child1Id: currentHolder?.orgChild1Id ?? null, + child1DnaId: currentHolder?.orgChild1?.ancestorDNA ?? null, + child2: currentHolder?.orgChild2?.orgChild2Name ?? null, + child2Id: currentHolder?.orgChild2Id ?? null, + child2DnaId: currentHolder?.orgChild2?.ancestorDNA ?? null, + child3: currentHolder?.orgChild3?.orgChild3Name ?? null, + child3Id: currentHolder?.orgChild3Id ?? null, + child3DnaId: currentHolder?.orgChild3?.ancestorDNA ?? null, + child4: currentHolder?.orgChild4?.orgChild4Name ?? null, + child4Id: currentHolder?.orgChild4Id ?? null, + child4DnaId: currentHolder?.orgChild4?.ancestorDNA ?? null, + }; + + // console.log( + // `[check-keycloak] SUCCESS_OFFICER - keycloakId=${keycloakId}, profileType=OFFICER`, + // ); + + return new HttpSuccess(mapProfile); + } catch (error: any) { + // Log เฉพาะ unexpected errors (ไม่ใช่ HttpError) + if (!(error instanceof HttpError)) { + console.error(`[check-keycloak] Unexpected error: keycloakId=${keycloakId}`, error); + } + throw error; + } + } + /** * API Get Profile For Logs * @@ -2358,6 +2516,7 @@ export class OrganizationDotnetController extends Controller { * @param {string} keycloakId keycloakId profile */ @Get("user-logs/{keycloakId}") + @Security("internalAuth") async UserLogs(@Path() keycloakId: string) { /* ========================= * 1. Load profile @@ -2437,6 +2596,7 @@ export class OrganizationDotnetController extends Controller { * @param {string} profileId Id profile */ @Get("profile/{profileId}") + @Security("internalAuth") async GetProfileByProfileIdAsync(@Path() profileId: string) { const profile = await this.profileRepo.findOne({ relations: [ @@ -3106,6 +3266,7 @@ export class OrganizationDotnetController extends Controller { * @param {string} citizenId citizen Id */ @Get("citizenId/{citizenId}") + @Security("internalAuth") async GetProfileByCitizenIdAsync(@Path() citizenId: string) { const profile = await this.profileRepo.findOne({ relations: [ @@ -3760,6 +3921,7 @@ export class OrganizationDotnetController extends Controller { * @param {string} keycloakId Id keycloak */ @Get("root/officer/{rootId}") + @Security("internalAuth") async GetProfileByRootIdAsync(@Path() rootId: string) { const profiles = await this.profileRepo.find({ relations: [ @@ -4072,6 +4234,7 @@ export class OrganizationDotnetController extends Controller { * @param {string} keycloakId Id keycloak */ @Get("root/employee/{rootId}") + @Security("internalAuth") async GetProfileByRootIdEmpAsync(@Path() rootId: string) { const profiles = await this.profileEmpRepo.find({ relations: [ @@ -4276,6 +4439,7 @@ export class OrganizationDotnetController extends Controller { * @param {string} keycloakId Id keycloak */ @Post("find/employee/position") + @Security("internalAuth") async GetProfileByPositionEmpAsync( @Body() body: { @@ -4605,6 +4769,7 @@ export class OrganizationDotnetController extends Controller { * @param {string} keycloakId Id keycloak */ @Get("user-fullname/{keycloakId}") + @Security("internalAuth") async GetUserFullName(@Path() keycloakId: string) { const profile = await this.profileRepo.findOne({ where: { keycloak: keycloakId }, @@ -4623,6 +4788,7 @@ export class OrganizationDotnetController extends Controller { * @param {string} keycloakId Id keycloak */ @Get("user-oc/{keycloakId}") + @Security("internalAuth") async getProfileByKeycloak(@Path() keycloakId: string) { const profile = await this.profileRepo.findOne({ where: { keycloak: keycloakId }, @@ -4674,6 +4840,7 @@ export class OrganizationDotnetController extends Controller { * @param {string} keycloakId Id keycloak */ @Get("user-oc-all/{keycloakId}") + @Security("internalAuth") async getAllProfileByKeycloak(@Path() keycloakId: string) { const profile = await this.profileRepo.findOne({ where: { keycloak: keycloakId }, @@ -4841,6 +5008,7 @@ export class OrganizationDotnetController extends Controller { * @param {string} ocId Id หน่วยงาน */ @Get("root-oc/{ocId}") + @Security("internalAuth") async GetRootOcId(@Path() ocId: string) { const orgRoot = await this.orgRootRepo.findOne({ where: { id: ocId }, @@ -4857,6 +5025,7 @@ export class OrganizationDotnetController extends Controller { * */ @Get("keycloak") + @Security("internalAuth") async GetProfileWithKeycloak() { const profile = await this.profileRepo.find({ where: { keycloak: Not(IsNull()) }, @@ -5066,6 +5235,7 @@ export class OrganizationDotnetController extends Controller { * */ @Get("keycloak-employee") + @Security("internalAuth") async GetProfileWithKeycloakEmployee() { const profile = await this.profileEmpRepo.find({ where: { keycloak: Not(IsNull()) || Not("") }, @@ -5194,6 +5364,7 @@ export class OrganizationDotnetController extends Controller { * */ @Post("keycloak-all-officer") + @Security("internalAuth") async PostProfileWithKeycloakAllOfficer( @Body() body: { @@ -5364,6 +5535,7 @@ export class OrganizationDotnetController extends Controller { * */ @Post("keycloak-all-officer/date") + @Security("internalAuth") async PostProfileWithKeycloakAllOfficerDate( @Body() body: { @@ -5542,6 +5714,7 @@ export class OrganizationDotnetController extends Controller { * */ @Post("none-validate-keycloak-all-officer") + @Security("internalAuth") async PostProfileWithNoneValidateKeycloakAllOfficer( @Body() body: { @@ -5711,6 +5884,7 @@ export class OrganizationDotnetController extends Controller { * */ @Post("find-node-name") + @Security("internalAuth") async findNodeName( @Body() body: { @@ -5819,6 +5993,7 @@ export class OrganizationDotnetController extends Controller { * */ @Post("officer-by-admin-role") + @Security("internalAuth") async GetOfficersByAdminRole( @Body() body: { @@ -6156,6 +6331,7 @@ export class OrganizationDotnetController extends Controller { * */ @Post("keycloak-all-employee") + @Security("internalAuth") async PostProfileWithKeycloakAllEmployee( @Body() body: { @@ -6309,6 +6485,7 @@ export class OrganizationDotnetController extends Controller { * */ @Post("none-validate-keycloak-all-employee") + @Security("internalAuth") async PostProfileWithNoneValidateKeycloakAllEmployee( @Body() body: { @@ -6462,6 +6639,7 @@ export class OrganizationDotnetController extends Controller { * */ @Post("employee-by-admin-role") + @Security("internalAuth") async GetEmployeesByAdminRole( @Body() body: { @@ -6797,229 +6975,274 @@ export class OrganizationDotnetController extends Controller { return new HttpSuccess(profile_); } - // /** - // * รายชื่อขรก. ตามสิทธิ์ admin - // * - // * @summary รายชื่อขรก. ตามสิทธิ์ admin - // * - // */ - // @Post("employee-by-admin-rolev2") - // async GetEmployeesByAdminRoleV2( - // @Request() req: RequestWithUser, - // @Body() - // body: { - // node: number; - // nodeId: string; - // role: string; - // isRetirement?: boolean; - // reqNode?: number; - // reqNodeId?: string; - // date?: Date; - // }, - // ) { - // let typeCondition: any = {}; - // if (body.role === "CHILD" || body.role === "PARENT" || body.role === "BROTHER") { - // if (body.role === "CHILD") { - // switch (body.node) { - // case 0: - // typeCondition = { - // rootDnaId: body.nodeId, - // }; - // break; - // case 1: - // typeCondition = { - // child1DnaId: body.nodeId, - // }; - // break; - // case 2: - // typeCondition = { - // child2DnaId: body.nodeId, - // }; - // break; - // case 3: - // typeCondition = { - // child3DnaId: body.nodeId, - // }; - // break; - // case 4: - // typeCondition = { - // child4DnaId: body.nodeId, - // }; - // break; - // default: - // typeCondition = {}; - // break; - // } - // } else if (body.role === "BROTHER") { - // switch (body.node) { - // case 0: - // typeCondition = { - // rootDnaId: body.nodeId, - // }; - // break; - // case 1: - // typeCondition = { - // rootDnaId: body.nodeId, - // }; - // break; - // case 2: - // typeCondition = { - // child1DnaId: body.nodeId, - // }; - // break; - // case 3: - // typeCondition = { - // child2DnaId: body.nodeId, - // }; - // break; - // case 4: - // typeCondition = { - // child3DnaId: body.nodeId, - // }; - // break; - // default: - // typeCondition = {}; - // break; - // } - // } else if (body.role === "PARENT") { - // typeCondition = { - // rootDnaId: body.nodeId, - // child1DnaId: Not(IsNull()), - // }; - // } - // } else if (body.role === "OWNER" || body.role === "ROOT") { - // switch (body.reqNode) { - // case 0: - // typeCondition = { - // rootDnaId: body.reqNodeId, - // }; - // break; - // case 1: - // typeCondition = { - // child1DnaId: body.reqNodeId, - // }; - // break; - // case 2: - // typeCondition = { - // child2DnaId: body.reqNodeId, - // }; - // break; - // case 3: - // typeCondition = { - // child3DnaId: body.reqNodeId, - // }; - // break; - // case 4: - // typeCondition = { - // child4DnaId: body.reqNodeId, - // }; - // break; - // default: - // typeCondition = {}; - // break; - // } - // } else if (body.role === "NORMAL") { - // switch (body.node) { - // case 0: - // typeCondition = { - // rootDnaId: body.nodeId, - // child1DnaId: IsNull(), - // }; - // break; - // case 1: - // typeCondition = { - // child1DnaId: body.nodeId, - // child2DnaId: IsNull(), - // }; - // break; - // case 2: - // typeCondition = { - // child2DnaId: body.nodeId, - // child3DnaId: IsNull(), - // }; - // break; - // case 3: - // typeCondition = { - // child3DnaId: body.nodeId, - // child4DnaId: IsNull(), - // }; - // break; - // case 4: - // typeCondition = { - // child4DnaId: body.nodeId, - // }; - // break; - // default: - // typeCondition = {}; - // break; - // } - // } - // const date = body.date ? new Date(body.date) : new Date(); - // // set เวลาเป็น 23:59:59 ของวันนั้น - // date.setHours(23, 59, 59, 999); + /** + * รายชื่อลูกจ้างประจำ ตามสิทธิ์ admin + * @summary รายชื่อลูกจ้างประจำ ตามสิทธิ์ admin + */ + @Post("employee-by-admin-rolev2") + @Security("internalAuth") + async GetEmployeesByAdminRoleV2( + // @Request() req: RequestWithUser, // ไม่ได้ใช้ + @Body() + body: { + node: number; + nodeId: string; + role: string; + isRetirement?: boolean; + reqNode?: number; + reqNodeId?: string; + date: Date; + }, + ) { + let typeCondition: any = {}; + if (body.role === "CHILD" || body.role === "BROTHER") { + if (body.role === "CHILD") { + switch (body.node) { + case 0: + typeCondition = { + rootDnaId: body.nodeId, + }; + break; + case 1: + typeCondition = { + child1DnaId: body.nodeId, + }; + break; + case 2: + typeCondition = { + child2DnaId: body.nodeId, + }; + break; + case 3: + typeCondition = { + child3DnaId: body.nodeId, + }; + break; + case 4: + typeCondition = { + child4DnaId: body.nodeId, + }; + break; + default: + typeCondition = {}; + break; + } + } else if (body.role === "BROTHER") { + switch (body.node) { + case 0: + typeCondition = { + rootDnaId: body.nodeId, + }; + break; + case 1: + typeCondition = { + rootDnaId: body.nodeId, + }; + break; + case 2: + typeCondition = { + child1DnaId: body.nodeId, + }; + break; + case 3: + typeCondition = { + child2DnaId: body.nodeId, + }; + break; + case 4: + typeCondition = { + child3DnaId: body.nodeId, + }; + break; + default: + typeCondition = {}; + break; + } + } + } else if (body.role === "OWNER" || body.role === "ROOT" || body.role === "PARENT") { + switch (body.reqNode) { + case 0: + typeCondition = { + rootDnaId: body.reqNodeId, + }; + break; + case 1: + typeCondition = { + child1DnaId: body.reqNodeId, + }; + break; + case 2: + typeCondition = { + child2DnaId: body.reqNodeId, + }; + break; + case 3: + typeCondition = { + child3DnaId: body.reqNodeId, + }; + break; + case 4: + typeCondition = { + child4DnaId: body.reqNodeId, + }; + break; + default: + typeCondition = {}; + break; + } + } else if (body.role === "NORMAL") { + switch (body.node) { + case 0: + typeCondition = { + rootDnaId: body.nodeId, + child1DnaId: IsNull(), + }; + break; + case 1: + typeCondition = { + child1DnaId: body.nodeId, + child2DnaId: IsNull(), + }; + break; + case 2: + typeCondition = { + child2DnaId: body.nodeId, + child3DnaId: IsNull(), + }; + break; + case 3: + typeCondition = { + child3DnaId: body.nodeId, + child4DnaId: IsNull(), + }; + break; + case 4: + typeCondition = { + child4DnaId: body.nodeId, + }; + break; + default: + typeCondition = {}; + break; + } + } + // set เวลาเป็น 23:59:59 ของวันนั้น + const date = body.date ? new Date(body.date.toISOString().slice(0, 10)) : new Date(); + date.setHours(23, 59, 59, 999); - // let profile = await this.posMasterEmployeeHistoryRepository.find({ - // where: { - // ...typeCondition, - // createdAt: LessThanOrEqual(date), - // // firstName: Not("") && Not(IsNull()), - // // lastName: Not("") && Not(IsNull()), - // }, - // order: { - // firstName: "ASC", - // lastName: "ASC", - // createdAt: "DESC", // ให้ createdAt ล่าสุดอยู่ข้างบน - // }, - // }); + let posEmpHis = await this.posMasterEmployeeHistoryRepository.find({ + where: { + ...typeCondition, + createdAt: LessThanOrEqual(date), + }, + select: [ + "profileEmployeeId", + "prefix", + "firstName", + "lastName", + "shortName", + "posMasterNo", + "position", + "posType", + "posLevel", + "ancestorDNA", + "rootDnaId", + "child1DnaId", + "child2DnaId", + "child3DnaId", + "child4DnaId", + "createdAt", + ], + order: { + firstName: "ASC", + lastName: "ASC", + createdAt: "DESC", + }, + }); - // // group by ancestorDNA แล้วเลือก create_at ล่าสุด - // const grouped = new Map(); - // for (const item of profile) { - // const key = `${item.shortName}-${item.posMasterNo}`; - // if (!grouped.has(key)) { - // grouped.set(key, item); - // } else { - // // ถ้าเจอซ้ำ ให้เลือก createdAt ล่าสุด - // const exist = grouped.get(key); - // if (exist && item.createdAt > exist.createdAt) { - // grouped.set(key, item); - // } - // } - // } + // group1: group by ancestorDNA แล้วเลือก create_at ล่าสุด + const grouped1 = new Map(); + for (const item of posEmpHis) { + const key = `${item.ancestorDNA}`; + if (!grouped1.has(key)) { + grouped1.set(key, item); + } else { + // ถ้าเจอซ้ำ ให้เลือก createdAt ล่าสุด + const exist = grouped1.get(key); + if (exist && item.createdAt > exist.createdAt) { + grouped1.set(key, item); + } + } + } + // group2: group by shortName-posMasterNo จากค่าที่ได้จาก group1 + const grouped2 = new Map(); + for (const item of Array.from(grouped1.values())) { + const key = `${item.shortName}-${item.posMasterNo}`; + if (!grouped2.has(key)) { + grouped2.set(key, item); + } else { + // ถ้าเจอซ้ำ ให้เลือก createdAt ล่าสุด + const exist = grouped2.get(key); + if (exist && item.createdAt > exist.createdAt) { + grouped2.set(key, item); + } + } + } + // group3: group by firstName-lastName จากค่าที่ได้จาก group2 + const grouped3 = new Map(); + for (const item of Array.from(grouped2.values())) { + const key = `${item.firstName}-${item.lastName}`; + if (!grouped3.has(key)) { + grouped3.set(key, item); + } else { + // ถ้าเจอซ้ำ ให้เลือก createdAt ล่าสุด + const exist = grouped3.get(key); + if (exist && item.createdAt > exist.createdAt) { + grouped3.set(key, item); + } + } + } - // const profile_ = await Promise.all( - // Array.from(grouped.values()) - // .filter((x) => x.profileId != null) - // .map(async (item: PosMasterEmployeeHistory) => { - // let profile = await this.profileRepo.findOne({ - // where: { id: item.profileId }, - // }); + const profileEmployeeIds = Array.from(grouped3.values()) + .filter((x) => x.profileEmployeeId != null) + .map((x) => x.profileEmployeeId); - // return { - // id: item.profileId, - // prefix: item.prefix, - // firstName: item.firstName, - // lastName: item.lastName, - // citizenId: profile?.citizenId ?? null, - // dateStart: profile?.dateStart ?? null, - // dateAppoint: profile?.dateAppoint ?? null, - // keycloak: profile?.keycloak ?? null, - // posNo: item.shortName, - // position: item.position, - // positionLevel: item.posLevel, - // positionType: item.posType, - // // oc: Oc, - // orgRootId: item.rootDnaId, - // orgChild1Id: item.child1DnaId, - // orgChild2Id: item.child2DnaId, - // orgChild3Id: item.child3DnaId, - // orgChild4Id: item.child4DnaId, - // }; - // }), - // ); + const profileEmployees = await this.profileEmpRepo.find({ + where: { id: In(profileEmployeeIds) }, + select: ["id", "citizenId", "dateStart", "dateAppoint", "keycloak"], + }); - // return new HttpSuccess(profile_); - // } + const profileEmployeeMap = new Map(profileEmployees.map((p) => [p.id, p])); + + const profile_ = Array.from(grouped3.values()) + .filter((x) => x.profileEmployeeId != null) + .map((item: PosMasterEmployeeHistory) => { + const profileEmp = profileEmployeeMap.get(item.profileEmployeeId); + return { + id: item.profileEmployeeId, + prefix: item.prefix, + firstName: item.firstName, + lastName: item.lastName, + citizenId: profileEmp?.citizenId ?? null, + dateStart: profileEmp?.dateStart ?? null, + dateAppoint: profileEmp?.dateAppoint ?? null, + keycloak: profileEmp?.keycloak ?? null, + posNo: `${item.shortName} ${item.posMasterNo}`, + position: item.position, + positionLevel: item.posLevel, + positionType: item.posType, + orgRootId: item.rootDnaId, + orgChild1Id: item.child1DnaId, + orgChild2Id: item.child2DnaId, + orgChild3Id: item.child3DnaId, + orgChild4Id: item.child4DnaId, + }; + }); + + return new HttpSuccess( + (profile_ ?? []).sort((a, b) => a.posNo.localeCompare(b.posNo, undefined, { numeric: true })), + ); + } /** * 4. API Update รอบการลงเวลา ในตาราง profile @@ -7028,6 +7251,7 @@ export class OrganizationDotnetController extends Controller { * */ @Put("update-dutytime") + @Security("bearerAuth") async UpdateDutyTimeAsync( @Request() req: RequestWithUser, @Body() @@ -7070,6 +7294,7 @@ export class OrganizationDotnetController extends Controller { * */ @Post("insignia/Dumb") + @Security("bearerAuth") public async newInsignia(@Request() req: RequestWithUser, @Body() body: CreateProfileInsignia) { if (!body.profileId) { throw new HttpError(HttpStatus.BAD_REQUEST, "กรุณากรอก profileId"); @@ -7146,6 +7371,7 @@ export class OrganizationDotnetController extends Controller { * @param {string} keycloakId Id keycloak */ @Get("profile-leave/keycloak/{keycloakId}") + @Security("internalAuth") async GetProfileLeaveByKeycloakIdAsync(@Path() keycloakId: string) { const CURRENT_DATE = await AppDataSource.query("SELECT CURRENT_DATE() as today"); let _currentDate = CURRENT_DATE[0].today; @@ -7433,6 +7659,7 @@ export class OrganizationDotnetController extends Controller { } @Post("profile-leave/keycloak") + @Security("internalAuth") async GetProfileLeaveReportByKeycloakIdAsync( @Body() body: { keycloakId: string; report?: string }, ) { @@ -7737,6 +7964,7 @@ export class OrganizationDotnetController extends Controller { * @param {string} type ประเภท (ข้าราชการ หรือ ลูกจ้าง) */ @Post("find/insignia-requests-profile/{type}") + @Security("internalAuth") async GetInsigniaRequestsProfileAsync( @Path() type: string, @Body() @@ -7866,6 +8094,7 @@ export class OrganizationDotnetController extends Controller { * */ @Post("officer-by-admin-rolev2") + @Security("internalAuth") async GetOfficersByAdminRoleV2( @Body() body: { @@ -8089,6 +8318,7 @@ export class OrganizationDotnetController extends Controller { * */ @Post("officer-by-admin-rolev3") + @Security("internalAuth") async GetOfficersByAdminRoleV3( @Body() body: { @@ -8435,6 +8665,7 @@ export class OrganizationDotnetController extends Controller { * */ @Post("officer-by-admin-rolev4") + @Security("internalAuth") async GetOfficersByAdminRoleV4( @Body() body: { @@ -8711,6 +8942,7 @@ export class OrganizationDotnetController extends Controller { * */ @Post("find-staff") + @Security("internalAuth") async findHigher( @Body() requestBody: { @@ -8863,7 +9095,6 @@ export class OrganizationDotnetController extends Controller { // Staff ต้องอยู่บนกว่าหรือเท่ากับ User if (staffNode > userNode) return false; - // เช็ค DNA ตรงกันที่ระดับ Staff switch (staffNode) { case 0: if (staffDna.root !== userDna.root) return false; @@ -8896,25 +9127,22 @@ export class OrganizationDotnetController extends Controller { // BROTHER: Staff เห็น User ที่อยู่ในกิ่งข้างบนและลูก if (privilege === "BROTHER") { - // User ต้องอยู่ในช่วง [Staff Node - 1, 4] if (userNode < staffNode - 1 || userNode > 4) return false; - // เช็ค DNA ตรงกันตามระดับของ Staff - if (staffNode === 0) { + if (staffNode === 0 || staffNode === 1) { if (staffDna.root !== userDna.root) return false; - } else if (staffNode === 1) { + } /*else if (staffNode === 1) { if (staffDna.root !== userDna.root) return false; if (staffDna.child1 !== userDna.child1) return false; - } else if (staffNode === 2) { + }*/ else if (staffNode === 2) { if (staffDna.child1 !== userDna.child1) return false; - if (staffDna.child2 !== userDna.child2) return false; - if (staffDna.child3 !== userDna.child3) return false; + // if (staffDna.child2 !== userDna.child2 && userDna.child2 !== null) return false; } else if (staffNode === 3) { if (staffDna.child2 !== userDna.child2) return false; - if (staffDna.child3 !== userDna.child3) return false; + // if (staffDna.child3 !== userDna.child3 && userDna.child3 !== null) return false; } else if (staffNode === 4) { if (staffDna.child3 !== userDna.child3) return false; - if (staffDna.child4 !== userDna.child4) return false; + // if (staffDna.child4 !== userDna.child4 && userDna.child4 !== null) return false; } return true; } @@ -8923,4 +9151,43 @@ export class OrganizationDotnetController extends Controller { }); return new HttpSuccess(filteredPosMasters); } + + /** + * API ตรวจสอบสถานะผู้สมัครสอบ + * @summary API ตรวจสอบสถานะผู้สมัครสอบ + */ + @Post("check-isLeave") + @Security("internalAuth") + async findProfileIsLeave( + @Body() + req: { citizenIds: string[] } + ) { + + const profiles = await this.profileRepo.find({ + select: { + id: true, + citizenId: true, + isLeave: true, + isActive: true + }, + where: { + citizenId: In(req.citizenIds) + } + }); + + if (profiles.length === 0) { + return new HttpSuccess([]); + } + + return new HttpSuccess( + profiles.map(p => ({ + citizenId: p.citizenId, + profileId: p.id, + isLeave: p.isLeave ?? false, + isActive: p.isActive ?? false + })) + ); + + } + } diff --git a/src/controllers/PermissionController.ts b/src/controllers/PermissionController.ts index 8c713947..44747b09 100644 --- a/src/controllers/PermissionController.ts +++ b/src/controllers/PermissionController.ts @@ -37,11 +37,13 @@ export class PermissionController extends Controller { @Get("") public async getPermission(@Request() request: RequestWithUser) { - const redisClient = await this.redis.createClient({ - host: REDIS_HOST, - port: REDIS_PORT, - }); - const getAsync = promisify(redisClient.get).bind(redisClient); + let redisClient; + try { + redisClient = await this.redis.createClient({ + host: REDIS_HOST, + port: REDIS_PORT, + }); + const getAsync = promisify(redisClient.get).bind(redisClient); let profile: any = await this.profileRepo.findOne({ select: ["id"], @@ -270,6 +272,11 @@ export class PermissionController extends Controller { redisClient.setex("role_" + profile.id, 86400, JSON.stringify(reply)); } return new HttpSuccess(reply); + } finally { + if (redisClient) { + redisClient.quit(); + } + } } @Get("menu") @@ -281,11 +288,13 @@ export class PermissionController extends Controller { orgRevisionIsCurrent: true, }, }); - const redisClient = await this.redis.createClient({ - host: REDIS_HOST, - port: REDIS_PORT, - }); - const getAsync = promisify(redisClient.get).bind(redisClient); + let redisClient; + try { + redisClient = await this.redis.createClient({ + host: REDIS_HOST, + port: REDIS_PORT, + }); + const getAsync = promisify(redisClient.get).bind(redisClient); let profileType = "OFFICER"; let profile: any = await this.profileRepo.findOne({ @@ -438,6 +447,11 @@ export class PermissionController extends Controller { } return new HttpSuccess(reply); + } finally { + if (redisClient) { + redisClient.quit(); + } + } } /** @@ -672,11 +686,13 @@ export class PermissionController extends Controller { @Path() system: string, @Path() action: string, ) { - const redisClient = await this.redis.createClient({ - host: REDIS_HOST, - port: REDIS_PORT, - }); - const getAsync = promisify(redisClient.get).bind(redisClient); + let redisClient; + try { + redisClient = await this.redis.createClient({ + host: REDIS_HOST, + port: REDIS_PORT, + }); + const getAsync = promisify(redisClient.get).bind(redisClient); let profileType = "OFFICER"; let profile: any = await this.profileRepo.findOne({ @@ -765,6 +781,11 @@ export class PermissionController extends Controller { } return new HttpSuccess(reply); + } finally { + if (redisClient) { + redisClient.quit(); + } + } } @Get("user/{system}/{action}/{id}") @@ -781,11 +802,13 @@ export class PermissionController extends Controller { orgRevisionIsCurrent: true, }, }); - const redisClient = await this.redis.createClient({ - host: REDIS_HOST, - port: REDIS_PORT, - }); - const getAsync = promisify(redisClient.get).bind(redisClient); + let redisClient; + try { + redisClient = await this.redis.createClient({ + host: REDIS_HOST, + port: REDIS_PORT, + }); + const getAsync = promisify(redisClient.get).bind(redisClient); let org = this.PermissionOrg(request, system, action); let reply = await getAsync("user_" + id); @@ -866,14 +889,21 @@ export class PermissionController extends Controller { } return new HttpSuccess(reply); + } finally { + if (redisClient) { + redisClient.quit(); + } + } } public async getPermissionFunc(@Request() request: RequestWithUser) { - const redisClient = await this.redis.createClient({ - host: REDIS_HOST, - port: REDIS_PORT, - }); - const getAsync = promisify(redisClient.get).bind(redisClient); + let redisClient; + try { + redisClient = await this.redis.createClient({ + host: REDIS_HOST, + port: REDIS_PORT, + }); + const getAsync = promisify(redisClient.get).bind(redisClient); let profile: any = await this.profileRepo.findOne({ select: ["id"], @@ -1090,6 +1120,11 @@ export class PermissionController extends Controller { redisClient.setex("role_" + profile.id, 86400, JSON.stringify(reply)); } return reply; + } finally { + if (redisClient) { + redisClient.quit(); + } + } } public async Permission(req: RequestWithUser, system: string, action: string) { @@ -1115,11 +1150,13 @@ export class PermissionController extends Controller { } public async listAuthSysOrgFunc(request: RequestWithUser, system: string, action: string) { - const redisClient = await this.redis.createClient({ - host: REDIS_HOST, - port: REDIS_PORT, - }); - const getAsync = promisify(redisClient.get).bind(redisClient); + let redisClient; + try { + redisClient = await this.redis.createClient({ + host: REDIS_HOST, + port: REDIS_PORT, + }); + const getAsync = promisify(redisClient.get).bind(redisClient); let profileType = "OFFICER"; let profile: any = await this.profileRepo.findOne({ @@ -1187,6 +1224,11 @@ export class PermissionController extends Controller { redisClient.setex("posMaster_" + profile.id, 86400, JSON.stringify(reply)); } return reply; + } finally { + if (redisClient) { + redisClient.quit(); + } + } } // Helper method: ดึง org scope จากตำแหน่งปกติ @@ -1366,11 +1408,13 @@ export class PermissionController extends Controller { @Get("checkOrg/{keycloakId}") public async checkOrg(@Path() keycloakId: string) { - const redisClient = await this.redis.createClient({ - host: REDIS_HOST, - port: REDIS_PORT, - }); - // const getAsync = promisify(redisClient.get).bind(redisClient); + let redisClient; + try { + redisClient = await this.redis.createClient({ + host: REDIS_HOST, + port: REDIS_PORT, + }); + // const getAsync = promisify(redisClient.get).bind(redisClient); // let profileType = "OFFICER"; let profile: any = await this.profileRepo.findOne({ @@ -1448,5 +1492,10 @@ export class PermissionController extends Controller { // } return new HttpSuccess(reply); + } finally { + if (redisClient) { + redisClient.quit(); + } + } } } diff --git a/src/controllers/PermissionProfileController.ts b/src/controllers/PermissionProfileController.ts index 9f565828..0df114af 100644 --- a/src/controllers/PermissionProfileController.ts +++ b/src/controllers/PermissionProfileController.ts @@ -65,7 +65,7 @@ export class PermissionProfileController extends Controller { if (!request.user.role.includes("SUPER_ADMIN")) { rootId = - orgRevisionActive?.posMasters?.filter((x) => x.next_holderId == profile.id)[0] + orgRevisionActive?.posMasters?.filter((x) => x.current_holderId == profile.id)[0] ?.orgRootId || null; if (!rootId) return new HttpSuccess([]); } @@ -165,59 +165,59 @@ export class PermissionProfileController extends Controller { _data.current_holders.length == 0 ? null : _data.current_holders.find((x) => x.orgRevisionId == findRevision.id) != null && - _data.current_holders.find((x) => x.orgRevisionId == findRevision.id)?.orgChild4 != - null + _data.current_holders.find((x) => x.orgRevisionId == findRevision.id)?.orgChild4 != + null ? `${_data.current_holders.find((x) => x.orgRevisionId == findRevision.id)?.orgChild4.orgChild4ShortName} ${_data.current_holders.find((x) => x.orgRevisionId == findRevision.id)?.posMasterNo}` : _data.current_holders.find((x) => x.orgRevisionId == findRevision.id) != null && - _data.current_holders.find((x) => x.orgRevisionId == findRevision.id) - ?.orgChild3 != null + _data.current_holders.find((x) => x.orgRevisionId == findRevision.id) + ?.orgChild3 != null ? `${_data.current_holders.find((x) => x.orgRevisionId == findRevision.id)?.orgChild3.orgChild3ShortName} ${_data.current_holders.find((x) => x.orgRevisionId == findRevision.id)?.posMasterNo}` : _data.current_holders.find((x) => x.orgRevisionId == findRevision.id) != null && - _data.current_holders.find((x) => x.orgRevisionId == findRevision.id) - ?.orgChild2 != null + _data.current_holders.find((x) => x.orgRevisionId == findRevision.id) + ?.orgChild2 != null ? `${_data.current_holders.find((x) => x.orgRevisionId == findRevision.id)?.orgChild2.orgChild2ShortName} ${_data.current_holders.find((x) => x.orgRevisionId == findRevision.id)?.posMasterNo}` : _data.current_holders.find((x) => x.orgRevisionId == findRevision.id) != null && - _data.current_holders.find((x) => x.orgRevisionId == findRevision.id) - ?.orgChild1 != null + _data.current_holders.find((x) => x.orgRevisionId == findRevision.id) + ?.orgChild1 != null ? `${_data.current_holders.find((x) => x.orgRevisionId == findRevision.id)?.orgChild1.orgChild1ShortName} ${_data.current_holders.find((x) => x.orgRevisionId == findRevision.id)?.posMasterNo}` : _data.current_holders.find((x) => x.orgRevisionId == findRevision.id) != - null && - _data.current_holders.find((x) => x.orgRevisionId == findRevision.id) - ?.orgRoot != null + null && + _data.current_holders.find((x) => x.orgRevisionId == findRevision.id) + ?.orgRoot != null ? `${_data.current_holders.find((x) => x.orgRevisionId == findRevision.id)?.orgRoot.orgRootShortName} ${_data.current_holders.find((x) => x.orgRevisionId == findRevision.id)?.posMasterNo}` : null; const root = _data.current_holders.length == 0 || - (_data.current_holders.find((x) => x.orgRevisionId == findRevision.id) != null && - _data.current_holders.find((x) => x.orgRevisionId == findRevision.id)?.orgRoot == null) + (_data.current_holders.find((x) => x.orgRevisionId == findRevision.id) != null && + _data.current_holders.find((x) => x.orgRevisionId == findRevision.id)?.orgRoot == null) ? null : _data.current_holders.find((x) => x.orgRevisionId == findRevision.id)?.orgRoot; const child1 = _data.current_holders == null || - _data.current_holders.length == 0 || - _data.current_holders.find((x) => x.orgRevisionId == findRevision.id) == null + _data.current_holders.length == 0 || + _data.current_holders.find((x) => x.orgRevisionId == findRevision.id) == null ? null : _data.current_holders.find((x) => x.orgRevisionId == findRevision.id)?.orgChild1; const child2 = _data.current_holders == null || - _data.current_holders.length == 0 || - _data.current_holders.find((x) => x.orgRevisionId == findRevision.id) == null + _data.current_holders.length == 0 || + _data.current_holders.find((x) => x.orgRevisionId == findRevision.id) == null ? null : _data.current_holders.find((x) => x.orgRevisionId == findRevision.id)?.orgChild2; const child3 = _data.current_holders == null || - _data.current_holders.length == 0 || - _data.current_holders.find((x) => x.orgRevisionId == findRevision.id) == null + _data.current_holders.length == 0 || + _data.current_holders.find((x) => x.orgRevisionId == findRevision.id) == null ? null : _data.current_holders.find((x) => x.orgRevisionId == findRevision.id)?.orgChild3; const child4 = _data.current_holders == null || - _data.current_holders.length == 0 || - _data.current_holders.find((x) => x.orgRevisionId == findRevision.id) == null + _data.current_holders.length == 0 || + _data.current_holders.find((x) => x.orgRevisionId == findRevision.id) == null ? null : _data.current_holders.find((x) => x.orgRevisionId == findRevision.id)?.orgChild4; @@ -363,78 +363,78 @@ export class PermissionProfileController extends Controller { _data.profileTree.current_holders.length == 0 ? null : _data.profileTree.current_holders.find((x) => x.orgRevisionId == findRevision.id) != - null && - _data.profileTree.current_holders.find((x) => x.orgRevisionId == findRevision.id) - ?.orgChild4 != null + null && + _data.profileTree.current_holders.find((x) => x.orgRevisionId == findRevision.id) + ?.orgChild4 != null ? `${_data.profileTree.current_holders.find((x) => x.orgRevisionId == findRevision.id)?.orgChild4.orgChild4ShortName} ${_data.profileTree.current_holders.find((x) => x.orgRevisionId == findRevision.id)?.posMasterNo}` : _data.profileTree.current_holders.find((x) => x.orgRevisionId == findRevision.id) != - null && - _data.profileTree.current_holders.find((x) => x.orgRevisionId == findRevision.id) - ?.orgChild3 != null + null && + _data.profileTree.current_holders.find((x) => x.orgRevisionId == findRevision.id) + ?.orgChild3 != null ? `${_data.profileTree.current_holders.find((x) => x.orgRevisionId == findRevision.id)?.orgChild3.orgChild3ShortName} ${_data.profileTree.current_holders.find((x) => x.orgRevisionId == findRevision.id)?.posMasterNo}` : _data.profileTree.current_holders.find( - (x) => x.orgRevisionId == findRevision.id, - ) != null && - _data.profileTree.current_holders.find( - (x) => x.orgRevisionId == findRevision.id, - )?.orgChild2 != null + (x) => x.orgRevisionId == findRevision.id, + ) != null && + _data.profileTree.current_holders.find( + (x) => x.orgRevisionId == findRevision.id, + )?.orgChild2 != null ? `${_data.profileTree.current_holders.find((x) => x.orgRevisionId == findRevision.id)?.orgChild2.orgChild2ShortName} ${_data.profileTree.current_holders.find((x) => x.orgRevisionId == findRevision.id)?.posMasterNo}` : _data.profileTree.current_holders.find( - (x) => x.orgRevisionId == findRevision.id, - ) != null && - _data.profileTree.current_holders.find( - (x) => x.orgRevisionId == findRevision.id, - )?.orgChild1 != null + (x) => x.orgRevisionId == findRevision.id, + ) != null && + _data.profileTree.current_holders.find( + (x) => x.orgRevisionId == findRevision.id, + )?.orgChild1 != null ? `${_data.profileTree.current_holders.find((x) => x.orgRevisionId == findRevision.id)?.orgChild1.orgChild1ShortName} ${_data.profileTree.current_holders.find((x) => x.orgRevisionId == findRevision.id)?.posMasterNo}` : _data.profileTree.current_holders.find( - (x) => x.orgRevisionId == findRevision.id, - ) != null && - _data.profileTree.current_holders.find( - (x) => x.orgRevisionId == findRevision.id, - )?.orgRoot != null + (x) => x.orgRevisionId == findRevision.id, + ) != null && + _data.profileTree.current_holders.find( + (x) => x.orgRevisionId == findRevision.id, + )?.orgRoot != null ? `${_data.profileTree.current_holders.find((x) => x.orgRevisionId == findRevision.id)?.orgRoot.orgRootShortName} ${_data.profileTree.current_holders.find((x) => x.orgRevisionId == findRevision.id)?.posMasterNo}` : null; const root = _data.profileTree.current_holders.length == 0 || - (_data.profileTree.current_holders.find((x) => x.orgRevisionId == findRevision.id) != - null && - _data.profileTree.current_holders.find((x) => x.orgRevisionId == findRevision.id) - ?.orgRoot == null) + (_data.profileTree.current_holders.find((x) => x.orgRevisionId == findRevision.id) != + null && + _data.profileTree.current_holders.find((x) => x.orgRevisionId == findRevision.id) + ?.orgRoot == null) ? null : _data.profileTree.current_holders.find((x) => x.orgRevisionId == findRevision.id) - ?.orgRoot; + ?.orgRoot; const child1 = _data.profileTree.current_holders == null || - _data.profileTree.current_holders.length == 0 || - _data.profileTree.current_holders.find((x) => x.orgRevisionId == findRevision.id) == null + _data.profileTree.current_holders.length == 0 || + _data.profileTree.current_holders.find((x) => x.orgRevisionId == findRevision.id) == null ? null : _data.profileTree.current_holders.find((x) => x.orgRevisionId == findRevision.id) - ?.orgChild1; + ?.orgChild1; const child2 = _data.profileTree.current_holders == null || - _data.profileTree.current_holders.length == 0 || - _data.profileTree.current_holders.find((x) => x.orgRevisionId == findRevision.id) == null + _data.profileTree.current_holders.length == 0 || + _data.profileTree.current_holders.find((x) => x.orgRevisionId == findRevision.id) == null ? null : _data.profileTree.current_holders.find((x) => x.orgRevisionId == findRevision.id) - ?.orgChild2; + ?.orgChild2; const child3 = _data.profileTree.current_holders == null || - _data.profileTree.current_holders.length == 0 || - _data.profileTree.current_holders.find((x) => x.orgRevisionId == findRevision.id) == null + _data.profileTree.current_holders.length == 0 || + _data.profileTree.current_holders.find((x) => x.orgRevisionId == findRevision.id) == null ? null : _data.profileTree.current_holders.find((x) => x.orgRevisionId == findRevision.id) - ?.orgChild3; + ?.orgChild3; const child4 = _data.profileTree.current_holders == null || - _data.profileTree.current_holders.length == 0 || - _data.profileTree.current_holders.find((x) => x.orgRevisionId == findRevision.id) == null + _data.profileTree.current_holders.length == 0 || + _data.profileTree.current_holders.find((x) => x.orgRevisionId == findRevision.id) == null ? null : _data.profileTree.current_holders.find((x) => x.orgRevisionId == findRevision.id) - ?.orgChild4; + ?.orgChild4; let _root = root?.orgRootName; let _child1 = child1?.orgChild1Name; diff --git a/src/controllers/PositionController.ts b/src/controllers/PositionController.ts index 98120f30..96fa3db6 100644 --- a/src/controllers/PositionController.ts +++ b/src/controllers/PositionController.ts @@ -38,7 +38,7 @@ import { EmployeePosLevel } from "../entities/EmployeePosLevel"; import { AuthRole } from "../entities/AuthRole"; import { RequestWithUser } from "../middlewares/user"; import permission from "../interfaces/permission"; -import { resolveNodeLevel, setLogDataDiff } from "../interfaces/utils"; +import { resolveNodeLevel, setLogDataDiff, logPositionIsSelectedChange } from "../interfaces/utils"; import { getPosMasterNo, getOrgFullName } from "../utils/org-formatting"; import { PosMasterAssign } from "../entities/PosMasterAssign"; import { Assign } from "../entities/Assign"; @@ -1427,7 +1427,17 @@ export class PositionController extends Controller { requestBody.positions.map(async (x: any) => { const match = posMaster.positions.find((p: any) => p.id == x.id); if (match) { - match.positionIsSelected = x.positionIsSelected ?? false; + const oldValue = match.positionIsSelected; + const newValue = x.positionIsSelected ?? false; + + logPositionIsSelectedChange(match.id, oldValue, newValue, { + posMasterId: posMaster.id, + userId: request.user.sub, + endpoint: "updateMaster", + action: "update_position", + }); + + match.positionIsSelected = newValue; match.orderNo = x.orderNo ?? null; return match; } else { @@ -1678,11 +1688,11 @@ export class PositionController extends Controller { let checkChildConditions: any = {}; let keywordAsInt: any; let searchShortName = "1=1"; - let searchShortName0 = `CONCAT(orgRoot.orgRootShortName," ",posMaster.posMasterNoPrefix,posMaster.posMasterNo,posMaster.posMasterNoSuffix)`; - let searchShortName1 = `CONCAT(orgChild1.orgChild1ShortName," ",posMaster.posMasterNoPrefix,posMaster.posMasterNo,posMaster.posMasterNoSuffix)`; - let searchShortName2 = `CONCAT(orgChild2.orgChild2ShortName," ",posMaster.posMasterNoPrefix,posMaster.posMasterNo,posMaster.posMasterNoSuffix)`; - let searchShortName3 = `CONCAT(orgChild3.orgChild3ShortName," ",posMaster.posMasterNoPrefix,posMaster.posMasterNo,posMaster.posMasterNoSuffix)`; - let searchShortName4 = `CONCAT(orgChild4.orgChild4ShortName," ",posMaster.posMasterNoPrefix,posMaster.posMasterNo,posMaster.posMasterNoSuffix)`; + let searchShortName0 = `CONCAT_WS(' ',orgRoot.orgRootShortName,NULLIF(posMaster.posMasterNoPrefix,''),posMaster.posMasterNo,NULLIF(posMaster.posMasterNoSuffix,''))`; + let searchShortName1 = `CONCAT_WS(' ',orgChild1.orgChild1ShortName,NULLIF(posMaster.posMasterNoPrefix,''),posMaster.posMasterNo,NULLIF(posMaster.posMasterNoSuffix,''))`; + let searchShortName2 = `CONCAT_WS(' ',orgChild2.orgChild2ShortName,NULLIF(posMaster.posMasterNoPrefix,''),posMaster.posMasterNo,NULLIF(posMaster.posMasterNoSuffix,''))`; + let searchShortName3 = `CONCAT_WS(' ',orgChild3.orgChild3ShortName,NULLIF(posMaster.posMasterNoPrefix,''),posMaster.posMasterNo,NULLIF(posMaster.posMasterNoSuffix,''))`; + let searchShortName4 = `CONCAT_WS(' ',orgChild4.orgChild4ShortName,NULLIF(posMaster.posMasterNoPrefix,''),posMaster.posMasterNo,NULLIF(posMaster.posMasterNoSuffix,''))`; if (body.type != null && body.id != null) { if (body.type === 0) { typeCondition = { @@ -1692,7 +1702,7 @@ export class PositionController extends Controller { checkChildConditions = { orgChild1Id: IsNull(), }; - searchShortName = `CONCAT(orgRoot.orgRootShortName," ",posMaster.posMasterNoPrefix,posMaster.posMasterNo,posMaster.posMasterNoSuffix) like '%${body.keyword}%'`; + searchShortName = `CONCAT_WS(' ',orgRoot.orgRootShortName,NULLIF(posMaster.posMasterNoPrefix,''),posMaster.posMasterNo,NULLIF(posMaster.posMasterNoSuffix,'')) like '%${body.keyword}%'`; } else { } } else if (body.type === 1) { @@ -1703,7 +1713,7 @@ export class PositionController extends Controller { checkChildConditions = { orgChild2Id: IsNull(), }; - searchShortName = `CONCAT(orgChild1.orgChild1ShortName," ",posMaster.posMasterNoPrefix,posMaster.posMasterNo,posMaster.posMasterNoSuffix) like '%${body.keyword}%'`; + searchShortName = `CONCAT_WS(' ',orgChild1.orgChild1ShortName,NULLIF(posMaster.posMasterNoPrefix,''),posMaster.posMasterNo,NULLIF(posMaster.posMasterNoSuffix,'')) like '%${body.keyword}%'`; } else { } } else if (body.type === 2) { @@ -1714,7 +1724,7 @@ export class PositionController extends Controller { checkChildConditions = { orgChild3Id: IsNull(), }; - searchShortName = `CONCAT(orgChild2.orgChild2ShortName," ",posMaster.posMasterNoPrefix,posMaster.posMasterNo,posMaster.posMasterNoSuffix) like '%${body.keyword}%'`; + searchShortName = `CONCAT_WS(' ',orgChild2.orgChild2ShortName,NULLIF(posMaster.posMasterNoPrefix,''),posMaster.posMasterNo,NULLIF(posMaster.posMasterNoSuffix,'')) like '%${body.keyword}%'`; } else { } } else if (body.type === 3) { @@ -1725,14 +1735,14 @@ export class PositionController extends Controller { checkChildConditions = { orgChild4Id: IsNull(), }; - searchShortName = `CONCAT(orgChild3.orgChild3ShortName," ",posMaster.posMasterNoPrefix,posMaster.posMasterNo,posMaster.posMasterNoSuffix) like '%${body.keyword}%'`; + searchShortName = `CONCAT_WS(' ',orgChild3.orgChild3ShortName,NULLIF(posMaster.posMasterNoPrefix,''),posMaster.posMasterNo,NULLIF(posMaster.posMasterNoSuffix,'')) like '%${body.keyword}%'`; } else { } } else if (body.type === 4) { typeCondition = { orgChild4Id: body.id, }; - searchShortName = `CONCAT(orgChild4.orgChild4ShortName," ",posMaster.posMasterNoPrefix,posMaster.posMasterNo,posMaster.posMasterNoSuffix) like '%${body.keyword}%'`; + searchShortName = `CONCAT_WS(' ',orgChild4.orgChild4ShortName,NULLIF(posMaster.posMasterNoPrefix,''),posMaster.posMasterNo,NULLIF(posMaster.posMasterNoSuffix,'')) like '%${body.keyword}%'`; } } else { body.isAll = true; @@ -1777,10 +1787,8 @@ export class PositionController extends Controller { select: ["posMasterId"], }); masterId = masterId.concat(findPosition.map((position: any) => position.posMasterId)); - keywordAsInt = body.keyword == null ? null : parseInt(body.keyword, 10); - if (isNaN(keywordAsInt)) { - keywordAsInt = "P@ssw0rd!z"; - } + const numericMatch = body.keyword == null ? null : body.keyword.match(/\d+/); + keywordAsInt = numericMatch ? parseInt(numericMatch[0], 10) : null; masterId = [...new Set(masterId)]; //serch name สิทธิ์ @@ -1813,7 +1821,7 @@ export class PositionController extends Controller { ...(body.keyword && (masterId.length > 0 ? { id: In(masterId) } - : { posMasterNo: Like(`%${body.keyword}%`) })), + : /^\d+$/.test(body.keyword) ? { posMasterNo: keywordAsInt } : { posMasterNo: Like(`%${body.keyword}%`) })), }, ]; let [posMaster, total] = await AppDataSource.getRepository(PosMaster) @@ -2154,11 +2162,11 @@ export class PositionController extends Controller { let checkChildConditions: any = {}; let keywordAsInt: any; let searchShortName = "1=1"; - let searchShortName0 = `CONCAT(orgRoot.orgRootShortName," ",posMaster.posMasterNo)`; - let searchShortName1 = `CONCAT(orgChild1.orgChild1ShortName," ",posMaster.posMasterNo)`; - let searchShortName2 = `CONCAT(orgChild2.orgChild2ShortName," ",posMaster.posMasterNo)`; - let searchShortName3 = `CONCAT(orgChild3.orgChild3ShortName," ",posMaster.posMasterNo)`; - let searchShortName4 = `CONCAT(orgChild4.orgChild4ShortName," ",posMaster.posMasterNo)`; + let searchShortName0 = `CONCAT_WS(' ',orgRoot.orgRootShortName,NULLIF(posMaster.posMasterNoPrefix,''),posMaster.posMasterNo,NULLIF(posMaster.posMasterNoSuffix,''))`; + let searchShortName1 = `CONCAT_WS(' ',orgChild1.orgChild1ShortName,NULLIF(posMaster.posMasterNoPrefix,''),posMaster.posMasterNo,NULLIF(posMaster.posMasterNoSuffix,''))`; + let searchShortName2 = `CONCAT_WS(' ',orgChild2.orgChild2ShortName,NULLIF(posMaster.posMasterNoPrefix,''),posMaster.posMasterNo,NULLIF(posMaster.posMasterNoSuffix,''))`; + let searchShortName3 = `CONCAT_WS(' ',orgChild3.orgChild3ShortName,NULLIF(posMaster.posMasterNoPrefix,''),posMaster.posMasterNo,NULLIF(posMaster.posMasterNoSuffix,''))`; + let searchShortName4 = `CONCAT_WS(' ',orgChild4.orgChild4ShortName,NULLIF(posMaster.posMasterNoPrefix,''),posMaster.posMasterNo,NULLIF(posMaster.posMasterNoSuffix,''))`; let _data = await new permission().PermissionOrgList(request, "SYS_ORG"); if (body.type === 0) { typeCondition = { @@ -2168,7 +2176,7 @@ export class PositionController extends Controller { checkChildConditions = { orgChild1Id: IsNull(), }; - searchShortName = `CONCAT(orgRoot.orgRootShortName," ",posMaster.posMasterNo) like '%${body.keyword}%'`; + searchShortName = `CONCAT_WS(' ',orgRoot.orgRootShortName,NULLIF(posMaster.posMasterNoPrefix,''),posMaster.posMasterNo,NULLIF(posMaster.posMasterNoSuffix,'')) like '%${body.keyword}%'`; } } else if (body.type === 1) { typeCondition = { @@ -2178,7 +2186,7 @@ export class PositionController extends Controller { checkChildConditions = { orgChild2Id: IsNull(), }; - searchShortName = `CONCAT(orgChild1.orgChild1ShortName," ",posMaster.posMasterNo) like '%${body.keyword}%'`; + searchShortName = `CONCAT_WS(' ',orgChild1.orgChild1ShortName,NULLIF(posMaster.posMasterNoPrefix,''),posMaster.posMasterNo,NULLIF(posMaster.posMasterNoSuffix,'')) like '%${body.keyword}%'`; } } else if (body.type === 2) { typeCondition = { @@ -2188,7 +2196,7 @@ export class PositionController extends Controller { checkChildConditions = { orgChild3Id: IsNull(), }; - searchShortName = `CONCAT(orgChild2.orgChild2ShortName," ",posMaster.posMasterNo) like '%${body.keyword}%'`; + searchShortName = `CONCAT_WS(' ',orgChild2.orgChild2ShortName,NULLIF(posMaster.posMasterNoPrefix,''),posMaster.posMasterNo,NULLIF(posMaster.posMasterNoSuffix,'')) like '%${body.keyword}%'`; } } else if (body.type === 3) { typeCondition = { @@ -2198,13 +2206,13 @@ export class PositionController extends Controller { checkChildConditions = { orgChild4Id: IsNull(), }; - searchShortName = `CONCAT(orgChild3.orgChild3ShortName," ",posMaster.posMasterNo) like '%${body.keyword}%'`; + searchShortName = `CONCAT_WS(' ',orgChild3.orgChild3ShortName,NULLIF(posMaster.posMasterNoPrefix,''),posMaster.posMasterNo,NULLIF(posMaster.posMasterNoSuffix,'')) like '%${body.keyword}%'`; } } else if (body.type === 4) { typeCondition = { orgChild4Id: body.id, }; - searchShortName = `CONCAT(orgChild4.orgChild4ShortName," ",posMaster.posMasterNo) like '%${body.keyword}%'`; + searchShortName = `CONCAT_WS(' ',orgChild4.orgChild4ShortName,NULLIF(posMaster.posMasterNoPrefix,''),posMaster.posMasterNo,NULLIF(posMaster.posMasterNoSuffix,'')) like '%${body.keyword}%'`; } let findPosition: any; let masterId = new Array(); @@ -2241,10 +2249,8 @@ export class PositionController extends Controller { select: ["posMasterId"], }); masterId = masterId.concat(findPosition.map((position: any) => position.posMasterId)); - keywordAsInt = body.keyword == null ? null : parseInt(body.keyword, 10); - if (isNaN(keywordAsInt)) { - keywordAsInt = "P@ssw0rd!z"; - } + const numericMatch = body.keyword == null ? null : body.keyword.match(/\d+/); + keywordAsInt = numericMatch ? parseInt(numericMatch[0], 10) : null; masterId = [...new Set(masterId)]; } @@ -2271,7 +2277,7 @@ export class PositionController extends Controller { ...(body.keyword && (masterId.length > 0 ? { id: In(masterId) } - : { posMasterNo: Like(`%${body.keyword}%`) })), + : /^\d+$/.test(body.keyword) ? { posMasterNo: keywordAsInt } : { posMasterNo: Like(`%${body.keyword}%`) })), }, ]; @@ -2760,7 +2766,19 @@ export class PositionController extends Controller { id: data.id, posMasterOrder: requestBody.sortId.indexOf(data.id) + 1, })); - await this.posMasterRepository.save(sortData_0, { data: request }); + // Bulk update using CASE WHEN instead of save() per row + const caseClauses_0 = sortData_0 + .map((d) => `WHEN '${d.id}' THEN ${d.posMasterOrder}`) + .join(" "); + const ids_0 = sortData_0.map((d) => `'${d.id}'`).join(","); + await this.posMasterRepository + .createQueryBuilder() + .update(PosMaster) + .set({ + posMasterOrder: () => `CASE id ${caseClauses_0} END`, + }) + .where(`id IN (${ids_0})`) + .execute(); setLogDataDiff(request, { before, after: sortData_0 }); break; } @@ -2789,7 +2807,19 @@ export class PositionController extends Controller { id: data.id, posMasterOrder: requestBody.sortId.indexOf(data.id) + 1, })); - await this.posMasterRepository.save(sortData_1, { data: request }); + // Bulk update using CASE WHEN instead of save() per row + const caseClauses_1 = sortData_1 + .map((d) => `WHEN '${d.id}' THEN ${d.posMasterOrder}`) + .join(" "); + const ids_1 = sortData_1.map((d) => `'${d.id}'`).join(","); + await this.posMasterRepository + .createQueryBuilder() + .update(PosMaster) + .set({ + posMasterOrder: () => `CASE id ${caseClauses_1} END`, + }) + .where(`id IN (${ids_1})`) + .execute(); setLogDataDiff(request, { before, after: sortData_1 }); break; } @@ -2818,7 +2848,19 @@ export class PositionController extends Controller { id: data.id, posMasterOrder: requestBody.sortId.indexOf(data.id) + 1, })); - await this.posMasterRepository.save(sortData_2, { data: request }); + // Bulk update using CASE WHEN instead of save() per row + const caseClauses_2 = sortData_2 + .map((d) => `WHEN '${d.id}' THEN ${d.posMasterOrder}`) + .join(" "); + const ids_2 = sortData_2.map((d) => `'${d.id}'`).join(","); + await this.posMasterRepository + .createQueryBuilder() + .update(PosMaster) + .set({ + posMasterOrder: () => `CASE id ${caseClauses_2} END`, + }) + .where(`id IN (${ids_2})`) + .execute(); setLogDataDiff(request, { before, after: sortData_2 }); break; } @@ -2847,7 +2889,19 @@ export class PositionController extends Controller { id: data.id, posMasterOrder: requestBody.sortId.indexOf(data.id) + 1, })); - await this.posMasterRepository.save(sortData_3, { data: request }); + // Bulk update using CASE WHEN instead of save() per row + const caseClauses_3 = sortData_3 + .map((d) => `WHEN '${d.id}' THEN ${d.posMasterOrder}`) + .join(" "); + const ids_3 = sortData_3.map((d) => `'${d.id}'`).join(","); + await this.posMasterRepository + .createQueryBuilder() + .update(PosMaster) + .set({ + posMasterOrder: () => `CASE id ${caseClauses_3} END`, + }) + .where(`id IN (${ids_3})`) + .execute(); setLogDataDiff(request, { before, after: sortData_3 }); break; } @@ -2876,7 +2930,19 @@ export class PositionController extends Controller { id: data.id, posMasterOrder: requestBody.sortId.indexOf(data.id) + 1, })); - await this.posMasterRepository.save(sortData_4, { data: request }); + // Bulk update using CASE WHEN instead of save() per row + const caseClauses_4 = sortData_4 + .map((d) => `WHEN '${d.id}' THEN ${d.posMasterOrder}`) + .join(" "); + const ids_4 = sortData_4.map((d) => `'${d.id}'`).join(","); + await this.posMasterRepository + .createQueryBuilder() + .update(PosMaster) + .set({ + posMasterOrder: () => `CASE id ${caseClauses_4} END`, + }) + .where(`id IN (${ids_4})`) + .execute(); setLogDataDiff(request, { before, after: sortData_4 }); break; } @@ -3974,7 +4040,18 @@ export class PositionController extends Controller { statusReport: "PENDING", }); + console.log( + `[positionIsSelected-DEBUG] Deleting holder, resetting ALL positions to false (posMasterId: ${id}, userId: ${request.user.sub}, endpoint: deleteHolder)` + ); + dataMaster.positions.forEach(async (position) => { + logPositionIsSelectedChange(position.id, position.positionIsSelected, false, { + posMasterId: id, + userId: request.user.sub, + endpoint: "deleteHolder", + action: "delete_holder_reset_positions", + }); + await this.positionRepository.update(position.id, { positionIsSelected: false, }); @@ -4165,6 +4242,7 @@ export class PositionController extends Controller { const [posMaster, total] = await AppDataSource.getRepository(PosMaster) .createQueryBuilder("posMaster") + .leftJoinAndSelect("posMaster.orgRevision", "orgRevision") .leftJoinAndSelect("posMaster.orgRoot", "orgRoot") .leftJoinAndSelect("posMaster.orgChild1", "orgChild1") .leftJoinAndSelect("posMaster.orgChild2", "orgChild2") @@ -4176,6 +4254,8 @@ export class PositionController extends Controller { .leftJoinAndSelect("positions.posType", "posType") .leftJoinAndSelect("positions.posLevel", "posLevel") .leftJoinAndSelect("positions.posExecutive", "posExecutive") + .andWhere("orgRevision.orgRevisionIsCurrent = true") + .andWhere("orgRevision.orgRevisionIsDraft = false") .andWhere( new Brackets((qb) => { qb.andWhere(typeCondition).andWhere(conditionA == null ? "1=1" : conditionA, { @@ -4354,6 +4434,7 @@ export class PositionController extends Controller { typeCommand: string | null; posType?: string | null; posLevel?: string | null; + profileId?: string | null; isAll: boolean; isBlank: boolean; }, @@ -4395,9 +4476,13 @@ export class PositionController extends Controller { posLevel: posLevel?.id, }; } else if (body.typeCommand == "APPOINT") { - conditionA = "posType.posTypeRank > :posTypeRank"; + // เดิม : กรองเฉพาะ posTypeRank ที่สูงกว่า + // conditionA = "posType.posTypeRank > :posTypeRank"; + // ใหม่ : กรองเฉพาะ posType ที่สูงหรือต่ำกว่าก็ได้ + conditionA = "positions.posTypeId != :currentPosType"; params = { - posTypeRank: posType?.posTypeRank ?? 0, + // posTypeRank: posType?.posTypeRank ?? 0, + currentPosType: posType?.id, }; } else if (body.typeCommand == "SLIP") { conditionA = "positions.posTypeId LIKE :posType AND posLevel.posLevelRank > :posLevelRank"; @@ -4461,8 +4546,13 @@ export class PositionController extends Controller { typeCondition.current_holderId = IsNull(); } + if (body.typeCommand === "MOVE" && body.profileId && !body.isBlank) { + typeCondition.current_holderId = Not(body.profileId); + } + const [posMaster, total] = await AppDataSource.getRepository(PosMaster) .createQueryBuilder("posMaster") + .leftJoinAndSelect("posMaster.orgRevision", "orgRevision") .leftJoinAndSelect("posMaster.orgRoot", "orgRoot") .leftJoinAndSelect("posMaster.orgChild1", "orgChild1") .leftJoinAndSelect("posMaster.orgChild2", "orgChild2") @@ -4475,6 +4565,8 @@ export class PositionController extends Controller { .leftJoinAndSelect("positions.posLevel", "posLevel") .leftJoinAndSelect("positions.posExecutive", "posExecutive") .andWhere("posMaster.next_holderId IS NULL") + .andWhere("orgRevision.orgRevisionIsCurrent = true") + .andWhere("orgRevision.orgRevisionIsDraft = false") .andWhere( new Brackets((qb) => { qb.andWhere(typeCondition) @@ -5274,11 +5366,11 @@ export class PositionController extends Controller { let checkChildConditions: any = {}; let keywordAsInt: any; let searchShortName = "1=1"; - let searchShortName0 = `CONCAT(orgRoot.orgRootShortName," ",COALESCE(posMaster.posMasterNoPrefix, ""),posMaster.posMasterNo,COALESCE(posMaster.posMasterNoSuffix, ""))`; - let searchShortName1 = `CONCAT(orgChild1.orgChild1ShortName," ",COALESCE(posMaster.posMasterNoPrefix, ""),posMaster.posMasterNo,COALESCE(posMaster.posMasterNoSuffix, ""))`; - let searchShortName2 = `CONCAT(orgChild2.orgChild2ShortName," ",COALESCE(posMaster.posMasterNoPrefix, ""),posMaster.posMasterNo,COALESCE(posMaster.posMasterNoSuffix, ""))`; - let searchShortName3 = `CONCAT(orgChild3.orgChild3ShortName," ",COALESCE(posMaster.posMasterNoPrefix, ""),posMaster.posMasterNo,COALESCE(posMaster.posMasterNoSuffix, ""))`; - let searchShortName4 = `CONCAT(orgChild4.orgChild4ShortName," ",COALESCE(posMaster.posMasterNoPrefix, ""),posMaster.posMasterNo,COALESCE(posMaster.posMasterNoSuffix, ""))`; + let searchShortName0 = `CONCAT_WS(' ',orgRoot.orgRootShortName,NULLIF(posMaster.posMasterNoPrefix,''),posMaster.posMasterNo,NULLIF(posMaster.posMasterNoSuffix,''))`; + let searchShortName1 = `CONCAT_WS(' ',orgChild1.orgChild1ShortName,NULLIF(posMaster.posMasterNoPrefix,''),posMaster.posMasterNo,NULLIF(posMaster.posMasterNoSuffix,''))`; + let searchShortName2 = `CONCAT_WS(' ',orgChild2.orgChild2ShortName,NULLIF(posMaster.posMasterNoPrefix,''),posMaster.posMasterNo,NULLIF(posMaster.posMasterNoSuffix,''))`; + let searchShortName3 = `CONCAT_WS(' ',orgChild3.orgChild3ShortName,NULLIF(posMaster.posMasterNoPrefix,''),posMaster.posMasterNo,NULLIF(posMaster.posMasterNoSuffix,''))`; + let searchShortName4 = `CONCAT_WS(' ',orgChild4.orgChild4ShortName,NULLIF(posMaster.posMasterNoPrefix,''),posMaster.posMasterNo,NULLIF(posMaster.posMasterNoSuffix,''))`; let _data = await new permission().PermissionOrgList(request, "SYS_POS_CONDITION"); const orgDna = await new permission().checkDna(request, request.user.sub); let level: any = resolveNodeLevel(orgDna); @@ -5320,7 +5412,7 @@ export class PositionController extends Controller { // checkChildConditions = { // orgChild1Id: IsNull(), // }; - // searchShortName = `CONCAT(orgRoot.orgRootShortName," ",COALESCE(posMaster.posMasterNoPrefix, ""),posMaster.posMasterNo,COALESCE(posMaster.posMasterNoSuffix, "")) like '%${body.keyword}%'`; + // searchShortName = `CONCAT_WS(' ',orgRoot.orgRootShortName,NULLIF(posMaster.posMasterNoPrefix,''),posMaster.posMasterNo,NULLIF(posMaster.posMasterNoSuffix,'')) like '%${body.keyword}%'`; // } else { // } } else if (body.type === 1) { @@ -5331,7 +5423,7 @@ export class PositionController extends Controller { // checkChildConditions = { // orgChild2Id: IsNull(), // }; - // searchShortName = `CONCAT(orgChild1.orgChild1ShortName," ",COALESCE(posMaster.posMasterNoPrefix, ""),posMaster.posMasterNo,COALESCE(posMaster.posMasterNoSuffix, "")) like '%${body.keyword}%'`; + // searchShortName = `CONCAT_WS(' ',orgChild1.orgChild1ShortName,NULLIF(posMaster.posMasterNoPrefix,''),posMaster.posMasterNo,NULLIF(posMaster.posMasterNoSuffix,'')) like '%${body.keyword}%'`; // } else { // } } else if (body.type === 2) { @@ -5342,7 +5434,7 @@ export class PositionController extends Controller { // checkChildConditions = { // orgChild3Id: IsNull(), // }; - // searchShortName = `CONCAT(orgChild2.orgChild2ShortName," ",COALESCE(posMaster.posMasterNoPrefix, ""),posMaster.posMasterNo,COALESCE(posMaster.posMasterNoSuffix, "")) like '%${body.keyword}%'`; + // searchShortName = `CONCAT_WS(' ',orgChild2.orgChild2ShortName,NULLIF(posMaster.posMasterNoPrefix,''),posMaster.posMasterNo,NULLIF(posMaster.posMasterNoSuffix,'')) like '%${body.keyword}%'`; // } else { // } } else if (body.type === 3) { @@ -5353,14 +5445,14 @@ export class PositionController extends Controller { // checkChildConditions = { // orgChild4Id: IsNull(), // }; - // searchShortName = `CONCAT(orgChild3.orgChild3ShortName," ",COALESCE(posMaster.posMasterNoPrefix, ""),posMaster.posMasterNo,COALESCE(posMaster.posMasterNoSuffix, "")) like '%${body.keyword}%'`; + // searchShortName = `CONCAT_WS(' ',orgChild3.orgChild3ShortName,NULLIF(posMaster.posMasterNoPrefix,''),posMaster.posMasterNo,NULLIF(posMaster.posMasterNoSuffix,'')) like '%${body.keyword}%'`; // } else { // } } else if (body.type === 4) { typeCondition = { ...(cannotViewChild4PosMaster ? { orgChild4Id: null } : { orgChild4Id: body.id }), }; - searchShortName = `CONCAT(orgChild4.orgChild4ShortName," ",COALESCE(posMaster.posMasterNoPrefix, ""),posMaster.posMasterNo,COALESCE(posMaster.posMasterNoSuffix, "")) like '%${body.keyword}%'`; + searchShortName = `CONCAT_WS(' ',orgChild4.orgChild4ShortName,NULLIF(posMaster.posMasterNoPrefix,''),posMaster.posMasterNo,NULLIF(posMaster.posMasterNoSuffix,'')) like '%${body.keyword}%'`; } let findPosition: any; let masterId = new Array(); @@ -5397,10 +5489,8 @@ export class PositionController extends Controller { select: ["posMasterId"], }); masterId = masterId.concat(findPosition.map((position: any) => position.posMasterId)); - keywordAsInt = body.keyword == null ? null : parseInt(body.keyword, 10); - if (isNaN(keywordAsInt)) { - keywordAsInt = "P@ssw0rd!z"; - } + const numericMatch = body.keyword == null ? null : body.keyword.match(/\d+/); + keywordAsInt = numericMatch ? parseInt(numericMatch[0], 10) : null; masterId = [...new Set(masterId)]; } @@ -5427,7 +5517,7 @@ export class PositionController extends Controller { ...(body.keyword && (masterId.length > 0 ? { id: In(masterId) } - : { posMasterNo: Like(`%${body.keyword}%`) })), + : /^\d+$/.test(body.keyword) ? { posMasterNo: keywordAsInt } : { posMasterNo: Like(`%${body.keyword}%`) })), ...(!body.isAll && { isCondition: true }), }, ]; diff --git a/src/controllers/ProfileChangeNameController.ts b/src/controllers/ProfileChangeNameController.ts index 77cff634..22f147b8 100644 --- a/src/controllers/ProfileChangeNameController.ts +++ b/src/controllers/ProfileChangeNameController.ts @@ -25,6 +25,7 @@ import { } from "../entities/ProfileChangeName"; import { updateName } from "../keycloak"; import permission from "../interfaces/permission"; +import { updateHolderProfileHistory } from "../services/PositionService"; import { setLogDataDiff } from "../interfaces/utils"; @Route("api/v1/org/profile/changeName") @Tags("ProfileChangeName") @@ -110,8 +111,12 @@ export class ProfileChangeNameController extends Controller { setLogDataDiff(req, { before, after: history }); profile.firstName = body.firstName ?? profile.firstName; profile.lastName = body.lastName ?? profile.lastName; - profile.prefix = body.prefix ?? profile.prefix; - profile.prefixMain = profile.rank ?? profile.prefix; + // profile.prefix = body.prefix ?? profile.prefix; //old + profile.rank = body.rank ?? profile.rank; + // profile.prefixMain = profile.rank ?? profile.prefix; // old + profile.prefixMain = body.prefix ?? profile.prefix; + profile.prefix = body.rank && body.rank.length > 0 ? body.rank : body.prefix ?? profile.prefix; + await this.profileRepository.save(profile, { data: req }); setLogDataDiff(req, { before, after: profile }); @@ -127,6 +132,9 @@ export class ProfileChangeNameController extends Controller { } } + // บันทึกประวัติคนครองตำแหน่ง (ถ้า profile นี้ครองตำแหน่งอยู่) + await updateHolderProfileHistory(profile.id, req); + return new HttpSuccess(data.id); } @@ -179,8 +187,11 @@ export class ProfileChangeNameController extends Controller { if (profile && chkLastRecord.id === record.id) { profile.firstName = body.firstName ?? profile.firstName; profile.lastName = body.lastName ?? profile.lastName; - profile.prefix = body.prefix ?? profile.prefix; - profile.prefixMain = profile.rank ?? profile.prefix; + // profile.prefix = body.prefix ?? profile.prefix; //old + profile.rank = body.rank ?? profile.rank; + // profile.prefixMain = profile.rank ?? profile.prefix; // old + profile.prefixMain = body.prefix ?? profile.prefix; + profile.prefix = body.rank && body.rank.length > 0 ? body.rank : body.prefix ?? profile.prefix; await this.profileRepository.save(profile, { data: req }); setLogDataDiff(req, { before: before_profile, after: profile }); } diff --git a/src/controllers/ProfileChangeNameEmployeeController.ts b/src/controllers/ProfileChangeNameEmployeeController.ts index c2df9c8c..a632e718 100644 --- a/src/controllers/ProfileChangeNameEmployeeController.ts +++ b/src/controllers/ProfileChangeNameEmployeeController.ts @@ -24,6 +24,7 @@ import { } from "../entities/ProfileChangeName"; import { ProfileEmployee } from "../entities/ProfileEmployee"; import permission from "../interfaces/permission"; +import { updateHolderProfileHistory } from "../services/PositionService"; import { updateName } from "../keycloak"; import { setLogDataDiff } from "../interfaces/utils"; @Route("api/v1/org/profile-employee/changeName") @@ -116,8 +117,11 @@ export class ProfileChangeNameEmployeeController extends Controller { profile.firstName = body.firstName ?? profile.firstName; profile.lastName = body.lastName ?? profile.lastName; - profile.prefix = body.prefix ?? profile.prefix; - profile.prefixMain = profile.rank ?? profile.prefix; + // profile.prefix = body.prefix ?? profile.prefix; //old + profile.rank = body.rank ?? profile.rank; + // profile.prefixMain = profile.rank ?? profile.prefix; // old + profile.prefixMain = body.prefix ?? profile.prefix; + profile.prefix = body.rank && body.rank.length > 0 ? body.rank : body.prefix ?? profile.prefix; await this.profileEmployeeRepo.save(profile, { data: req }); setLogDataDiff(req, { before, after: profile }); @@ -133,6 +137,9 @@ export class ProfileChangeNameEmployeeController extends Controller { } } + // บันทึกประวัติคนครองตำแหน่ง (ถ้า profile นี้ครองตำแหน่งอยู่) + await updateHolderProfileHistory(profile.id, req, "EMPLOYEE"); + return new HttpSuccess(data.id); } @@ -186,8 +193,11 @@ export class ProfileChangeNameEmployeeController extends Controller { if (profile && chkLastRecord.id === record.id) { profile.firstName = body.firstName ?? profile.firstName; profile.lastName = body.lastName ?? profile.lastName; - profile.prefix = body.prefix ?? profile.prefix; - profile.prefixMain = profile.rank ?? profile.prefix; + // profile.prefix = body.prefix ?? profile.prefix; //old + profile.rank = body.rank ?? profile.rank; + // profile.prefixMain = profile.rank ?? profile.prefix; // old + profile.prefixMain = body.prefix ?? profile.prefix; + profile.prefix = body.rank && body.rank.length > 0 ? body.rank : body.prefix ?? profile.prefix; await this.profileEmployeeRepo.save(profile); } diff --git a/src/controllers/ProfileChangeNameEmployeeTempController.ts b/src/controllers/ProfileChangeNameEmployeeTempController.ts index 92e1e2f3..86c6b352 100644 --- a/src/controllers/ProfileChangeNameEmployeeTempController.ts +++ b/src/controllers/ProfileChangeNameEmployeeTempController.ts @@ -108,8 +108,11 @@ export class ProfileChangeNameEmployeeTempController extends Controller { profile.firstName = body.firstName ?? profile.firstName; profile.lastName = body.lastName ?? profile.lastName; - profile.prefix = body.prefix ?? profile.prefix; - profile.prefixMain = profile.rank ?? profile.prefix; + // profile.prefix = body.prefix ?? profile.prefix; //old + profile.rank = body.rank ?? profile.rank; + // profile.prefixMain = profile.rank ?? profile.prefix; // old + profile.prefixMain = body.prefix ?? profile.prefix; + profile.prefix = body.rank && body.rank.length > 0 ? body.rank : body.prefix ?? profile.prefix; await this.profileEmployeeRepo.save(profile, { data: req }); setLogDataDiff(req, { before, after: profile }); @@ -178,8 +181,11 @@ export class ProfileChangeNameEmployeeTempController extends Controller { if (profile && chkLastRecord.id === record.id) { profile.firstName = body.firstName ?? profile.firstName; profile.lastName = body.lastName ?? profile.lastName; - profile.prefix = body.prefix ?? profile.prefix; - profile.prefixMain = profile.rank ?? profile.prefix; + // profile.prefix = body.prefix ?? profile.prefix; //old + profile.rank = body.rank ?? profile.rank; + // profile.prefixMain = profile.rank ?? profile.prefix; // old + profile.prefixMain = body.prefix ?? profile.prefix; + profile.prefix = body.rank && body.rank.length > 0 ? body.rank : body.prefix ?? profile.prefix; await this.profileEmployeeRepo.save(profile); } diff --git a/src/controllers/ProfileController.ts b/src/controllers/ProfileController.ts index 3c514b6b..c6f1b68c 100644 --- a/src/controllers/ProfileController.ts +++ b/src/controllers/ProfileController.ts @@ -93,6 +93,7 @@ import { CreatePosMasterHistoryOfficer, getTopDegrees, getPosMasterPositions } f import { ProfileLeaveService } from "../services/ProfileLeaveService"; // import { PostRetireToExprofile } from "./ExRetirementController"; import { getPosNumCodeSit } from "../services/CommandService"; +import { updateHolderProfileHistory } from "../services/PositionService"; @Route("api/v1/org/profile") @Tags("Profile") @Security("bearerAuth") @@ -407,8 +408,8 @@ export class ProfileController extends Controller { ? Extension.ToThaiNumber(Extension.ToThaiNumber(salary_raw[0].positionExecutive)) : "", org: `${salary_raw.length > 0 && salary_raw[0].orgChild4 && salary_raw[0].orgChild4 != "-" - ? Extension.ToThaiNumber(Extension.ToThaiNumber(salary_raw[0].orgChild4)) + " " - : "" + ? Extension.ToThaiNumber(Extension.ToThaiNumber(salary_raw[0].orgChild4)) + " " + : "" }${salary_raw.length > 0 && salary_raw[0].orgChild3 && salary_raw[0].orgChild3 != "-" ? Extension.ToThaiNumber(Extension.ToThaiNumber(salary_raw[0].orgChild3)) + " " : "" @@ -5774,36 +5775,34 @@ export class ProfileController extends Controller { } if (body.citizenId) { - const citizenIdDigits = body.citizenId.toString().split("").map(Number); - const cal = - citizenIdDigits[0] * 13 + - citizenIdDigits[1] * 12 + - citizenIdDigits[2] * 11 + - citizenIdDigits[3] * 10 + - citizenIdDigits[4] * 9 + - citizenIdDigits[5] * 8 + - citizenIdDigits[6] * 7 + - citizenIdDigits[7] * 6 + - citizenIdDigits[8] * 5 + - citizenIdDigits[9] * 4 + - citizenIdDigits[10] * 3 + - citizenIdDigits[11] * 2; - const calStp2 = cal % 11; - const chkDigit = (11 - calStp2) % 10; - - if (citizenIdDigits[12] !== chkDigit) { - throw new HttpError(HttpStatus.NOT_FOUND, "ข้อมูลรหัสบัตรประจำตัวประชาชนไม่ถูกต้อง"); - } + Extension.CheckCitizen(body.citizenId); } const record = await this.profileRepo.findOneBy({ id }); const before = structuredClone(record); + // เช็คว่ามี profileHistory ของ profile นี้หรือไม่ + const historyCount = await this.profileHistoryRepo.count({ + where: { profileId: id }, + }); + + // ถ้าไม่มีเลย ให้บันทึกข้อมูลเริ่มต้น (ก่อน update) ลงไปก่อน + if (historyCount === 0) { + await this.profileHistoryRepo.save( + Object.assign(new ProfileHistory(), { + ...before, + birthDateOld: before?.birthDate, + profileId: id, + id: undefined, + }), + ); + } if (!record) throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูลโปรไฟล์นี้"); Object.assign(record, body); record.dateRetireLaw = calculateRetireLaw(record.birthDate); record.prefixMain = record.prefix; - record.prefix = record.rank && record.rank.length > 0 ? record.rank : record.prefixMain; + // record.prefix = record.rank && record.rank.length > 0 ? record.rank : record.prefixMain; + record.prefix = record.rank && record.rank.length > 0 ? record.rank : record.prefix; record.createdUserId = request.user.sub; record.createdFullName = request.user.name; record.createdAt = new Date(); @@ -5833,6 +5832,9 @@ export class ProfileController extends Controller { } } + // บันทึกประวัติคนครองตำแหน่ง (ถ้า profile นี้ครองตำแหน่งอยู่) + await updateHolderProfileHistory(record.id, request); + return new HttpSuccess(); } @@ -6025,12 +6027,12 @@ export class ProfileController extends Controller { queryLike = "profile.position LIKE :keyword"; } else if (searchField == "posNo") { queryLike = ` - CASE - WHEN current_holders.orgChild4Id IS NOT NULL THEN CONCAT(orgChild4.orgChild4ShortName, " ", current_holders.posMasterNo) - WHEN current_holders.orgChild3Id IS NOT NULL THEN CONCAT(orgChild3.orgChild3ShortName, " ", current_holders.posMasterNo) - WHEN current_holders.orgChild2Id IS NOT NULL THEN CONCAT(orgChild2.orgChild2ShortName, " ", current_holders.posMasterNo) - WHEN current_holders.orgChild1Id IS NOT NULL THEN CONCAT(orgChild1.orgChild1ShortName, " ", current_holders.posMasterNo) - ELSE CONCAT(orgRoot.orgRootShortName, " ", current_holders.posMasterNo) + CASE + WHEN current_holders.orgChild4Id IS NOT NULL THEN CONCAT_WS(' ', orgChild4.orgChild4ShortName, NULLIF(current_holders.posMasterNoPrefix,''), current_holders.posMasterNo, NULLIF(current_holders.posMasterNoSuffix,'')) + WHEN current_holders.orgChild3Id IS NOT NULL THEN CONCAT_WS(' ', orgChild3.orgChild3ShortName, NULLIF(current_holders.posMasterNoPrefix,''), current_holders.posMasterNo, NULLIF(current_holders.posMasterNoSuffix,'')) + WHEN current_holders.orgChild2Id IS NOT NULL THEN CONCAT_WS(' ', orgChild2.orgChild2ShortName, NULLIF(current_holders.posMasterNoPrefix,''), current_holders.posMasterNo, NULLIF(current_holders.posMasterNoSuffix,'')) + WHEN current_holders.orgChild1Id IS NOT NULL THEN CONCAT_WS(' ', orgChild1.orgChild1ShortName, NULLIF(current_holders.posMasterNoPrefix,''), current_holders.posMasterNo, NULLIF(current_holders.posMasterNoSuffix,'')) + ELSE CONCAT_WS(' ', orgRoot.orgRootShortName, NULLIF(current_holders.posMasterNoPrefix,''), current_holders.posMasterNo, NULLIF(current_holders.posMasterNoSuffix,'')) END LIKE :keyword `; } @@ -6300,7 +6302,7 @@ export class ProfileController extends Controller { @Query() sortBy: string = "profile.dateLeave", @Query() sort: "ASC" | "DESC" = "ASC", ) { - let _data = await new permission().PermissionOrgList(request, "SYS_REGISTRY_OFFICER"); + let _data = await new permission().PermissionOrgList(request, "SYS_REGISTRY_RETIRE_OFFICER"); const { data, total } = await this.profileLeaveService.getLeaveOfficer(request, { page, @@ -6615,12 +6617,12 @@ export class ProfileController extends Controller { queryLike = "profile.position LIKE :keyword"; } else if (searchField == "posNo") { queryLike = ` - CASE - WHEN current_holders.orgChild4Id IS NOT NULL THEN CONCAT(orgChild4.orgChild4ShortName, " ", current_holders.posMasterNo) - WHEN current_holders.orgChild3Id IS NOT NULL THEN CONCAT(orgChild3.orgChild3ShortName, " ", current_holders.posMasterNo) - WHEN current_holders.orgChild2Id IS NOT NULL THEN CONCAT(orgChild2.orgChild2ShortName, " ", current_holders.posMasterNo) - WHEN current_holders.orgChild1Id IS NOT NULL THEN CONCAT(orgChild1.orgChild1ShortName, " ", current_holders.posMasterNo) - ELSE CONCAT(orgRoot.orgRootShortName, " ", current_holders.posMasterNo) + CASE + WHEN current_holders.orgChild4Id IS NOT NULL THEN CONCAT_WS(' ', orgChild4.orgChild4ShortName, NULLIF(current_holders.posMasterNoPrefix,''), current_holders.posMasterNo, NULLIF(current_holders.posMasterNoSuffix,'')) + WHEN current_holders.orgChild3Id IS NOT NULL THEN CONCAT_WS(' ', orgChild3.orgChild3ShortName, NULLIF(current_holders.posMasterNoPrefix,''), current_holders.posMasterNo, NULLIF(current_holders.posMasterNoSuffix,'')) + WHEN current_holders.orgChild2Id IS NOT NULL THEN CONCAT_WS(' ', orgChild2.orgChild2ShortName, NULLIF(current_holders.posMasterNoPrefix,''), current_holders.posMasterNo, NULLIF(current_holders.posMasterNoSuffix,'')) + WHEN current_holders.orgChild1Id IS NOT NULL THEN CONCAT_WS(' ', orgChild1.orgChild1ShortName, NULLIF(current_holders.posMasterNoPrefix,''), current_holders.posMasterNo, NULLIF(current_holders.posMasterNoSuffix,'')) + ELSE CONCAT_WS(' ', orgRoot.orgRootShortName, NULLIF(current_holders.posMasterNoPrefix,''), current_holders.posMasterNo, NULLIF(current_holders.posMasterNoSuffix,'')) END LIKE :keyword `; } @@ -6803,18 +6805,19 @@ export class ProfileController extends Controller { .filter(Boolean) .join("\n"); + const numPart = holder ? [holder.posMasterNoPrefix, holder.posMasterNo, holder.posMasterNoSuffix].filter((p) => p !== null && p !== undefined && p !== '').join(' ') : ''; const shortName = !holder ? null : holder.orgChild4 != null - ? `${holder.orgChild4.orgChild4ShortName} ${holder.posMasterNo}` + ? `${holder.orgChild4.orgChild4ShortName} ${numPart}` : holder.orgChild3 != null - ? `${holder.orgChild3.orgChild3ShortName} ${holder.posMasterNo}` + ? `${holder.orgChild3.orgChild3ShortName} ${numPart}` : holder.orgChild2 != null - ? `${holder.orgChild2.orgChild2ShortName} ${holder.posMasterNo}` + ? `${holder.orgChild2.orgChild2ShortName} ${numPart}` : holder.orgChild1 != null - ? `${holder.orgChild1.orgChild1ShortName} ${holder.posMasterNo}` + ? `${holder.orgChild1.orgChild1ShortName} ${numPart}` : holder.orgRoot != null - ? `${holder.orgRoot.orgRootShortName} ${holder.posMasterNo}` + ? `${holder.orgRoot.orgRootShortName} ${numPart}` : null; return { @@ -7008,12 +7011,12 @@ export class ProfileController extends Controller { queryLike = "profile.position LIKE :keyword"; } else if (searchField == "posNo") { queryLike = ` - CASE - WHEN current_holders.orgChild4Id IS NOT NULL THEN CONCAT(orgChild4.orgChild4ShortName, " ", current_holders.posMasterNo) - WHEN current_holders.orgChild3Id IS NOT NULL THEN CONCAT(orgChild3.orgChild3ShortName, " ", current_holders.posMasterNo) - WHEN current_holders.orgChild2Id IS NOT NULL THEN CONCAT(orgChild2.orgChild2ShortName, " ", current_holders.posMasterNo) - WHEN current_holders.orgChild1Id IS NOT NULL THEN CONCAT(orgChild1.orgChild1ShortName, " ", current_holders.posMasterNo) - ELSE CONCAT(orgRoot.orgRootShortName, " ", current_holders.posMasterNo) + CASE + WHEN current_holders.orgChild4Id IS NOT NULL THEN CONCAT_WS(' ', orgChild4.orgChild4ShortName, NULLIF(current_holders.posMasterNoPrefix,''), current_holders.posMasterNo, NULLIF(current_holders.posMasterNoSuffix,'')) + WHEN current_holders.orgChild3Id IS NOT NULL THEN CONCAT_WS(' ', orgChild3.orgChild3ShortName, NULLIF(current_holders.posMasterNoPrefix,''), current_holders.posMasterNo, NULLIF(current_holders.posMasterNoSuffix,'')) + WHEN current_holders.orgChild2Id IS NOT NULL THEN CONCAT_WS(' ', orgChild2.orgChild2ShortName, NULLIF(current_holders.posMasterNoPrefix,''), current_holders.posMasterNo, NULLIF(current_holders.posMasterNoSuffix,'')) + WHEN current_holders.orgChild1Id IS NOT NULL THEN CONCAT_WS(' ', orgChild1.orgChild1ShortName, NULLIF(current_holders.posMasterNoPrefix,''), current_holders.posMasterNo, NULLIF(current_holders.posMasterNoSuffix,'')) + ELSE CONCAT_WS(' ', orgRoot.orgRootShortName, NULLIF(current_holders.posMasterNoPrefix,''), current_holders.posMasterNo, NULLIF(current_holders.posMasterNoSuffix,'')) END LIKE :keyword `; } @@ -7190,7 +7193,7 @@ export class ProfileController extends Controller { .filter(Boolean) .join("\n"); - const numPart = holder ? `${holder.posMasterNoPrefix ?? ''}${holder.posMasterNo ?? ''}${holder.posMasterNoSuffix ?? ''}` : ''; + const numPart = holder ? [holder.posMasterNoPrefix, holder.posMasterNo, holder.posMasterNoSuffix].filter((p) => p !== null && p !== undefined && p !== '').join(' ') : ''; const shortName = !holder ? null @@ -7949,40 +7952,38 @@ export class ProfileController extends Controller { privacyUser: profile.privacyUser, privacyMgt: profile.privacyMgt, isDeputy: root?.isDeputy ?? false, - // root?.orgRootShortName && posMaster?.posMasterNo - // ? `${root?.orgRootShortName} ${posMaster?.posMasterNo}` - // : "", }; + const _numPart = posMaster ? [posMaster.posMasterNoPrefix, posMaster.posMasterNo, posMaster.posMasterNoSuffix].filter((p) => p !== null && p !== undefined && p !== '').join(' ') : ''; if (_profile.child4Id != null) { _profile.node = 4; _profile.nodeId = _profile.child4Id; _profile.nodeDnaId = _profile.child4DnaId; _profile.nodeShortName = _profile.child4ShortName; - _profile.posNo = `${_profile.child4ShortName} ${_profile.posMasterNo}`; + _profile.posNo = `${_profile.child4ShortName} ${_numPart}`; } else if (_profile.child3Id != null) { _profile.node = 3; _profile.nodeId = _profile.child3Id; _profile.nodeDnaId = _profile.child3DnaId; _profile.nodeShortName = _profile.child3ShortName; - _profile.posNo = `${_profile.child3ShortName} ${_profile.posMasterNo}`; + _profile.posNo = `${_profile.child3ShortName} ${_numPart}`; } else if (_profile.child2Id != null) { _profile.node = 2; _profile.nodeId = _profile.child2Id; _profile.nodeDnaId = _profile.child2DnaId; _profile.nodeShortName = _profile.child2ShortName; - _profile.posNo = `${_profile.child2ShortName} ${_profile.posMasterNo}`; + _profile.posNo = `${_profile.child2ShortName} ${_numPart}`; } else if (_profile.child1Id != null) { _profile.node = 1; _profile.nodeId = _profile.child1Id; _profile.nodeDnaId = _profile.child1DnaId; _profile.nodeShortName = _profile.child1ShortName; - _profile.posNo = `${_profile.child1ShortName} ${_profile.posMasterNo}`; + _profile.posNo = `${_profile.child1ShortName} ${_numPart}`; } else if (_profile.rootId != null) { _profile.node = 0; _profile.nodeId = _profile.rootId; _profile.nodeDnaId = _profile.rootDnaId; _profile.nodeShortName = _profile.rootShortName; - _profile.posNo = `${_profile.rootShortName} ${_profile.posMasterNo}`; + _profile.posNo = `${_profile.rootShortName} ${_numPart}`; } return new HttpSuccess(_profile); } @@ -8122,41 +8123,39 @@ export class ProfileController extends Controller { privacyUser: profile.privacyUser, privacyMgt: profile.privacyMgt, isDeputy: root?.isDeputy ?? false, - // root?.orgRootShortName && posMaster?.posMasterNo - // ? `${root?.orgRootShortName} ${posMaster?.posMasterNo}` - // : "", }; + const _numPart = posMaster ? [posMaster.posMasterNoPrefix, posMaster.posMasterNo, posMaster.posMasterNoSuffix].filter((p) => p !== null && p !== undefined && p !== '').join(' ') : ''; if (_profile.child4Id != null) { _profile.node = 4; _profile.nodeId = _profile.child4Id; _profile.nodeDnaId = _profile.child4DnaId; _profile.nodeShortName = _profile.child4ShortName; - _profile.posNo = `${_profile.child4ShortName} ${posMaster?.posMasterNo}`; + _profile.posNo = `${_profile.child4ShortName} ${_numPart}`; } else if (_profile.child3Id != null) { _profile.node = 3; _profile.nodeId = _profile.child3Id; _profile.nodeDnaId = _profile.child3DnaId; _profile.nodeShortName = _profile.child3ShortName; - _profile.posNo = `${_profile.child3ShortName} ${posMaster?.posMasterNo}`; + _profile.posNo = `${_profile.child3ShortName} ${_numPart}`; } else if (_profile.child2Id != null) { _profile.node = 2; _profile.nodeId = _profile.child2Id; _profile.nodeDnaId = _profile.child2DnaId; _profile.nodeShortName = _profile.child2ShortName; - _profile.posNo = `${_profile.child2ShortName} ${posMaster?.posMasterNo}`; + _profile.posNo = `${_profile.child2ShortName} ${_numPart}`; } else if (_profile.child1Id != null) { _profile.node = 1; _profile.nodeId = _profile.child1Id; _profile.nodeDnaId = _profile.child1DnaId; _profile.nodeShortName = _profile.child1ShortName; - _profile.posNo = `${_profile.child1ShortName} ${posMaster?.posMasterNo}`; + _profile.posNo = `${_profile.child1ShortName} ${_numPart}`; } else if (_profile.rootId != null) { _profile.node = 0; _profile.nodeId = _profile.rootId; _profile.nodeDnaId = _profile.rootDnaId; _profile.nodeShortName = _profile.rootShortName; - _profile.posNo = `${_profile.rootShortName} ${posMaster?.posMasterNo}`; + _profile.posNo = `${_profile.rootShortName} ${_numPart}`; } return new HttpSuccess(_profile); } @@ -8796,32 +8795,21 @@ export class ProfileController extends Controller { posMasterId: posMaster?.id, }, }); + const holder = profile.current_holders.find((x) => x.orgRevisionId == orgRevisionPublish.id); + const numPart = holder ? [holder.posMasterNoPrefix, holder.posMasterNo, holder.posMasterNoSuffix].filter((p) => p !== null && p !== undefined && p !== '').join(' ') : ''; const shortName = - profile.current_holders.length == 0 + holder == null ? null - : profile.current_holders.find((x) => x.orgRevisionId == orgRevisionPublish.id) != null && - profile.current_holders.find((x) => x.orgRevisionId == orgRevisionPublish.id) - ?.orgChild4 != null - ? `${profile.current_holders.find((x) => x.orgRevisionId == orgRevisionPublish.id)?.orgChild4.orgChild4ShortName} ${profile.current_holders.find((x) => x.orgRevisionId == orgRevisionPublish.id)?.posMasterNo}` - : profile.current_holders.find((x) => x.orgRevisionId == orgRevisionPublish.id) != null && - profile.current_holders.find((x) => x.orgRevisionId == orgRevisionPublish.id) - ?.orgChild3 != null - ? `${profile.current_holders.find((x) => x.orgRevisionId == orgRevisionPublish.id)?.orgChild3.orgChild3ShortName} ${profile.current_holders.find((x) => x.orgRevisionId == orgRevisionPublish.id)?.posMasterNo}` - : profile.current_holders.find((x) => x.orgRevisionId == orgRevisionPublish.id) != - null && - profile.current_holders.find((x) => x.orgRevisionId == orgRevisionPublish.id) - ?.orgChild2 != null - ? `${profile.current_holders.find((x) => x.orgRevisionId == orgRevisionPublish.id)?.orgChild2.orgChild2ShortName} ${profile.current_holders.find((x) => x.orgRevisionId == orgRevisionPublish.id)?.posMasterNo}` - : profile.current_holders.find((x) => x.orgRevisionId == orgRevisionPublish.id) != - null && - profile.current_holders.find((x) => x.orgRevisionId == orgRevisionPublish.id) - ?.orgChild1 != null - ? `${profile.current_holders.find((x) => x.orgRevisionId == orgRevisionPublish.id)?.orgChild1.orgChild1ShortName} ${profile.current_holders.find((x) => x.orgRevisionId == orgRevisionPublish.id)?.posMasterNo}` - : profile.current_holders.find((x) => x.orgRevisionId == orgRevisionPublish.id) != - null && - profile.current_holders.find((x) => x.orgRevisionId == orgRevisionPublish.id) - ?.orgRoot != null - ? `${profile.current_holders.find((x) => x.orgRevisionId == orgRevisionPublish.id)?.orgRoot.orgRootShortName} ${profile.current_holders.find((x) => x.orgRevisionId == orgRevisionPublish.id)?.posMasterNo}` + : holder.orgChild4 != null + ? `${holder.orgChild4.orgChild4ShortName} ${numPart}` + : holder.orgChild3 != null + ? `${holder.orgChild3.orgChild3ShortName} ${numPart}` + : holder.orgChild2 != null + ? `${holder.orgChild2.orgChild2ShortName} ${numPart}` + : holder.orgChild1 != null + ? `${holder.orgChild1.orgChild1ShortName} ${numPart}` + : holder.orgRoot != null + ? `${holder.orgRoot.orgRootShortName} ${numPart}` : null; // const posMasterActs = await this.posMasterActRepository.find({ // relations: [ @@ -9183,26 +9171,32 @@ export class ProfileController extends Controller { profile.avatar && profile.avatarName ? `${profile.avatar}/${profile.avatarName}` : null, }; + const _numPart = posMaster ? [posMaster.posMasterNoPrefix, posMaster.posMasterNo, posMaster.posMasterNoSuffix].filter((p) => p !== null && p !== undefined && p !== '').join(' ') : ''; if (_profile.child4Id != null) { _profile.node = 4; _profile.nodeId = _profile.child4Id; _profile.nodeShortName = _profile.child4ShortName; + _profile.posNo = `${_profile.child4ShortName} ${_numPart}`; } else if (_profile.child3Id != null) { _profile.node = 3; _profile.nodeId = _profile.child3Id; _profile.nodeShortName = _profile.child3ShortName; + _profile.posNo = `${_profile.child3ShortName} ${_numPart}`; } else if (_profile.child2Id != null) { _profile.node = 2; _profile.nodeId = _profile.child2Id; _profile.nodeShortName = _profile.child2ShortName; + _profile.posNo = `${_profile.child2ShortName} ${_numPart}`; } else if (_profile.child1Id != null) { _profile.node = 1; _profile.nodeId = _profile.child1Id; _profile.nodeShortName = _profile.child1ShortName; + _profile.posNo = `${_profile.child1ShortName} ${_numPart}`; } else if (_profile.rootId != null) { _profile.node = 0; _profile.nodeId = _profile.rootId; _profile.nodeShortName = _profile.rootShortName; + _profile.posNo = `${_profile.rootShortName} ${_numPart}`; } return new HttpSuccess(_profile); } @@ -9332,26 +9326,32 @@ export class ProfileController extends Controller { : "-", }; + const _numPart = posMaster ? [posMaster.posMasterNoPrefix, posMaster.posMasterNo, posMaster.posMasterNoSuffix].filter((p) => p !== null && p !== undefined && p !== '').join(' ') : ''; if (_profile.child4Id != null) { _profile.node = 4; _profile.nodeId = _profile.child4Id; _profile.nodeShortName = _profile.child4ShortName; + _profile.posNo = `${_profile.child4ShortName} ${_numPart}`; } else if (_profile.child3Id != null) { _profile.node = 3; _profile.nodeId = _profile.child3Id; _profile.nodeShortName = _profile.child3ShortName; + _profile.posNo = `${_profile.child3ShortName} ${_numPart}`; } else if (_profile.child2Id != null) { _profile.node = 2; _profile.nodeId = _profile.child2Id; _profile.nodeShortName = _profile.child2ShortName; + _profile.posNo = `${_profile.child2ShortName} ${_numPart}`; } else if (_profile.child1Id != null) { _profile.node = 1; _profile.nodeId = _profile.child1Id; _profile.nodeShortName = _profile.child1ShortName; + _profile.posNo = `${_profile.child1ShortName} ${_numPart}`; } else if (_profile.rootId != null) { _profile.node = 0; _profile.nodeId = _profile.rootId; _profile.nodeShortName = _profile.rootShortName; + _profile.posNo = `${_profile.rootShortName} ${_numPart}`; } return new HttpSuccess(_profile); } @@ -9532,38 +9532,28 @@ export class ProfileController extends Controller { const mapDataProfile = await Promise.all( findProfile.map(async (item: Profile) => { const fullName = `${item.prefix}${item.firstName} ${item.lastName}`; - const shortName = - item.current_holders.length == 0 - ? null - : item.current_holders.find((x) => x.orgRevisionId == findRevision.id) != null && - item.current_holders.find((x) => x.orgRevisionId == findRevision.id)?.orgChild4 != - null - ? `${item.current_holders.find((x) => x.orgRevisionId == findRevision.id)?.orgChild4.orgChild4ShortName} ${item.current_holders.find((x) => x.orgRevisionId == findRevision.id)?.posMasterNo}` - : item.current_holders.find((x) => x.orgRevisionId == findRevision.id) != null && - item.current_holders.find((x) => x.orgRevisionId == findRevision.id)?.orgChild3 != - null - ? `${item.current_holders.find((x) => x.orgRevisionId == findRevision.id)?.orgChild3.orgChild3ShortName} ${item.current_holders.find((x) => x.orgRevisionId == findRevision.id)?.posMasterNo}` - : item.current_holders.find((x) => x.orgRevisionId == findRevision.id) != null && - item.current_holders.find((x) => x.orgRevisionId == findRevision.id) - ?.orgChild2 != null - ? `${item.current_holders.find((x) => x.orgRevisionId == findRevision.id)?.orgChild2.orgChild2ShortName} ${item.current_holders.find((x) => x.orgRevisionId == findRevision.id)?.posMasterNo}` - : item.current_holders.find((x) => x.orgRevisionId == findRevision.id) != null && - item.current_holders.find((x) => x.orgRevisionId == findRevision.id) - ?.orgChild1 != null - ? `${item.current_holders.find((x) => x.orgRevisionId == findRevision.id)?.orgChild1.orgChild1ShortName} ${item.current_holders.find((x) => x.orgRevisionId == findRevision.id)?.posMasterNo}` - : item.current_holders.find((x) => x.orgRevisionId == findRevision.id) != - null && - item.current_holders.find((x) => x.orgRevisionId == findRevision.id) - ?.orgRoot != null - ? `${item.current_holders.find((x) => x.orgRevisionId == findRevision.id)?.orgRoot.orgRootShortName} ${item.current_holders.find((x) => x.orgRevisionId == findRevision.id)?.posMasterNo}` - : null; + const holder = item.current_holders?.find((x) => x.orgRevisionId == findRevision.id); + const _numPart = holder ? [holder.posMasterNoPrefix, holder.posMasterNo, holder.posMasterNoSuffix].filter((p) => p !== null && p !== undefined && p !== '').join(' ') : ''; + const shortName = !holder + ? null + : holder.orgChild4 != null + ? `${holder.orgChild4.orgChild4ShortName} ${_numPart}` + : holder.orgChild3 != null + ? `${holder.orgChild3.orgChild3ShortName} ${_numPart}` + : holder.orgChild2 != null + ? `${holder.orgChild2.orgChild2ShortName} ${_numPart}` + : holder.orgChild1 != null + ? `${holder.orgChild1.orgChild1ShortName} ${_numPart}` + : holder.orgRoot != null + ? `${holder.orgRoot.orgRootShortName} ${_numPart}` + : null; const root = item.current_holders.length == 0 || - (item.current_holders.find((x) => x.orgRevisionId == findRevision.id) != null && - item.current_holders.find((x) => x.orgRevisionId == findRevision.id)?.orgRoot == null) + (holder != null && + holder?.orgRoot == null) ? null - : item.current_holders.find((x) => x.orgRevisionId == findRevision.id)?.orgRoot; + : holder?.orgRoot; const rootHolder = item.current_holders?.find( (x) => x.orgRevisionId == findRevision.id, diff --git a/src/controllers/ProfileEmployeeController.ts b/src/controllers/ProfileEmployeeController.ts index c0b36961..61efbdb7 100644 --- a/src/controllers/ProfileEmployeeController.ts +++ b/src/controllers/ProfileEmployeeController.ts @@ -84,6 +84,7 @@ import { ProfileDuty } from "../entities/ProfileDuty"; import { CreatePosMasterHistoryEmployee, getTopDegrees } from "../services/PositionService"; import { ProfileLeaveService } from "../services/ProfileLeaveService"; import { CommandCode } from "../entities/CommandCode"; +import { updateHolderProfileHistory } from "../services/PositionService"; @Route("api/v1/org/profile-employee") @Tags("ProfileEmployee") @Security("bearerAuth") @@ -196,7 +197,7 @@ export class ProfileEmployeeController extends Controller { }, }); ImgUrl = response_.data.downloadUrl; - } catch {} + } catch { } } const province = await this.provinceRepository.findOneBy({ id: profile.registrationProvinceId, @@ -208,36 +209,36 @@ export class ProfileEmployeeController extends Controller { const root = profile.current_holders == null || - profile.current_holders.length == 0 || - profile.current_holders.find((x) => x.orgRevisionId == orgRevision?.id) == null + profile.current_holders.length == 0 || + profile.current_holders.find((x) => x.orgRevisionId == orgRevision?.id) == null ? null : profile.current_holders.find((x) => x.orgRevisionId == orgRevision?.id)?.orgRoot; const child1 = profile.current_holders == null || - profile.current_holders.length == 0 || - profile.current_holders.find((x) => x.orgRevisionId == orgRevision?.id) == null + profile.current_holders.length == 0 || + profile.current_holders.find((x) => x.orgRevisionId == orgRevision?.id) == null ? null : profile.current_holders.find((x) => x.orgRevisionId == orgRevision?.id)?.orgChild1; const child2 = profile.current_holders == null || - profile.current_holders.length == 0 || - profile.current_holders.find((x) => x.orgRevisionId == orgRevision?.id) == null + profile.current_holders.length == 0 || + profile.current_holders.find((x) => x.orgRevisionId == orgRevision?.id) == null ? null : profile.current_holders.find((x) => x.orgRevisionId == orgRevision?.id)?.orgChild2; const child3 = profile.current_holders == null || - profile.current_holders.length == 0 || - profile.current_holders.find((x) => x.orgRevisionId == orgRevision?.id) == null + profile.current_holders.length == 0 || + profile.current_holders.find((x) => x.orgRevisionId == orgRevision?.id) == null ? null : profile.current_holders.find((x) => x.orgRevisionId == orgRevision?.id)?.orgChild3; const child4 = profile.current_holders == null || - profile.current_holders.length == 0 || - profile.current_holders.find((x) => x.orgRevisionId == orgRevision?.id) == null + profile.current_holders.length == 0 || + profile.current_holders.find((x) => x.orgRevisionId == orgRevision?.id) == null ? null : profile.current_holders.find((x) => x.orgRevisionId == orgRevision?.id)?.orgChild4; @@ -286,38 +287,38 @@ export class ProfileEmployeeController extends Controller { const salarys = salary_raw.length > 1 ? salary_raw.slice(1).map((item) => ({ - date: item.commandDateAffect - ? Extension.ToThaiNumber(Extension.ToThaiShortDate(item.commandDateAffect)) - : null, - position: Extension.ToThaiNumber( - Extension.ToThaiNumber( - `${item.positionName != null ? item.positionName : "-"} ${item.positionType == null ? item.positionCee ?? "" : (item.positionType == "อำนวยการ" || item.positionType == "บริหาร" ? item.positionType : "") + item.positionLevel}`, - ), + date: item.commandDateAffect + ? Extension.ToThaiNumber(Extension.ToThaiShortDate(item.commandDateAffect)) + : null, + position: Extension.ToThaiNumber( + Extension.ToThaiNumber( + `${item.positionName != null ? item.positionName : "-"} ${item.positionType == null ? item.positionCee ?? "" : (item.positionType == "อำนวยการ" || item.positionType == "บริหาร" ? item.positionType : "") + item.positionLevel}`, ), - posNo: item.posNo != null ? Extension.ToThaiNumber(item.posNo) : "", - orgRoot: item.orgRoot != null ? Extension.ToThaiNumber(item.orgRoot) : "", - orgChild1: item.orgChild1 != null ? Extension.ToThaiNumber(item.orgChild1) : "", - orgChild2: item.orgChild2 != null ? Extension.ToThaiNumber(item.orgChild2) : "", - orgChild3: item.orgChild3 != null ? Extension.ToThaiNumber(item.orgChild3) : "", - orgChild4: item.orgChild4 != null ? Extension.ToThaiNumber(item.orgChild4) : "", - positionCee: item.positionCee != null ? Extension.ToThaiNumber(item.positionCee) : "", - positionExecutive: - item.positionExecutive != null ? Extension.ToThaiNumber(item.positionExecutive) : "", - })) + ), + posNo: item.posNo != null ? Extension.ToThaiNumber(item.posNo) : "", + orgRoot: item.orgRoot != null ? Extension.ToThaiNumber(item.orgRoot) : "", + orgChild1: item.orgChild1 != null ? Extension.ToThaiNumber(item.orgChild1) : "", + orgChild2: item.orgChild2 != null ? Extension.ToThaiNumber(item.orgChild2) : "", + orgChild3: item.orgChild3 != null ? Extension.ToThaiNumber(item.orgChild3) : "", + orgChild4: item.orgChild4 != null ? Extension.ToThaiNumber(item.orgChild4) : "", + positionCee: item.positionCee != null ? Extension.ToThaiNumber(item.positionCee) : "", + positionExecutive: + item.positionExecutive != null ? Extension.ToThaiNumber(item.positionExecutive) : "", + })) : [ - { - date: "-", - position: "-", - posNo: "-", - orgRoot: null, - orgChild1: null, - orgChild2: null, - orgChild3: null, - orgChild4: null, - positionCee: null, - positionExecutive: null, - }, - ]; + { + date: "-", + position: "-", + posNo: "-", + orgRoot: null, + orgChild1: null, + orgChild2: null, + orgChild3: null, + orgChild4: null, + positionCee: null, + positionExecutive: null, + }, + ]; const educations = await this.profileEducationRepo.find({ select: [ @@ -335,20 +336,20 @@ export class ProfileEmployeeController extends Controller { const Education = educations && educations.length > 0 ? educations.map((item) => ({ - institute: item.institute ? item.institute : "-", - date: - item.startDate && item.endDate - ? `${Extension.ToThaiNumber(Extension.ToThaiFullDate2(item.startDate))} - ${Extension.ToThaiNumber(Extension.ToThaiFullDate2(item.endDate))}` - : "-", - degree: item.degree && item.field ? `${item.degree} ${item.field}` : "-", - })) + institute: item.institute ? item.institute : "-", + date: + item.startDate && item.endDate + ? `${Extension.ToThaiNumber(Extension.ToThaiFullDate2(item.startDate))} - ${Extension.ToThaiNumber(Extension.ToThaiFullDate2(item.endDate))}` + : "-", + degree: item.degree && item.field ? `${item.degree} ${item.field}` : "-", + })) : [ - { - institute: "-", - date: "-", - degree: "-", - }, - ]; + { + institute: "-", + date: "-", + degree: "-", + }, + ]; const mapData = { // Id: profile.id, @@ -386,10 +387,10 @@ export class ProfileEmployeeController extends Controller { position: salary_raw.length > 0 && salary_raw[0].positionName != null ? Extension.ToThaiNumber( - Extension.ToThaiNumber( - `${salary_raw[0].positionName != null ? salary_raw[0].positionName : "-"} ${salary_raw[0].positionType == null ? salary_raw[0].positionCee ?? "" : (salary_raw[0].positionType == "อำนวยการ" || salary_raw[0].positionType == "บริหาร" ? salary_raw[0].positionType : "") + salary_raw[0].positionLevel}`, - ), - ) + Extension.ToThaiNumber( + `${salary_raw[0].positionName != null ? salary_raw[0].positionName : "-"} ${salary_raw[0].positionType == null ? salary_raw[0].positionCee ?? "" : (salary_raw[0].positionType == "อำนวยการ" || salary_raw[0].positionType == "บริหาร" ? salary_raw[0].positionType : "") + salary_raw[0].positionLevel}`, + ), + ) : "", positionCee: salary_raw.length > 0 && salary_raw[0].positionCee != null @@ -399,27 +400,22 @@ export class ProfileEmployeeController extends Controller { salary_raw.length > 0 && salary_raw[0].positionExecutive != null ? Extension.ToThaiNumber(Extension.ToThaiNumber(salary_raw[0].positionExecutive)) : "", - org: `${ - salary_raw.length > 0 && salary_raw[0].orgChild4 && salary_raw[0].orgChild4 != "-" + org: `${salary_raw.length > 0 && salary_raw[0].orgChild4 && salary_raw[0].orgChild4 != "-" ? Extension.ToThaiNumber(Extension.ToThaiNumber(salary_raw[0].orgChild4)) + " " : "" - }${ - salary_raw.length > 0 && salary_raw[0].orgChild3 && salary_raw[0].orgChild3 != "-" + }${salary_raw.length > 0 && salary_raw[0].orgChild3 && salary_raw[0].orgChild3 != "-" ? Extension.ToThaiNumber(Extension.ToThaiNumber(salary_raw[0].orgChild3)) + " " : "" - }${ - salary_raw.length > 0 && salary_raw[0].orgChild2 && salary_raw[0].orgChild2 != "-" + }${salary_raw.length > 0 && salary_raw[0].orgChild2 && salary_raw[0].orgChild2 != "-" ? Extension.ToThaiNumber(Extension.ToThaiNumber(salary_raw[0].orgChild2)) + " " : "" - }${ - salary_raw.length > 0 && salary_raw[0].orgChild1 && salary_raw[0].orgChild1 != "-" + }${salary_raw.length > 0 && salary_raw[0].orgChild1 && salary_raw[0].orgChild1 != "-" ? Extension.ToThaiNumber(Extension.ToThaiNumber(salary_raw[0].orgChild1)) + " " : "" - }${ - salary_raw.length > 0 && salary_raw[0].orgRoot && salary_raw[0].orgRoot != "-" + }${salary_raw.length > 0 && salary_raw[0].orgRoot && salary_raw[0].orgRoot != "-" ? Extension.ToThaiNumber(Extension.ToThaiNumber(salary_raw[0].orgRoot)) : "" - }`, + }`, ocFullPath: (_child4 == null ? "" : _child4 + "\n") + (_child3 == null ? "" : _child3 + "\n") + @@ -488,7 +484,7 @@ export class ProfileEmployeeController extends Controller { }, }); _ImgUrl[i] = response_.data.downloadUrl; - } catch {} + } catch { } } }), ); @@ -502,7 +498,7 @@ export class ProfileEmployeeController extends Controller { }, }); ImgUrl = response_.data.downloadUrl; - } catch {} + } catch { } } const profileOc = await this.profileRepo.findOne({ relations: [ @@ -541,36 +537,36 @@ export class ProfileEmployeeController extends Controller { const root = profileOc.current_holders == null || - profileOc.current_holders.length == 0 || - profileOc.current_holders.find((x) => x.orgRevisionId == orgRevision?.id) == null + profileOc.current_holders.length == 0 || + profileOc.current_holders.find((x) => x.orgRevisionId == orgRevision?.id) == null ? null : profileOc.current_holders.find((x) => x.orgRevisionId == orgRevision?.id)?.orgRoot; const child1 = profileOc.current_holders == null || - profileOc.current_holders.length == 0 || - profileOc.current_holders.find((x) => x.orgRevisionId == orgRevision?.id) == null + profileOc.current_holders.length == 0 || + profileOc.current_holders.find((x) => x.orgRevisionId == orgRevision?.id) == null ? null : profileOc.current_holders.find((x) => x.orgRevisionId == orgRevision?.id)?.orgChild1; const child2 = profileOc.current_holders == null || - profileOc.current_holders.length == 0 || - profileOc.current_holders.find((x) => x.orgRevisionId == orgRevision?.id) == null + profileOc.current_holders.length == 0 || + profileOc.current_holders.find((x) => x.orgRevisionId == orgRevision?.id) == null ? null : profileOc.current_holders.find((x) => x.orgRevisionId == orgRevision?.id)?.orgChild2; const child3 = profileOc.current_holders == null || - profileOc.current_holders.length == 0 || - profileOc.current_holders.find((x) => x.orgRevisionId == orgRevision?.id) == null + profileOc.current_holders.length == 0 || + profileOc.current_holders.find((x) => x.orgRevisionId == orgRevision?.id) == null ? null : profileOc.current_holders.find((x) => x.orgRevisionId == orgRevision?.id)?.orgChild3; const child4 = profileOc.current_holders == null || - profileOc.current_holders.length == 0 || - profileOc.current_holders.find((x) => x.orgRevisionId == orgRevision?.id) == null + profileOc.current_holders.length == 0 || + profileOc.current_holders.find((x) => x.orgRevisionId == orgRevision?.id) == null ? null : profileOc.current_holders.find((x) => x.orgRevisionId == orgRevision?.id)?.orgChild4; @@ -589,19 +585,19 @@ export class ProfileEmployeeController extends Controller { const certs = cert_raw.length > 0 ? cert_raw.slice(-2).map((item) => ({ - CertificateType: item.certificateType ?? null, - Issuer: item.issuer ?? null, - CertificateNo: Extension.ToThaiNumber(item.certificateNo) ?? null, - IssueDate: Extension.ToThaiNumber(Extension.ToThaiFullDate2(item.issueDate)) ?? null, - })) + CertificateType: item.certificateType ?? null, + Issuer: item.issuer ?? null, + CertificateNo: Extension.ToThaiNumber(item.certificateNo) ?? null, + IssueDate: Extension.ToThaiNumber(Extension.ToThaiFullDate2(item.issueDate)) ?? null, + })) : [ - { - CertificateType: "-", - Issuer: "-", - CertificateNo: "-", - IssueDate: "-", - }, - ]; + { + CertificateType: "-", + Issuer: "-", + CertificateNo: "-", + IssueDate: "-", + }, + ]; const training_raw = await this.trainingRepository.find({ select: ["startDate", "endDate", "place", "department", "isDeleted"], where: { profileEmployeeId: id, isDeleted: false }, @@ -610,34 +606,34 @@ export class ProfileEmployeeController extends Controller { const trainings = training_raw.length > 0 ? training_raw.slice(-2).map((item) => ({ - Institute: item.department ?? "", - Start: - item.startDate == null - ? "" - : Extension.ToThaiNumber(Extension.ToThaiFullDate2(item.startDate)), - End: - item.endDate == null - ? "" - : Extension.ToThaiNumber(Extension.ToThaiFullDate2(item.endDate)), - Date: - item.startDate && item.endDate - ? `${Extension.ToThaiNumber(Extension.ToThaiFullDate2(item.startDate))} - ${Extension.ToThaiNumber(Extension.ToThaiFullDate2(item.endDate))}` - : "", - Level: "", - Degree: item.name, - Field: "", - })) + Institute: item.department ?? "", + Start: + item.startDate == null + ? "" + : Extension.ToThaiNumber(Extension.ToThaiFullDate2(item.startDate)), + End: + item.endDate == null + ? "" + : Extension.ToThaiNumber(Extension.ToThaiFullDate2(item.endDate)), + Date: + item.startDate && item.endDate + ? `${Extension.ToThaiNumber(Extension.ToThaiFullDate2(item.startDate))} - ${Extension.ToThaiNumber(Extension.ToThaiFullDate2(item.endDate))}` + : "", + Level: "", + Degree: item.name, + Field: "", + })) : [ - { - Institute: "-", - Start: "-", - End: "-", - Date: "-", - Level: "-", - Degree: "-", - Field: "-", - }, - ]; + { + Institute: "-", + Start: "-", + End: "-", + Date: "-", + Level: "-", + Degree: "-", + Field: "-", + }, + ]; const discipline_raw = await this.disciplineRepository.find({ select: ["refCommandDate", "refCommandNo", "detail", "isDeleted"], @@ -647,19 +643,19 @@ export class ProfileEmployeeController extends Controller { const disciplines = discipline_raw.length > 0 ? discipline_raw.slice(-2).map((item) => ({ - DisciplineYear: - Extension.ToThaiNumber(new Date(item.refCommandDate).getFullYear().toString()) ?? - null, - DisciplineDetail: item.detail ?? null, - RefNo: item.refCommandNo ? Extension.ToThaiNumber(item.refCommandNo) : null, - })) + DisciplineYear: + Extension.ToThaiNumber(new Date(item.refCommandDate).getFullYear().toString()) ?? + null, + DisciplineDetail: item.detail ?? null, + RefNo: item.refCommandNo ? Extension.ToThaiNumber(item.refCommandNo) : null, + })) : [ - { - DisciplineYear: "-", - DisciplineDetail: "-", - RefNo: "-", - }, - ]; + { + DisciplineYear: "-", + DisciplineDetail: "-", + RefNo: "-", + }, + ]; const education_raw = await this.profileEducationRepo.find({ select: [ @@ -678,34 +674,34 @@ export class ProfileEmployeeController extends Controller { const educations = education_raw.length > 0 ? education_raw.slice(-2).map((item) => ({ - Institute: item.institute, - Start: - item.startDate == null - ? "" - : Extension.ToThaiNumber(new Date(item.startDate).getFullYear().toString()), - End: - item.endDate == null - ? "" - : Extension.ToThaiNumber(new Date(item.endDate).getFullYear().toString()), - Date: - item.startDate && item.endDate - ? `${Extension.ToThaiNumber(Extension.ToThaiFullDate2(item.startDate))} - ${Extension.ToThaiNumber(Extension.ToThaiFullDate2(item.endDate))}` - : "", - Level: item.educationLevel ?? "", - Degree: item.degree ? `${item.degree} ${item.field ? item.field : ""}` : "", - Field: item.field ?? "-", - })) + Institute: item.institute, + Start: + item.startDate == null + ? "" + : Extension.ToThaiNumber(new Date(item.startDate).getFullYear().toString()), + End: + item.endDate == null + ? "" + : Extension.ToThaiNumber(new Date(item.endDate).getFullYear().toString()), + Date: + item.startDate && item.endDate + ? `${Extension.ToThaiNumber(Extension.ToThaiFullDate2(item.startDate))} - ${Extension.ToThaiNumber(Extension.ToThaiFullDate2(item.endDate))}` + : "", + Level: item.educationLevel ?? "", + Degree: item.degree ? `${item.degree} ${item.field ? item.field : ""}` : "", + Field: item.field ?? "-", + })) : [ - { - Institute: "-", - Start: "-", - End: "-", - Date: "-", - Level: "-", - Degree: "-", - Field: "-", - }, - ]; + { + Institute: "-", + Start: "-", + End: "-", + Date: "-", + Level: "-", + Degree: "-", + Field: "-", + }, + ]; const salary_raw = await this.salaryRepo.find({ select: [ "commandDateAffect", @@ -725,45 +721,45 @@ export class ProfileEmployeeController extends Controller { const salarys = salary_raw.length > 0 ? salary_raw.map((item) => ({ - SalaryDate: item.commandDateAffect - ? Extension.ToThaiNumber(Extension.ToThaiFullDate2(item.commandDateAffect)) - : null, - Position: item.positionName != null ? Extension.ToThaiNumber(item.positionName) : null, - PosNo: item.posNo != null ? Extension.ToThaiNumber(item.posNo) : null, - Salary: - item.amount != null ? Extension.ToThaiNumber(item.amount.toLocaleString()) : null, - Rank: item.positionLevel != null ? Extension.ToThaiNumber(item.positionLevel) : null, - RefAll: item.remark ? Extension.ToThaiNumber(item.remark) : null, - PositionLevel: - item.positionLevel != null ? Extension.ToThaiNumber(item.positionLevel) : null, - PositionType: item.positionType ?? null, - PositionAmount: - item.positionSalaryAmount == null - ? null - : Extension.ToThaiNumber(item.positionSalaryAmount.toLocaleString()), - FullName: `${profiles?.prefix}${profiles?.firstName} ${profiles?.lastName}`, - OcFullPath: - (_child4 == null ? "" : _child4 + "\n") + - (_child3 == null ? "" : _child3 + "\n") + - (_child2 == null ? "" : _child2 + "\n") + - (_child1 == null ? "" : _child1 + "\n") + - (_root == null ? "" : _root), - })) + SalaryDate: item.commandDateAffect + ? Extension.ToThaiNumber(Extension.ToThaiFullDate2(item.commandDateAffect)) + : null, + Position: item.positionName != null ? Extension.ToThaiNumber(item.positionName) : null, + PosNo: item.posNo != null ? Extension.ToThaiNumber(item.posNo) : null, + Salary: + item.amount != null ? Extension.ToThaiNumber(item.amount.toLocaleString()) : null, + Rank: item.positionLevel != null ? Extension.ToThaiNumber(item.positionLevel) : null, + RefAll: item.remark ? Extension.ToThaiNumber(item.remark) : null, + PositionLevel: + item.positionLevel != null ? Extension.ToThaiNumber(item.positionLevel) : null, + PositionType: item.positionType ?? null, + PositionAmount: + item.positionSalaryAmount == null + ? null + : Extension.ToThaiNumber(item.positionSalaryAmount.toLocaleString()), + FullName: `${profiles?.prefix}${profiles?.firstName} ${profiles?.lastName}`, + OcFullPath: + (_child4 == null ? "" : _child4 + "\n") + + (_child3 == null ? "" : _child3 + "\n") + + (_child2 == null ? "" : _child2 + "\n") + + (_child1 == null ? "" : _child1 + "\n") + + (_root == null ? "" : _root), + })) : [ - { - SalaryDate: "-", - Position: "-", - PosNo: "-", - Salary: "-", - Rank: "-", - RefAll: "-", - PositionLevel: "-", - PositionType: "-", - PositionAmount: "-", - FullName: "-", - OcFullPath: "-", - }, - ]; + { + SalaryDate: "-", + Position: "-", + PosNo: "-", + Salary: "-", + Rank: "-", + RefAll: "-", + PositionLevel: "-", + PositionType: "-", + PositionAmount: "-", + FullName: "-", + OcFullPath: "-", + }, + ]; const insignia_raw = await this.profileInsigniaRepo.find({ relations: { @@ -777,37 +773,37 @@ export class ProfileEmployeeController extends Controller { const insignias = insignia_raw.length > 0 ? insignia_raw.map((item) => ({ - ReceiveDate: item.receiveDate - ? Extension.ToThaiNumber(Extension.ToThaiFullDate2(item.receiveDate)) - : "", - InsigniaName: item.insignia.name, - InsigniaShortName: item.insignia.shortName, - InsigniaTypeName: item.insignia.insigniaType.name, - No: item.no ? Extension.ToThaiNumber(item.no) : "", - Issue: item.issue ? item.issue : "", - VolumeNo: item.volumeNo ? Extension.ToThaiNumber(item.volumeNo) : "", - Volume: item.volume ? Extension.ToThaiNumber(item.volume) : "", - Section: item.section ? Extension.ToThaiNumber(item.section) : "", - Page: item.page ? Extension.ToThaiNumber(item.page) : "", - RefCommandDate: item.refCommandDate - ? Extension.ToThaiNumber(Extension.ToThaiFullDate2(item.refCommandDate)) - : "", - })) + ReceiveDate: item.receiveDate + ? Extension.ToThaiNumber(Extension.ToThaiFullDate2(item.receiveDate)) + : "", + InsigniaName: item.insignia.name, + InsigniaShortName: item.insignia.shortName, + InsigniaTypeName: item.insignia.insigniaType.name, + No: item.no ? Extension.ToThaiNumber(item.no) : "", + Issue: item.issue ? item.issue : "", + VolumeNo: item.volumeNo ? Extension.ToThaiNumber(item.volumeNo) : "", + Volume: item.volume ? Extension.ToThaiNumber(item.volume) : "", + Section: item.section ? Extension.ToThaiNumber(item.section) : "", + Page: item.page ? Extension.ToThaiNumber(item.page) : "", + RefCommandDate: item.refCommandDate + ? Extension.ToThaiNumber(Extension.ToThaiFullDate2(item.refCommandDate)) + : "", + })) : [ - { - ReceiveDate: "-", - InsigniaName: "-", - InsigniaShortName: "-", - InsigniaTypeName: "-", - No: "-", - Issue: "-", - VolumeNo: "-", - Volume: "-", - Section: "-", - Page: "-", - RefCommandDate: "-", - }, - ]; + { + ReceiveDate: "-", + InsigniaName: "-", + InsigniaShortName: "-", + InsigniaTypeName: "-", + No: "-", + Issue: "-", + VolumeNo: "-", + Volume: "-", + Section: "-", + Page: "-", + RefCommandDate: "-", + }, + ]; const leave_raw = await this.profileLeaveRepository .createQueryBuilder("profileLeave") @@ -897,20 +893,20 @@ export class ProfileEmployeeController extends Controller { profiles.citizenId != null ? Extension.ToThaiNumber(profiles.citizenId.toString()) : "", fatherFullName: profileFamilyFather?.fatherPrefix || - profileFamilyFather?.fatherFirstName || - profileFamilyFather?.fatherLastName + profileFamilyFather?.fatherFirstName || + profileFamilyFather?.fatherLastName ? `${profileFamilyFather?.fatherPrefix ?? ""}${profileFamilyFather?.fatherFirstName ?? ""} ${profileFamilyFather?.fatherLastName ?? ""}`.trim() : null, motherFullName: profileFamilyMother?.motherPrefix || - profileFamilyMother?.motherFirstName || - profileFamilyMother?.motherLastName + profileFamilyMother?.motherFirstName || + profileFamilyMother?.motherLastName ? `${profileFamilyMother?.motherPrefix ?? ""}${profileFamilyMother?.motherFirstName ?? ""} ${profileFamilyMother?.motherLastName ?? ""}`.trim() : null, coupleFullName: profileFamilyCouple?.couplePrefix || - profileFamilyCouple?.coupleFirstName || - profileFamilyCouple?.coupleLastNameOld + profileFamilyCouple?.coupleFirstName || + profileFamilyCouple?.coupleLastNameOld ? `${profileFamilyCouple?.couplePrefix ?? ""}${profileFamilyCouple?.coupleFirstName ?? ""} ${profileFamilyCouple?.coupleLastName ?? ""}`.trim() : null, coupleLastNameOld: profileFamilyCouple?.coupleLastNameOld ?? null, @@ -1028,7 +1024,7 @@ export class ProfileEmployeeController extends Controller { }, }); _ImgUrl[i] = response_.data.downloadUrl; - } catch {} + } catch { } } }), ); @@ -1042,7 +1038,7 @@ export class ProfileEmployeeController extends Controller { }, }); ImgUrl = response_.data.downloadUrl; - } catch {} + } catch { } } const orgRevision = await this.orgRevisionRepo.findOne({ @@ -1076,36 +1072,36 @@ export class ProfileEmployeeController extends Controller { const root = profiles.current_holders == null || - profiles.current_holders.length == 0 || - profiles.current_holders.find((x) => x.orgRevisionId == orgRevision?.id) == null + profiles.current_holders.length == 0 || + profiles.current_holders.find((x) => x.orgRevisionId == orgRevision?.id) == null ? null : profiles.current_holders.find((x) => x.orgRevisionId == orgRevision?.id)?.orgRoot; const child1 = profiles.current_holders == null || - profiles.current_holders.length == 0 || - profiles.current_holders.find((x) => x.orgRevisionId == orgRevision?.id) == null + profiles.current_holders.length == 0 || + profiles.current_holders.find((x) => x.orgRevisionId == orgRevision?.id) == null ? null : profiles.current_holders.find((x) => x.orgRevisionId == orgRevision?.id)?.orgChild1; const child2 = profiles.current_holders == null || - profiles.current_holders.length == 0 || - profiles.current_holders.find((x) => x.orgRevisionId == orgRevision?.id) == null + profiles.current_holders.length == 0 || + profiles.current_holders.find((x) => x.orgRevisionId == orgRevision?.id) == null ? null : profiles.current_holders.find((x) => x.orgRevisionId == orgRevision?.id)?.orgChild2; const child3 = profiles.current_holders == null || - profiles.current_holders.length == 0 || - profiles.current_holders.find((x) => x.orgRevisionId == orgRevision?.id) == null + profiles.current_holders.length == 0 || + profiles.current_holders.find((x) => x.orgRevisionId == orgRevision?.id) == null ? null : profiles.current_holders.find((x) => x.orgRevisionId == orgRevision?.id)?.orgChild3; const child4 = profiles.current_holders == null || - profiles.current_holders.length == 0 || - profiles.current_holders.find((x) => x.orgRevisionId == orgRevision?.id) == null + profiles.current_holders.length == 0 || + profiles.current_holders.find((x) => x.orgRevisionId == orgRevision?.id) == null ? null : profiles.current_holders.find((x) => x.orgRevisionId == orgRevision?.id)?.orgChild4; @@ -1131,31 +1127,31 @@ export class ProfileEmployeeController extends Controller { const certs = cert_raw.length > 0 ? cert_raw.map((item) => ({ - certificateType: item.certificateType ?? null, - issuer: item.issuer ?? null, - certificateNo: item.certificateNo ? Extension.ToThaiNumber(item.certificateNo) : null, - detail: Extension.ToThaiNumber( - `${item.issuer ?? ""} ${item.certificateNo ?? ""}`.trim(), - ), - issueToExpireDate: item.issueDate - ? item.expireDate - ? Extension.ToThaiNumber( - `${Extension.ToThaiFullDate2(item.issueDate)} - ${Extension.ToThaiFullDate2(item.expireDate)}`, - ) - : Extension.ToThaiNumber(Extension.ToThaiFullDate2(item.issueDate)) - : item.expireDate - ? Extension.ToThaiNumber(Extension.ToThaiFullDate2(item.expireDate)) - : "", - })) + certificateType: item.certificateType ?? null, + issuer: item.issuer ?? null, + certificateNo: item.certificateNo ? Extension.ToThaiNumber(item.certificateNo) : null, + detail: Extension.ToThaiNumber( + `${item.issuer ?? ""} ${item.certificateNo ?? ""}`.trim(), + ), + issueToExpireDate: item.issueDate + ? item.expireDate + ? Extension.ToThaiNumber( + `${Extension.ToThaiFullDate2(item.issueDate)} - ${Extension.ToThaiFullDate2(item.expireDate)}`, + ) + : Extension.ToThaiNumber(Extension.ToThaiFullDate2(item.issueDate)) + : item.expireDate + ? Extension.ToThaiNumber(Extension.ToThaiFullDate2(item.expireDate)) + : "", + })) : [ - { - certificateType: "", - issuer: "", - certificateNo: "", - detail: "", - issueToExpireDate: "", - }, - ]; + { + certificateType: "", + issuer: "", + certificateNo: "", + detail: "", + issueToExpireDate: "", + }, + ]; const training_raw = await this.trainingRepository.find({ select: ["place", "department", "name", "duration", "isDeleted", "startDate", "endDate"], where: { profileEmployeeId: id, isDeleted: false }, @@ -1164,23 +1160,23 @@ export class ProfileEmployeeController extends Controller { const trainings = training_raw.length > 0 ? training_raw.map((item) => ({ - institute: item.department ?? "", - degree: item.name ? Extension.ToThaiNumber(item.name) : "", - place: item.place ? Extension.ToThaiNumber(item.place) : "", - duration: item.duration ? Extension.ToThaiNumber(item.duration) : "", - date: Extension.ToThaiNumber( - `${Extension.ToThaiFullDate2(item.startDate)} - ${Extension.ToThaiFullDate2(item.endDate)}`, - ), - })) + institute: item.department ?? "", + degree: item.name ? Extension.ToThaiNumber(item.name) : "", + place: item.place ? Extension.ToThaiNumber(item.place) : "", + duration: item.duration ? Extension.ToThaiNumber(item.duration) : "", + date: Extension.ToThaiNumber( + `${Extension.ToThaiFullDate2(item.startDate)} - ${Extension.ToThaiFullDate2(item.endDate)}`, + ), + })) : [ - { - institute: "", - degree: "", - place: "", - duration: "", - date: "", - }, - ]; + { + institute: "", + degree: "", + place: "", + duration: "", + date: "", + }, + ]; const discipline_raw = await this.disciplineRepository.find({ select: ["refCommandDate", "refCommandNo", "detail", "level", "isDeleted"], @@ -1190,21 +1186,21 @@ export class ProfileEmployeeController extends Controller { const disciplines = discipline_raw.length > 0 ? discipline_raw.map((item) => ({ - disciplineYear: item.refCommandDate - ? Extension.ToThaiNumber(Extension.ToThaiShortYear(new Date(item.refCommandDate))) - : null, - disciplineDetail: item.detail ?? null, - refNo: Extension.ToThaiNumber(item.refCommandNo) ?? null, - level: item.level ?? "", - })) + disciplineYear: item.refCommandDate + ? Extension.ToThaiNumber(Extension.ToThaiShortYear(new Date(item.refCommandDate))) + : null, + disciplineDetail: item.detail ?? null, + refNo: Extension.ToThaiNumber(item.refCommandNo) ?? null, + level: item.level ?? "", + })) : [ - { - disciplineYear: "", - disciplineDetail: "", - refNo: "", - level: "", - }, - ]; + { + disciplineYear: "", + disciplineDetail: "", + refNo: "", + level: "", + }, + ]; const education_raw = await this.profileEducationRepo .createQueryBuilder("education") @@ -1216,21 +1212,21 @@ export class ProfileEmployeeController extends Controller { const educations = education_raw.length > 0 ? education_raw.map((item) => ({ - institute: item.institute, - date: item.isDate - ? `${item.startDate ? Extension.ToThaiNumber(Extension.ToThaiFullDate2(item.startDate)) : ""} - ${item.endDate ? Extension.ToThaiNumber(Extension.ToThaiFullDate2(item.endDate)) : ""}` - : `${item.startDate ? Extension.ToThaiNumber(Extension.ToThaiShortYear(new Date(item.startDate))) : ""} - ${item.endDate ? Extension.ToThaiNumber(Extension.ToThaiShortYear(new Date(item.endDate))) : ""}`, - degree: `${item.degree ?? ""} ${item.field ?? ""}`.trim(), - level: item.educationLevel, - })) + institute: item.institute, + date: item.isDate + ? `${item.startDate ? Extension.ToThaiNumber(Extension.ToThaiFullDate2(item.startDate)) : ""} - ${item.endDate ? Extension.ToThaiNumber(Extension.ToThaiFullDate2(item.endDate)) : ""}` + : `${item.startDate ? Extension.ToThaiNumber(Extension.ToThaiShortYear(new Date(item.startDate))) : ""} - ${item.endDate ? Extension.ToThaiNumber(Extension.ToThaiShortYear(new Date(item.endDate))) : ""}`, + degree: `${item.degree ?? ""} ${item.field ?? ""}`.trim(), + level: item.educationLevel, + })) : [ - { - institute: "", - date: "", - degree: "", - level: "", - }, - ]; + { + institute: "", + date: "", + degree: "", + level: "", + }, + ]; const salary_raw = await this.salaryRepo.find({ select: [ "commandName", @@ -1258,58 +1254,58 @@ export class ProfileEmployeeController extends Controller { const salarys = salary_raw.length > 0 ? salary_raw.map((item) => ({ - commandName: item.commandName ?? "", - salaryDate: item.commandDateAffect - ? Extension.ToThaiNumber(Extension.ToThaiFullDate2(item.commandDateAffect)) + commandName: item.commandName ?? "", + salaryDate: item.commandDateAffect + ? Extension.ToThaiNumber(Extension.ToThaiFullDate2(item.commandDateAffect)) + : null, + position: item.positionName != null ? Extension.ToThaiNumber(item.positionName) : null, + posNo: + item.posNoAbb && item.posNo + ? Extension.ToThaiNumber(`${item.posNoAbb}${item.posNo}`) : null, - position: item.positionName != null ? Extension.ToThaiNumber(item.positionName) : null, - posNo: - item.posNoAbb && item.posNo - ? Extension.ToThaiNumber(`${item.posNoAbb}${item.posNo}`) - : null, - salary: - item.amount != null ? Extension.ToThaiNumber(item.amount.toLocaleString()) : null, - special: - item.amountSpecial != null - ? Extension.ToThaiNumber(item.amountSpecial.toLocaleString()) - : null, - rank: item.positionLevel != null ? Extension.ToThaiNumber(item.positionLevel) : null, - refAll: item.remark ? Extension.ToThaiNumber(item.remark) : null, - positionLevel: item.positionLevel - ? Extension.ToThaiNumber(item.positionLevel) - : item.positionCee - ? Extension.ToThaiNumber(item.positionCee) - : null, - positionType: item.positionType ?? null, - positionAmount: - item.positionSalaryAmount == null - ? null - : Extension.ToThaiNumber(item.positionSalaryAmount.toLocaleString()), - fullName: `${profiles?.prefix}${profiles?.firstName} ${profiles?.lastName}`, - ocFullPath: - (_child4 == null ? "" : _child4 + "\n") + - (_child3 == null ? "" : _child3 + "\n") + - (_child2 == null ? "" : _child2 + "\n") + - (_child1 == null ? "" : _child1 + "\n") + - (_root == null ? "" : _root), - })) + salary: + item.amount != null ? Extension.ToThaiNumber(item.amount.toLocaleString()) : null, + special: + item.amountSpecial != null + ? Extension.ToThaiNumber(item.amountSpecial.toLocaleString()) + : null, + rank: item.positionLevel != null ? Extension.ToThaiNumber(item.positionLevel) : null, + refAll: item.remark ? Extension.ToThaiNumber(item.remark) : null, + positionLevel: item.positionLevel + ? Extension.ToThaiNumber(item.positionLevel) + : item.positionCee + ? Extension.ToThaiNumber(item.positionCee) + : null, + positionType: item.positionType ?? null, + positionAmount: + item.positionSalaryAmount == null + ? null + : Extension.ToThaiNumber(item.positionSalaryAmount.toLocaleString()), + fullName: `${profiles?.prefix}${profiles?.firstName} ${profiles?.lastName}`, + ocFullPath: + (_child4 == null ? "" : _child4 + "\n") + + (_child3 == null ? "" : _child3 + "\n") + + (_child2 == null ? "" : _child2 + "\n") + + (_child1 == null ? "" : _child1 + "\n") + + (_root == null ? "" : _root), + })) : [ - { - commandName: "", - salaryDate: "", - position: "", - posNo: "", - salary: "", - special: "", - rank: "", - refAll: "", - positionLevel: "", - positionType: "", - positionAmount: "", - fullName: "", - ocFullPath: "", - }, - ]; + { + commandName: "", + salaryDate: "", + position: "", + posNo: "", + salary: "", + special: "", + rank: "", + refAll: "", + positionLevel: "", + positionType: "", + positionAmount: "", + fullName: "", + ocFullPath: "", + }, + ]; const insignia_raw = await this.profileInsigniaRepo.find({ select: [ @@ -1335,38 +1331,38 @@ export class ProfileEmployeeController extends Controller { const insignias = insignia_raw.length > 0 ? insignia_raw.map((item) => ({ - receiveDate: item.receiveDate - ? Extension.ToThaiNumber(Extension.ToThaiFullDate2(item.receiveDate)) - : "", - insigniaName: item.insignia?.name ?? "", - insigniaShortName: item.insignia?.shortName ?? "", - insigniaTypeName: item.insignia?.insigniaType?.name ?? "", - no: item.no ? Extension.ToThaiNumber(item.no) : "", - issue: item.issue ? Extension.ToThaiNumber(item.issue) : "", - volumeNo: item.volumeNo ? Extension.ToThaiNumber(item.volumeNo) : "", - volume: item.volume ? Extension.ToThaiNumber(item.volume) : "", - section: item.section ? Extension.ToThaiNumber(item.section) : "", - page: item.page ? Extension.ToThaiNumber(item.page) : "", - refCommandDate: item.refCommandDate - ? Extension.ToThaiNumber(Extension.ToThaiFullDate2(item.refCommandDate)) - : "", - note: item.note ? Extension.ToThaiNumber(item.note) : "", - })) + receiveDate: item.receiveDate + ? Extension.ToThaiNumber(Extension.ToThaiFullDate2(item.receiveDate)) + : "", + insigniaName: item.insignia?.name ?? "", + insigniaShortName: item.insignia?.shortName ?? "", + insigniaTypeName: item.insignia?.insigniaType?.name ?? "", + no: item.no ? Extension.ToThaiNumber(item.no) : "", + issue: item.issue ? Extension.ToThaiNumber(item.issue) : "", + volumeNo: item.volumeNo ? Extension.ToThaiNumber(item.volumeNo) : "", + volume: item.volume ? Extension.ToThaiNumber(item.volume) : "", + section: item.section ? Extension.ToThaiNumber(item.section) : "", + page: item.page ? Extension.ToThaiNumber(item.page) : "", + refCommandDate: item.refCommandDate + ? Extension.ToThaiNumber(Extension.ToThaiFullDate2(item.refCommandDate)) + : "", + note: item.note ? Extension.ToThaiNumber(item.note) : "", + })) : [ - { - receiveDate: "", - insigniaName: "", - insigniaShortName: "", - insigniaTypeName: "", - no: "", - issue: "", - volumeNo: "", - volume: "", - section: "", - page: "", - refCommandDate: "", - }, - ]; + { + receiveDate: "", + insigniaName: "", + insigniaShortName: "", + insigniaTypeName: "", + no: "", + issue: "", + volumeNo: "", + volume: "", + section: "", + page: "", + refCommandDate: "", + }, + ]; const leave_raw = await this.profileLeaveRepository .createQueryBuilder("profileLeave") @@ -1504,62 +1500,62 @@ export class ProfileEmployeeController extends Controller { const leaves2 = leave2_raw.length > 0 ? leave2_raw.map((item) => { - const leaveTypeCode = item.code ? item.code.trim().toUpperCase() : ""; + const leaveTypeCode = item.code ? item.code.trim().toUpperCase() : ""; - // ข้อที่ 1: LV-008 ให้ใช้ leaveSubTypeName (ประเภทย่อย) แทน name - const displayType = - leaveTypeCode === "LV-008" && item.leaveSubTypeName - ? item.leaveSubTypeName - : item.name || "-"; + // ข้อที่ 1: LV-008 ให้ใช้ leaveSubTypeName (ประเภทย่อย) แทน name + const displayType = + leaveTypeCode === "LV-008" && item.leaveSubTypeName + ? item.leaveSubTypeName + : item.name || "-"; - // ข้อที่ 2: แสดง reason ก่อนเสมอ ถ้ามี coupleDayLevelCountry ค่อยแสดงประเทศ - const displayReason = item.coupleDayLevelCountry - ? `${item.reason || ""} ลาไปประเทศ ${item.coupleDayLevelCountry}`.trim() - : item.reason || "-"; + // ข้อที่ 2: แสดง reason ก่อนเสมอ ถ้ามี coupleDayLevelCountry ค่อยแสดงประเทศ + const displayReason = item.coupleDayLevelCountry + ? `${item.reason || ""} ลาไปประเทศ ${item.coupleDayLevelCountry}`.trim() + : item.reason || "-"; - return { - date: - item.dateLeaveStart && item.dateLeaveEnd - ? Extension.ToThaiNumber(Extension.ToThaiFullDate2(item.dateLeaveStart)) + - " - " + - Extension.ToThaiNumber(Extension.ToThaiFullDate2(item.dateLeaveEnd)) - : "-", - type: displayType, - leaveDays: item.leaveDays ? Extension.ToThaiNumber(item.leaveDays.toString()) : "-", - reason: displayReason, - }; - }) + return { + date: + item.dateLeaveStart && item.dateLeaveEnd + ? Extension.ToThaiNumber(Extension.ToThaiFullDate2(item.dateLeaveStart)) + + " - " + + Extension.ToThaiNumber(Extension.ToThaiFullDate2(item.dateLeaveEnd)) + : "-", + type: displayType, + leaveDays: item.leaveDays ? Extension.ToThaiNumber(item.leaveDays.toString()) : "-", + reason: displayReason, + }; + }) : [ - { - date: "", - type: "", - leaveDays: "", - reason: "", - }, - ]; + { + date: "", + type: "", + leaveDays: "", + reason: "", + }, + ]; const children_raw = await this.profileChildrenRepository.find({ where: { profileEmployeeId: id, isDeleted: false }, }); const children = children_raw.length > 0 ? children_raw.map((item, index) => ({ - no: Extension.ToThaiNumber((index + 1).toString()), - childrenPrefix: item.childrenPrefix, - childrenFirstName: item.childrenFirstName, - childrenLastName: item.childrenLastName, - childrenFullName: `${item.childrenPrefix}${item.childrenFirstName} ${item.childrenLastName}`, - childrenLive: item.childrenLive == false ? "ถึงแก่กรรม" : "มีชีวิต", - })) + no: Extension.ToThaiNumber((index + 1).toString()), + childrenPrefix: item.childrenPrefix, + childrenFirstName: item.childrenFirstName, + childrenLastName: item.childrenLastName, + childrenFullName: `${item.childrenPrefix}${item.childrenFirstName} ${item.childrenLastName}`, + childrenLive: item.childrenLive == false ? "ถึงแก่กรรม" : "มีชีวิต", + })) : [ - { - no: "", - childrenPrefix: "", - childrenFirstName: "", - childrenLastName: "", - childrenFullName: "", - childrenLive: "", - }, - ]; + { + no: "", + childrenPrefix: "", + childrenFirstName: "", + childrenLastName: "", + childrenFullName: "", + childrenLive: "", + }, + ]; const changeName_raw = await this.changeNameRepository.find({ where: { profileEmployeeId: id, isDeleted: false }, order: { createdAt: "ASC" }, @@ -1567,23 +1563,23 @@ export class ProfileEmployeeController extends Controller { const changeName = changeName_raw.length > 0 ? changeName_raw.map((item) => ({ - createdAt: item.createdAt - ? Extension.ToThaiNumber(Extension.ToThaiFullDate2(item.createdAt)) - : null, - status: item.status, - prefix: item.prefix, - firstName: item.firstName, - lastName: item.lastName, - })) + createdAt: item.createdAt + ? Extension.ToThaiNumber(Extension.ToThaiFullDate2(item.createdAt)) + : null, + status: item.status, + prefix: item.prefix, + firstName: item.firstName, + lastName: item.lastName, + })) : [ - { - createdAt: "", - status: "", - prefix: "", - firstName: "", - lastName: "", - }, - ]; + { + createdAt: "", + status: "", + prefix: "", + firstName: "", + lastName: "", + }, + ]; const profileHistory = await this.profileHistoryRepo.find({ where: { profileEmployeeId: id }, @@ -1592,19 +1588,19 @@ export class ProfileEmployeeController extends Controller { const history = profileHistory.length > 0 ? profileHistory.map((item) => ({ - birthDateOld: item.birthDateOld - ? Extension.ToThaiNumber(Extension.ToThaiFullDate2(item.birthDateOld)) - : "", - birthDate: item.birthDate - ? Extension.ToThaiNumber(Extension.ToThaiFullDate2(item.birthDate)) - : "", - })) + birthDateOld: item.birthDateOld + ? Extension.ToThaiNumber(Extension.ToThaiFullDate2(item.birthDateOld)) + : "", + birthDate: item.birthDate + ? Extension.ToThaiNumber(Extension.ToThaiFullDate2(item.birthDate)) + : "", + })) : [ - { - birthDateOld: "", - birthDate: "", - }, - ]; + { + birthDateOld: "", + birthDate: "", + }, + ]; const position_raw = await this.salaryRepo.find({ where: [ @@ -1638,76 +1634,76 @@ export class ProfileEmployeeController extends Controller { const positionList = position_raw.length > 0 ? await Promise.all( - position_raw.map(async (item, idx, arr) => { - const isLast = idx === arr.length - 1; - if (isLast) { - _commandDateAffect = item.commandDateAffect - ? Extension.ToThaiNumber(Extension.ToThaiFullDate2(item.commandDateAffect)) - : ""; - } - const _code = item.commandCode ? Number(item.commandCode) : null; - if (_code != null) { - _commandName = await this.commandCodeRepository.findOne({ - select: { name: true, code: true }, - where: { code: _code }, - }); - } - const codeSitAbb = item.posNumCodeSitAbb ?? "-"; - const commandNo = - item.commandNo && item.commandYear - ? item.commandNo + - "/" + - (item.commandYear > 2500 ? item.commandYear : item.commandYear + 543) - : "-"; - const dateAffect = item.commandDateAffect - ? `${Extension.ToThaiFullDate2(item.commandDateAffect)}` + position_raw.map(async (item, idx, arr) => { + const isLast = idx === arr.length - 1; + if (isLast) { + _commandDateAffect = item.commandDateAffect + ? Extension.ToThaiNumber(Extension.ToThaiFullDate2(item.commandDateAffect)) + : ""; + } + const _code = item.commandCode ? Number(item.commandCode) : null; + if (_code != null) { + _commandName = await this.commandCodeRepository.findOne({ + select: { name: true, code: true }, + where: { code: _code }, + }); + } + const codeSitAbb = item.posNumCodeSitAbb ?? "-"; + const commandNo = + item.commandNo && item.commandYear + ? item.commandNo + + "/" + + (item.commandYear > 2500 ? item.commandYear : item.commandYear + 543) : "-"; - return { - commandName: - _commandName && _commandName.name ? _commandName.name : item.commandName, - commandDateAffect: item.commandDateAffect - ? Extension.ToThaiNumber(Extension.ToThaiFullDate2(item.commandDateAffect)) + const dateAffect = item.commandDateAffect + ? `${Extension.ToThaiFullDate2(item.commandDateAffect)}` + : "-"; + return { + commandName: + _commandName && _commandName.name ? _commandName.name : item.commandName, + commandDateAffect: item.commandDateAffect + ? Extension.ToThaiNumber(Extension.ToThaiFullDate2(item.commandDateAffect)) + : "", + commandDateSign: item.commandDateSign + ? Extension.ToThaiNumber(Extension.ToThaiFullDate2(item.commandDateSign)) + : "", + posNo: + item.posNoAbb && item.posNo + ? Extension.ToThaiNumber(`${item.posNoAbb} ${item.posNo}`) : "", - commandDateSign: item.commandDateSign - ? Extension.ToThaiNumber(Extension.ToThaiFullDate2(item.commandDateSign)) - : "", - posNo: - item.posNoAbb && item.posNo - ? Extension.ToThaiNumber(`${item.posNoAbb} ${item.posNo}`) - : "", - position: item.positionName, - posType: item.positionType, - posLevel: item.positionLevel - ? Extension.ToThaiNumber(item.positionLevel) - : item.positionCee - ? Extension.ToThaiNumber(item.positionCee) - : null, - amount: item.amount - ? Extension.ToThaiNumber(Number(item.amount).toLocaleString()) - : "", - positionSalaryAmount: item.positionSalaryAmount - ? Extension.ToThaiNumber(Number(item.positionSalaryAmount).toLocaleString()) - : "", - refDoc: Extension.ToThaiNumber( - `คำสั่ง ${codeSitAbb} ที่ ${commandNo} ลว. ${dateAffect}`, - ), - }; - }), - ) + position: item.positionName, + posType: item.positionType, + posLevel: item.positionLevel + ? Extension.ToThaiNumber(item.positionLevel) + : item.positionCee + ? Extension.ToThaiNumber(item.positionCee) + : null, + amount: item.amount + ? Extension.ToThaiNumber(Number(item.amount).toLocaleString()) + : "", + positionSalaryAmount: item.positionSalaryAmount + ? Extension.ToThaiNumber(Number(item.positionSalaryAmount).toLocaleString()) + : "", + refDoc: Extension.ToThaiNumber( + `คำสั่ง ${codeSitAbb} ที่ ${commandNo} ลว. ${dateAffect}`, + ), + }; + }), + ) : [ - { - commandName: "", - commandDateAffect: "", - commandDateSign: "", - posNo: "", - position: "", - posType: "", - posLevel: "", - amount: "", - positionSalaryAmount: "", - refDoc: "", - }, - ]; + { + commandName: "", + commandDateAffect: "", + commandDateSign: "", + posNo: "", + position: "", + posType: "", + posLevel: "", + amount: "", + positionSalaryAmount: "", + refDoc: "", + }, + ]; // ลูกจ้างยังไม่มีรักษาการและช่วยราชการ // const actposition_raw = await this.profileActpositionRepo.find({ @@ -1791,36 +1787,36 @@ export class ProfileEmployeeController extends Controller { const duty = duty_raw.length > 0 ? duty_raw.map((item) => ({ - date: - item.dateStart && item.dateEnd - ? Extension.ToThaiNumber( - `${Extension.ToThaiFullDate2(item.dateStart)} - ${Extension.ToThaiFullDate2(item.dateEnd)}`, - ) - : item.dateStart - ? Extension.ToThaiNumber(Extension.ToThaiFullDate2(item.dateStart)) - : item.dateEnd - ? Extension.ToThaiNumber(Extension.ToThaiFullDate2(item.dateEnd)) - : "", - type: "-", - detail: Extension.ToThaiNumber(item.detail), - agency: "-", - refCommandNo: item.refCommandNo - ? item.refCommandDate - ? Extension.ToThaiNumber( - `${item.refCommandNo} ลว. ${Extension.ToThaiFullDate2(item.refCommandDate)}`, - ) - : Extension.ToThaiNumber(item.refCommandNo) - : "-", - })) + date: + item.dateStart && item.dateEnd + ? Extension.ToThaiNumber( + `${Extension.ToThaiFullDate2(item.dateStart)} - ${Extension.ToThaiFullDate2(item.dateEnd)}`, + ) + : item.dateStart + ? Extension.ToThaiNumber(Extension.ToThaiFullDate2(item.dateStart)) + : item.dateEnd + ? Extension.ToThaiNumber(Extension.ToThaiFullDate2(item.dateEnd)) + : "", + type: "-", + detail: Extension.ToThaiNumber(item.detail), + agency: "-", + refCommandNo: item.refCommandNo + ? item.refCommandDate + ? Extension.ToThaiNumber( + `${item.refCommandNo} ลว. ${Extension.ToThaiFullDate2(item.refCommandDate)}`, + ) + : Extension.ToThaiNumber(item.refCommandNo) + : "-", + })) : [ - { - date: "", - type: "", - detail: "", - agency: "", - refCommandNo: "", - }, - ]; + { + date: "", + type: "", + detail: "", + agency: "", + refCommandNo: "", + }, + ]; const assessments_raw = await this.profileAssessmentsRepository.find({ where: { profileEmployeeId: id, isDeleted: false }, order: { createdAt: "ASC" }, @@ -1828,48 +1824,48 @@ export class ProfileEmployeeController extends Controller { const assessments = assessments_raw.length > 0 ? assessments_raw.map((item) => ({ - year: item.year ? Extension.ToThaiNumber((parseInt(item.year) + 543).toString()) : "", - period: - item.period && item.period == "APR" - ? Extension.ToThaiNumber( - `1 เม.ย. ${(parseInt(item.year) + 543 - 1).toString()} - 31 มี.ค. ${(parseInt(item.year) + 543).toString()}`, - ) - : Extension.ToThaiNumber( - `1 ต.ค. ${(parseInt(item.year) + 543 - 1).toString()} - 30 ก.ย. ${(parseInt(item.year) + 543).toString()}`, - ), - point1: item.point1 ? Extension.ToThaiNumber(item.point1.toString()) : "", - point1Total: item.point1Total - ? Extension.ToThaiNumber(item.point1Total.toString()) - : "", - point2: item.point2 ? Extension.ToThaiNumber(item.point2.toString()) : "", - point2Total: item.point2Total - ? Extension.ToThaiNumber(item.point2Total.toString()) - : "", - pointSum: item.pointSum - ? Extension.ToThaiNumber(`ร้อยละ ${item.pointSum.toString()}`) - : "", - pointSumTh: item.pointSum ? Extension.textPoint(item.pointSum) : "", - level: - item.pointSum < 60.0 - ? "ต้องปรับปรุง" - : item.pointSum <= 69.99 && item.pointSum >= 60.0 - ? "พอใช้" - : item.pointSum <= 79.99 && item.pointSum >= 70.0 - ? "ดี" - : item.pointSum <= 89.99 && item.pointSum >= 80.0 - ? "ดีมาก" - : "ดีเด่น", - })) + year: item.year ? Extension.ToThaiNumber((parseInt(item.year) + 543).toString()) : "", + period: + item.period && item.period == "APR" + ? Extension.ToThaiNumber( + `1 เม.ย. ${(parseInt(item.year) + 543 - 1).toString()} - 31 มี.ค. ${(parseInt(item.year) + 543).toString()}`, + ) + : Extension.ToThaiNumber( + `1 ต.ค. ${(parseInt(item.year) + 543 - 1).toString()} - 30 ก.ย. ${(parseInt(item.year) + 543).toString()}`, + ), + point1: item.point1 ? Extension.ToThaiNumber(item.point1.toString()) : "", + point1Total: item.point1Total + ? Extension.ToThaiNumber(item.point1Total.toString()) + : "", + point2: item.point2 ? Extension.ToThaiNumber(item.point2.toString()) : "", + point2Total: item.point2Total + ? Extension.ToThaiNumber(item.point2Total.toString()) + : "", + pointSum: item.pointSum + ? Extension.ToThaiNumber(`ร้อยละ ${item.pointSum.toString()}`) + : "", + pointSumTh: item.pointSum ? Extension.textPoint(item.pointSum) : "", + level: + item.pointSum < 60.0 + ? "ต้องปรับปรุง" + : item.pointSum <= 69.99 && item.pointSum >= 60.0 + ? "พอใช้" + : item.pointSum <= 79.99 && item.pointSum >= 70.0 + ? "ดี" + : item.pointSum <= 89.99 && item.pointSum >= 80.0 + ? "ดีมาก" + : "ดีเด่น", + })) : [ - { - year: "", - period: "", - point1: "", - point2: "", - pointSum: "", - pointSumTh: "", - }, - ]; + { + year: "", + period: "", + point1: "", + point2: "", + pointSum: "", + pointSumTh: "", + }, + ]; const profileAbility_raw = await this.profileAbilityRepo.find({ where: { profileEmployeeId: id }, order: { createdAt: "ASC" }, @@ -1877,15 +1873,15 @@ export class ProfileEmployeeController extends Controller { const profileAbility = profileAbility_raw.length > 0 ? profileAbility_raw.map((item) => ({ - field: item.field ? item.field : "", - detail: item.detail ? item.detail : "", - })) + field: item.field ? item.field : "", + detail: item.detail ? item.detail : "", + })) : [ - { - field: "", - detail: "", - }, - ]; + { + field: "", + detail: "", + }, + ]; const otherIncome_raw = await this.salaryRepo.find({ where: { @@ -1898,53 +1894,53 @@ export class ProfileEmployeeController extends Controller { const otherIncome = otherIncome_raw.length > 0 ? await Promise.all( - otherIncome_raw.map(async (item) => { - const codeSitAbb = item.posNumCodeSitAbb ?? "-"; - const commandNo = - item.commandNo && item.commandYear - ? item.commandNo + - "/" + - (item.commandYear > 2500 ? item.commandYear : item.commandYear + 543) - : "-"; - const dateAffect = item.commandDateAffect - ? `${Extension.ToThaiFullDate2(item.commandDateAffect)}` + otherIncome_raw.map(async (item) => { + const codeSitAbb = item.posNumCodeSitAbb ?? "-"; + const commandNo = + item.commandNo && item.commandYear + ? item.commandNo + + "/" + + (item.commandYear > 2500 ? item.commandYear : item.commandYear + 543) : "-"; - return { - commandName: item.commandName ?? "", - commandDateAffect: item.commandDateAffect - ? Extension.ToThaiNumber(Extension.ToThaiFullDate2(item.commandDateAffect)) - : "", - commandDateSign: item.commandDateSign - ? Extension.ToThaiNumber(Extension.ToThaiFullDate2(item.commandDateSign)) - : "", - commandNo: item.commandNo ? Extension.ToThaiNumber(item.commandNo) : "", - position: item.positionName, - posLevel: item.positionLevel - ? Extension.ToThaiNumber(item.positionLevel) - : item.positionCee - ? Extension.ToThaiNumber(item.positionCee) - : null, - amount: item.amount - ? Extension.ToThaiNumber(Number(item.amount).toLocaleString()) - : "", - refDoc: Extension.ToThaiNumber( - `คำสั่ง ${codeSitAbb} ที่ ${commandNo} ลว. ${dateAffect}`, - ), - }; - }), - ) + const dateAffect = item.commandDateAffect + ? `${Extension.ToThaiFullDate2(item.commandDateAffect)}` + : "-"; + return { + commandName: item.commandName ?? "", + commandDateAffect: item.commandDateAffect + ? Extension.ToThaiNumber(Extension.ToThaiFullDate2(item.commandDateAffect)) + : "", + commandDateSign: item.commandDateSign + ? Extension.ToThaiNumber(Extension.ToThaiFullDate2(item.commandDateSign)) + : "", + commandNo: item.commandNo ? Extension.ToThaiNumber(item.commandNo) : "", + position: item.positionName, + posLevel: item.positionLevel + ? Extension.ToThaiNumber(item.positionLevel) + : item.positionCee + ? Extension.ToThaiNumber(item.positionCee) + : null, + amount: item.amount + ? Extension.ToThaiNumber(Number(item.amount).toLocaleString()) + : "", + refDoc: Extension.ToThaiNumber( + `คำสั่ง ${codeSitAbb} ที่ ${commandNo} ลว. ${dateAffect}`, + ), + }; + }), + ) : [ - { - commandName: "", - commandDateAffect: "", - commandDateSign: "", - commandNo: "", - position: "", - posLevel: "", - amount: "", - refDoc: "", - }, - ]; + { + commandName: "", + commandDateAffect: "", + commandDateSign: "", + commandNo: "", + position: "", + posLevel: "", + amount: "", + refDoc: "", + }, + ]; // ประวัติพ้นจากราชการ let retires = []; @@ -2043,8 +2039,8 @@ export class ProfileEmployeeController extends Controller { profiles?.position != null ? profiles.posType != null && profiles?.posLevel != null ? Extension.ToThaiNumber( - `${profiles.position} ${profiles.posType.posTypeShortName} ${profiles.posLevel.posLevelName}`, - ) + `${profiles.position} ${profiles.posType.posTypeShortName} ${profiles.posLevel.posLevelName}`, + ) : profiles.position : "" ).trim(); @@ -2061,45 +2057,45 @@ export class ProfileEmployeeController extends Controller { const sum = profiles ? Extension.ToThaiNumber( - ( - Number(profiles.amount) + - Number(profiles.positionSalaryAmount) + - Number(profiles.mouthSalaryAmount) + - Number(profiles.amountSpecial) - ).toLocaleString(), - ) + ( + Number(profiles.amount) + + Number(profiles.positionSalaryAmount) + + Number(profiles.mouthSalaryAmount) + + Number(profiles.amountSpecial) + ).toLocaleString(), + ) : ""; const fullCurrentAddress = profiles && profiles.currentAddress ? Extension.ToThaiNumber( - profiles.currentAddress + - (profiles.currentSubDistrict && profiles.currentSubDistrict.name - ? " ตำบล/แขวง " + profiles.currentSubDistrict.name - : "") + - (profiles.currentDistrict && profiles.currentDistrict.name - ? " อำเภอ/เขต " + profiles.currentDistrict.name - : "") + - (profiles.currentProvince && profiles.currentProvince.name - ? " จังหวัด " + profiles.currentProvince.name - : "") + - (profiles.currentZipCode ? " " + profiles.currentZipCode : ""), - ) + profiles.currentAddress + + (profiles.currentSubDistrict && profiles.currentSubDistrict.name + ? " ตำบล/แขวง " + profiles.currentSubDistrict.name + : "") + + (profiles.currentDistrict && profiles.currentDistrict.name + ? " อำเภอ/เขต " + profiles.currentDistrict.name + : "") + + (profiles.currentProvince && profiles.currentProvince.name + ? " จังหวัด " + profiles.currentProvince.name + : "") + + (profiles.currentZipCode ? " " + profiles.currentZipCode : ""), + ) : ""; const fullRegistrationAddress = profiles && profiles.registrationAddress ? Extension.ToThaiNumber( - profiles.registrationAddress + - (profiles.registrationSubDistrict && profiles.registrationSubDistrict.name - ? " ตำบล/แขวง " + profiles.registrationSubDistrict.name - : "") + - (profiles.registrationDistrict && profiles.registrationDistrict.name - ? " อำเภอ/เขต " + profiles.registrationDistrict.name - : "") + - (profiles.registrationProvince && profiles.registrationProvince.name - ? " จังหวัด " + profiles.registrationProvince.name - : "") + - (profiles.currentZipCode ? " " + profiles.currentZipCode : ""), - ) + profiles.registrationAddress + + (profiles.registrationSubDistrict && profiles.registrationSubDistrict.name + ? " ตำบล/แขวง " + profiles.registrationSubDistrict.name + : "") + + (profiles.registrationDistrict && profiles.registrationDistrict.name + ? " อำเภอ/เขต " + profiles.registrationDistrict.name + : "") + + (profiles.registrationProvince && profiles.registrationProvince.name + ? " จังหวัด " + profiles.registrationProvince.name + : "") + + (profiles.currentZipCode ? " " + profiles.currentZipCode : ""), + ) : ""; const data = { currentDate: Extension.ToThaiNumber(Extension.ToThaiFullDate2(currentDate)), @@ -2147,24 +2143,24 @@ export class ProfileEmployeeController extends Controller { profiles.citizenId != null ? Extension.ToThaiNumber(profiles.citizenId.toString()) : "", fatherFullName: profileFamilyFather?.fatherPrefix || - profileFamilyFather?.fatherFirstName || - profileFamilyFather?.fatherLastName + profileFamilyFather?.fatherFirstName || + profileFamilyFather?.fatherLastName ? `${profileFamilyFather?.fatherPrefix ?? ""}${profileFamilyFather?.fatherFirstName ?? ""} ${profileFamilyFather?.fatherLastName ?? ""}`.trim() : null, fatherLive: profileFamilyFather && profileFamilyFather?.fatherLive == true ? "ถึงแก่กรรม" : "มีชีวิต", motherFullName: profileFamilyMother?.motherPrefix || - profileFamilyMother?.motherFirstName || - profileFamilyMother?.motherLastName + profileFamilyMother?.motherFirstName || + profileFamilyMother?.motherLastName ? `${profileFamilyMother?.motherPrefix ?? ""}${profileFamilyMother?.motherFirstName ?? ""} ${profileFamilyMother?.motherLastName ?? ""}`.trim() : null, motherLive: profileFamilyMother && profileFamilyMother?.motherLive == true ? "ถึงแก่กรรม" : "มีชีวิต", coupleFullName: profileFamilyCouple?.couplePrefix || - profileFamilyCouple?.coupleFirstName || - profileFamilyCouple?.coupleLastNameOld + profileFamilyCouple?.coupleFirstName || + profileFamilyCouple?.coupleLastNameOld ? `${profileFamilyCouple?.couplePrefix ?? ""}${profileFamilyCouple?.coupleFirstName ?? ""} ${profileFamilyCouple?.coupleLastName ?? ""}`.trim() : null, coupleLastNameOld: profileFamilyCouple?.coupleLastNameOld ?? null, @@ -2386,28 +2382,27 @@ export class ProfileEmployeeController extends Controller { } if (body.citizenId) { - const citizenIdDigits = body.citizenId.toString().split("").map(Number); - const cal = - citizenIdDigits[0] * 13 + - citizenIdDigits[1] * 12 + - citizenIdDigits[2] * 11 + - citizenIdDigits[3] * 10 + - citizenIdDigits[4] * 9 + - citizenIdDigits[5] * 8 + - citizenIdDigits[6] * 7 + - citizenIdDigits[7] * 6 + - citizenIdDigits[8] * 5 + - citizenIdDigits[9] * 4 + - citizenIdDigits[10] * 3 + - citizenIdDigits[11] * 2; - const calStp2 = cal % 11; - const chkDigit = (11 - calStp2) % 10; - - if (citizenIdDigits[12] !== chkDigit) { - throw new HttpError(HttpStatus.NOT_FOUND, "ข้อมูลรหัสบัตรประจำตัวประชาชนไม่ถูกต้อง"); - } + Extension.CheckCitizen(body.citizenId); } const record = await this.profileRepo.findOneBy({ id }); + const before = structuredClone(record); + // เช็คว่ามี profileHistory ของ profile นี้หรือไม่ + const historyCount = await this.profileHistoryRepo.count({ + where: { profileEmployeeId: id }, + }); + + // ถ้าไม่มีเลย ให้บันทึกข้อมูลเริ่มต้น (ก่อน update) ลงไปก่อน + if (historyCount === 0) { + await this.profileHistoryRepo.save( + Object.assign(new ProfileEmployeeHistory(), { + ...before, + birthDateOld: before?.birthDate, + profileEmployeeId: id, + id: undefined, + }), + ); + } + if (!record) throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูลโปรไฟล์นี้"); if (body.employeeClass == null || body.employeeClass == undefined || body.employeeClass == "") { @@ -2420,7 +2415,7 @@ export class ProfileEmployeeController extends Controller { Object.assign(record, body); record.dateRetireLaw = calculateRetireLaw(record.birthDate); record.prefixMain = record.prefix; - record.prefix = record.rank && record.rank.length > 0 ? record.rank : record.prefixMain; + record.prefix = record.rank && record.rank.length > 0 ? record.rank : record.prefix; record.createdUserId = request.user.sub; record.createdFullName = request.user.name; record.createdAt = new Date(); @@ -2439,6 +2434,8 @@ export class ProfileEmployeeController extends Controller { }), ); await this.profileRepo.save(record); + // บันทึกประวัติคนครองตำแหน่ง (ถ้า profile นี้ครองตำแหน่งอยู่) + await updateHolderProfileHistory(record.id, request, "EMPLOYEE"); return new HttpSuccess(); } @@ -2536,8 +2533,8 @@ export class ProfileEmployeeController extends Controller { _data.profileEmployeeEmployment.length == 0 ? null : _data.profileEmployeeEmployment.reduce((latest, current) => { - return latest.date > current.date ? latest : current; - }).date; + return latest.date > current.date ? latest : current; + }).date; return { id: _data.id, prefix: _data.prefix, @@ -2700,32 +2697,32 @@ export class ProfileEmployeeController extends Controller { profile.current_holders.length == 0 ? null : profile.current_holders.find((x) => x.orgRevisionId == findRevision.id) != null && - profile.current_holders.find((x) => x.orgRevisionId == findRevision.id)?.orgChild4 != - null + profile.current_holders.find((x) => x.orgRevisionId == findRevision.id)?.orgChild4 != + null ? `${profile.current_holders.find((x) => x.orgRevisionId == findRevision.id)?.orgChild4.orgChild4ShortName} ${profile.current_holders.find((x) => x.orgRevisionId == findRevision.id)?.posMasterNo}` : profile.current_holders.find((x) => x.orgRevisionId == findRevision.id) != null && - profile.current_holders.find((x) => x.orgRevisionId == findRevision.id) - ?.orgChild3 != null + profile.current_holders.find((x) => x.orgRevisionId == findRevision.id) + ?.orgChild3 != null ? `${profile.current_holders.find((x) => x.orgRevisionId == findRevision.id)?.orgChild3.orgChild3ShortName} ${profile.current_holders.find((x) => x.orgRevisionId == findRevision.id)?.posMasterNo}` : profile.current_holders.find((x) => x.orgRevisionId == findRevision.id) != null && - profile.current_holders.find((x) => x.orgRevisionId == findRevision.id) - ?.orgChild2 != null + profile.current_holders.find((x) => x.orgRevisionId == findRevision.id) + ?.orgChild2 != null ? `${profile.current_holders.find((x) => x.orgRevisionId == findRevision.id)?.orgChild2.orgChild2ShortName} ${profile.current_holders.find((x) => x.orgRevisionId == findRevision.id)?.posMasterNo}` : profile.current_holders.find((x) => x.orgRevisionId == findRevision.id) != null && - profile.current_holders.find((x) => x.orgRevisionId == findRevision.id) - ?.orgChild1 != null + profile.current_holders.find((x) => x.orgRevisionId == findRevision.id) + ?.orgChild1 != null ? `${profile.current_holders.find((x) => x.orgRevisionId == findRevision.id)?.orgChild1.orgChild1ShortName} ${profile.current_holders.find((x) => x.orgRevisionId == findRevision.id)?.posMasterNo}` : profile.current_holders.find((x) => x.orgRevisionId == findRevision.id) != - null && - profile.current_holders.find((x) => x.orgRevisionId == findRevision.id) - ?.orgRoot != null + null && + profile.current_holders.find((x) => x.orgRevisionId == findRevision.id) + ?.orgRoot != null ? `${profile.current_holders.find((x) => x.orgRevisionId == findRevision.id)?.orgRoot.orgRootShortName} ${profile.current_holders.find((x) => x.orgRevisionId == findRevision.id)?.posMasterNo}` : null; const root = profile.current_holders.length == 0 || - (profile.current_holders.find((x) => x.orgRevisionId == findRevision.id) != null && - profile.current_holders.find((x) => x.orgRevisionId == findRevision.id)?.orgRoot == null) + (profile.current_holders.find((x) => x.orgRevisionId == findRevision.id) != null && + profile.current_holders.find((x) => x.orgRevisionId == findRevision.id)?.orgRoot == null) ? null : profile.current_holders.find((x) => x.orgRevisionId == findRevision.id)?.orgRoot; @@ -2852,12 +2849,12 @@ export class ProfileEmployeeController extends Controller { queryLike = "profileEmployee.position LIKE :keyword"; } else if (searchField == "posNo") { queryLike = ` - CASE - WHEN current_holders.orgChild4Id IS NOT NULL THEN CONCAT(orgChild4.orgChild4ShortName, " ", current_holders.posMasterNo) - WHEN current_holders.orgChild3Id IS NOT NULL THEN CONCAT(orgChild3.orgChild3ShortName, " ", current_holders.posMasterNo) - WHEN current_holders.orgChild2Id IS NOT NULL THEN CONCAT(orgChild2.orgChild2ShortName, " ", current_holders.posMasterNo) - WHEN current_holders.orgChild1Id IS NOT NULL THEN CONCAT(orgChild1.orgChild1ShortName, " ", current_holders.posMasterNo) - ELSE CONCAT(orgRoot.orgRootShortName, " ", current_holders.posMasterNo) + CASE + WHEN current_holders.orgChild4Id IS NOT NULL THEN CONCAT_WS(' ', orgChild4.orgChild4ShortName, NULLIF(current_holders.posMasterNoPrefix,''), current_holders.posMasterNo, NULLIF(current_holders.posMasterNoSuffix,'')) + WHEN current_holders.orgChild3Id IS NOT NULL THEN CONCAT_WS(' ', orgChild3.orgChild3ShortName, NULLIF(current_holders.posMasterNoPrefix,''), current_holders.posMasterNo, NULLIF(current_holders.posMasterNoSuffix,'')) + WHEN current_holders.orgChild2Id IS NOT NULL THEN CONCAT_WS(' ', orgChild2.orgChild2ShortName, NULLIF(current_holders.posMasterNoPrefix,''), current_holders.posMasterNo, NULLIF(current_holders.posMasterNoSuffix,'')) + WHEN current_holders.orgChild1Id IS NOT NULL THEN CONCAT_WS(' ', orgChild1.orgChild1ShortName, NULLIF(current_holders.posMasterNoPrefix,''), current_holders.posMasterNo, NULLIF(current_holders.posMasterNoSuffix,'')) + ELSE CONCAT_WS(' ', orgRoot.orgRootShortName, NULLIF(current_holders.posMasterNoPrefix,''), current_holders.posMasterNo, NULLIF(current_holders.posMasterNoSuffix,'')) END LIKE :keyword `; } @@ -2950,57 +2947,57 @@ export class ProfileEmployeeController extends Controller { _data.current_holders.length == 0 ? null : _data.current_holders.find((x) => x.orgRevisionId == revisionId) != null && - _data.current_holders.find((x) => x.orgRevisionId == revisionId)?.orgChild4 != null + _data.current_holders.find((x) => x.orgRevisionId == revisionId)?.orgChild4 != null ? `${_data.current_holders.find((x) => x.orgRevisionId == revisionId)?.orgChild4.orgChild4ShortName} ${_data.current_holders.find((x) => x.orgRevisionId == revisionId)?.posMasterNo}` : _data.current_holders.find((x) => x.orgRevisionId == revisionId) != null && - _data.current_holders.find((x) => x.orgRevisionId == revisionId)?.orgChild3 != - null + _data.current_holders.find((x) => x.orgRevisionId == revisionId)?.orgChild3 != + null ? `${_data.current_holders.find((x) => x.orgRevisionId == revisionId)?.orgChild3.orgChild3ShortName} ${_data.current_holders.find((x) => x.orgRevisionId == revisionId)?.posMasterNo}` : _data.current_holders.find((x) => x.orgRevisionId == revisionId) != null && - _data.current_holders.find((x) => x.orgRevisionId == revisionId)?.orgChild2 != - null + _data.current_holders.find((x) => x.orgRevisionId == revisionId)?.orgChild2 != + null ? `${_data.current_holders.find((x) => x.orgRevisionId == revisionId)?.orgChild2.orgChild2ShortName} ${_data.current_holders.find((x) => x.orgRevisionId == revisionId)?.posMasterNo}` : _data.current_holders.find((x) => x.orgRevisionId == revisionId) != null && - _data.current_holders.find((x) => x.orgRevisionId == revisionId)?.orgChild1 != - null + _data.current_holders.find((x) => x.orgRevisionId == revisionId)?.orgChild1 != + null ? `${_data.current_holders.find((x) => x.orgRevisionId == revisionId)?.orgChild1.orgChild1ShortName} ${_data.current_holders.find((x) => x.orgRevisionId == revisionId)?.posMasterNo}` : _data.current_holders.find((x) => x.orgRevisionId == revisionId) != null && - _data.current_holders.find((x) => x.orgRevisionId == revisionId)?.orgRoot != - null + _data.current_holders.find((x) => x.orgRevisionId == revisionId)?.orgRoot != + null ? `${_data.current_holders.find((x) => x.orgRevisionId == revisionId)?.orgRoot.orgRootShortName} ${_data.current_holders.find((x) => x.orgRevisionId == revisionId)?.posMasterNo}` : null; const root = _data.current_holders.length == 0 || - (_data.current_holders.find((x) => x.orgRevisionId == revisionId) != null && - _data.current_holders.find((x) => x.orgRevisionId == revisionId)?.orgRoot == null) + (_data.current_holders.find((x) => x.orgRevisionId == revisionId) != null && + _data.current_holders.find((x) => x.orgRevisionId == revisionId)?.orgRoot == null) ? null : _data.current_holders.find((x) => x.orgRevisionId == revisionId)?.orgRoot; const child1 = _data.current_holders == null || - _data.current_holders.length == 0 || - _data.current_holders.find((x) => x.orgRevisionId == revisionId) == null + _data.current_holders.length == 0 || + _data.current_holders.find((x) => x.orgRevisionId == revisionId) == null ? null : _data.current_holders.find((x) => x.orgRevisionId == revisionId)?.orgChild1; const child2 = _data.current_holders == null || - _data.current_holders.length == 0 || - _data.current_holders.find((x) => x.orgRevisionId == revisionId) == null + _data.current_holders.length == 0 || + _data.current_holders.find((x) => x.orgRevisionId == revisionId) == null ? null : _data.current_holders.find((x) => x.orgRevisionId == revisionId)?.orgChild2; const child3 = _data.current_holders == null || - _data.current_holders.length == 0 || - _data.current_holders.find((x) => x.orgRevisionId == revisionId) == null + _data.current_holders.length == 0 || + _data.current_holders.find((x) => x.orgRevisionId == revisionId) == null ? null : _data.current_holders.find((x) => x.orgRevisionId == revisionId)?.orgChild3; const child4 = _data.current_holders == null || - _data.current_holders.length == 0 || - _data.current_holders.find((x) => x.orgRevisionId == revisionId) == null + _data.current_holders.length == 0 || + _data.current_holders.find((x) => x.orgRevisionId == revisionId) == null ? null : _data.current_holders.find((x) => x.orgRevisionId == revisionId)?.orgChild4; @@ -3111,7 +3108,7 @@ export class ProfileEmployeeController extends Controller { @Query() sortBy: string = "profileEmployee.dateLeave", @Query() sort: "ASC" | "DESC" = "DESC", ) { - let _data = await new permission().PermissionOrgList(request, "SYS_REGISTRY_EMP"); + let _data = await new permission().PermissionOrgList(request, "SYS_REGISTRY_RETIRE_EMP"); const { data, total } = await this.profileLeaveService.getLeaveEmployees(request, { page, @@ -3211,12 +3208,12 @@ export class ProfileEmployeeController extends Controller { queryLike = "profileEmployee.position LIKE :keyword"; } else if (searchField == "posNo") { queryLike = ` - CASE - WHEN current_holders.orgChild4Id IS NOT NULL THEN CONCAT(orgChild4.orgChild4ShortName, " ", current_holders.posMasterNo) - WHEN current_holders.orgChild3Id IS NOT NULL THEN CONCAT(orgChild3.orgChild3ShortName, " ", current_holders.posMasterNo) - WHEN current_holders.orgChild2Id IS NOT NULL THEN CONCAT(orgChild2.orgChild2ShortName, " ", current_holders.posMasterNo) - WHEN current_holders.orgChild1Id IS NOT NULL THEN CONCAT(orgChild1.orgChild1ShortName, " ", current_holders.posMasterNo) - ELSE CONCAT(orgRoot.orgRootShortName, " ", current_holders.posMasterNo) + CASE + WHEN current_holders.orgChild4Id IS NOT NULL THEN CONCAT_WS(' ', orgChild4.orgChild4ShortName, NULLIF(current_holders.posMasterNoPrefix,''), current_holders.posMasterNo, NULLIF(current_holders.posMasterNoSuffix,'')) + WHEN current_holders.orgChild3Id IS NOT NULL THEN CONCAT_WS(' ', orgChild3.orgChild3ShortName, NULLIF(current_holders.posMasterNoPrefix,''), current_holders.posMasterNo, NULLIF(current_holders.posMasterNoSuffix,'')) + WHEN current_holders.orgChild2Id IS NOT NULL THEN CONCAT_WS(' ', orgChild2.orgChild2ShortName, NULLIF(current_holders.posMasterNoPrefix,''), current_holders.posMasterNo, NULLIF(current_holders.posMasterNoSuffix,'')) + WHEN current_holders.orgChild1Id IS NOT NULL THEN CONCAT_WS(' ', orgChild1.orgChild1ShortName, NULLIF(current_holders.posMasterNoPrefix,''), current_holders.posMasterNo, NULLIF(current_holders.posMasterNoSuffix,'')) + ELSE CONCAT_WS(' ', orgRoot.orgRootShortName, NULLIF(current_holders.posMasterNoPrefix,''), current_holders.posMasterNo, NULLIF(current_holders.posMasterNoSuffix,'')) END LIKE :keyword `; } @@ -3275,7 +3272,7 @@ export class ProfileEmployeeController extends Controller { ? _data.child1[0] != null ? `current_holders.orgChild1Id IN (:...child1)` : // : `current_holders.orgChild1Id is ${_data.privilege == "PARENT" ? "not null" : "null"}` - `current_holders.orgChild1Id is null` + `current_holders.orgChild1Id is null` : "1=1", { child1: _data.child1, @@ -3369,7 +3366,7 @@ export class ProfileEmployeeController extends Controller { const data = await Promise.all( record.map((_data) => { const holder = _data.current_holders.find((x) => x.orgRevisionId == findRevision.id); - const numPart = holder ? `${holder.posMasterNoPrefix ?? ''}${holder.posMasterNo ?? ''}${holder.posMasterNoSuffix ?? ''}` : ''; + const numPart = holder ? [holder.posMasterNoPrefix, holder.posMasterNo, holder.posMasterNoSuffix].filter((p) => p !== null && p !== undefined && p !== '').join(' ') : ''; const shortName = !holder ? null : holder.orgChild4 != null @@ -3387,40 +3384,40 @@ export class ProfileEmployeeController extends Controller { _data.profileEmployeeEmployment.length == 0 ? null : _data.profileEmployeeEmployment.reduce((latest, current) => { - return latest.date > current.date ? latest : current; - }).date; + return latest.date > current.date ? latest : current; + }).date; const root = _data.current_holders.length == 0 || - (_data.current_holders.find((x) => x.orgRevisionId == findRevision.id) != null && - _data.current_holders.find((x) => x.orgRevisionId == findRevision.id)?.orgRoot == null) + (_data.current_holders.find((x) => x.orgRevisionId == findRevision.id) != null && + _data.current_holders.find((x) => x.orgRevisionId == findRevision.id)?.orgRoot == null) ? null : _data.current_holders.find((x) => x.orgRevisionId == findRevision.id)?.orgRoot; const child1 = _data.current_holders == null || - _data.current_holders.length == 0 || - _data.current_holders.find((x) => x.orgRevisionId == findRevision.id) == null + _data.current_holders.length == 0 || + _data.current_holders.find((x) => x.orgRevisionId == findRevision.id) == null ? null : _data.current_holders.find((x) => x.orgRevisionId == findRevision.id)?.orgChild1; const child2 = _data.current_holders == null || - _data.current_holders.length == 0 || - _data.current_holders.find((x) => x.orgRevisionId == findRevision.id) == null + _data.current_holders.length == 0 || + _data.current_holders.find((x) => x.orgRevisionId == findRevision.id) == null ? null : _data.current_holders.find((x) => x.orgRevisionId == findRevision.id)?.orgChild2; const child3 = _data.current_holders == null || - _data.current_holders.length == 0 || - _data.current_holders.find((x) => x.orgRevisionId == findRevision.id) == null + _data.current_holders.length == 0 || + _data.current_holders.find((x) => x.orgRevisionId == findRevision.id) == null ? null : _data.current_holders.find((x) => x.orgRevisionId == findRevision.id)?.orgChild3; const child4 = _data.current_holders == null || - _data.current_holders.length == 0 || - _data.current_holders.find((x) => x.orgRevisionId == findRevision.id) == null + _data.current_holders.length == 0 || + _data.current_holders.find((x) => x.orgRevisionId == findRevision.id) == null ? null : _data.current_holders.find((x) => x.orgRevisionId == findRevision.id)?.orgChild4; @@ -3688,8 +3685,8 @@ export class ProfileEmployeeController extends Controller { .map((x) => x.current_holderId).length == 0 ? ["zxc"] : orgRevision.employeePosMasters - .filter((x) => x.current_holderId != null) - .map((x) => x.current_holderId), + .filter((x) => x.current_holderId != null) + .map((x) => x.current_holderId), }); }), ) @@ -3846,7 +3843,7 @@ export class ProfileEmployeeController extends Controller { holder.orgChild2?.orgChild2ShortName || holder.orgChild1?.orgChild1ShortName || holder.orgRoot?.orgRootShortName; - return `${shortName || ""} ${holder.posMasterNo || ""}`; + return `${shortName || ""} ${[holder.posMasterNoPrefix, holder.posMasterNo, holder.posMasterNoSuffix].filter((p) => p !== null && p !== undefined && p !== '').join(' ')}`; }); return profile.current_holders.map((holder, index) => { const position = holder.positions.find((position) => position.posMasterId === holder.id); @@ -3927,37 +3924,37 @@ export class ProfileEmployeeController extends Controller { const posMaster = profile.current_holders == null || - profile.current_holders.length == 0 || - profile.current_holders.find((x) => x.orgRevisionId == orgRevisionPublish.id) == null + profile.current_holders.length == 0 || + profile.current_holders.find((x) => x.orgRevisionId == orgRevisionPublish.id) == null ? null : profile.current_holders.find((x) => x.orgRevisionId == orgRevisionPublish.id); const root = profile.current_holders == null || - profile.current_holders.find((x) => x.orgRevisionId == orgRevisionPublish.id)?.orgRoot == null + profile.current_holders.find((x) => x.orgRevisionId == orgRevisionPublish.id)?.orgRoot == null ? null : profile.current_holders.find((x) => x.orgRevisionId == orgRevisionPublish.id)?.orgRoot; const child1 = profile.current_holders == null || - profile.current_holders.find((x) => x.orgRevisionId == orgRevisionPublish.id)?.orgChild1 == + profile.current_holders.find((x) => x.orgRevisionId == orgRevisionPublish.id)?.orgChild1 == null ? null : profile.current_holders.find((x) => x.orgRevisionId == orgRevisionPublish.id)?.orgChild1; const child2 = profile.current_holders == null || - profile.current_holders.find((x) => x.orgRevisionId == orgRevisionPublish.id)?.orgChild2 == + profile.current_holders.find((x) => x.orgRevisionId == orgRevisionPublish.id)?.orgChild2 == null ? null : profile.current_holders.find((x) => x.orgRevisionId == orgRevisionPublish.id)?.orgChild2; const child3 = profile.current_holders == null || - profile.current_holders.find((x) => x.orgRevisionId == orgRevisionPublish.id)?.orgChild3 == + profile.current_holders.find((x) => x.orgRevisionId == orgRevisionPublish.id)?.orgChild3 == null ? null : profile.current_holders.find((x) => x.orgRevisionId == orgRevisionPublish.id)?.orgChild3; const child4 = profile.current_holders == null || - profile.current_holders.find((x) => x.orgRevisionId == orgRevisionPublish.id)?.orgChild4 == + profile.current_holders.find((x) => x.orgRevisionId == orgRevisionPublish.id)?.orgChild4 == null ? null : profile.current_holders.find((x) => x.orgRevisionId == orgRevisionPublish.id)?.orgChild4; @@ -4024,40 +4021,38 @@ export class ProfileEmployeeController extends Controller { salary: profile ? profile.amount : null, amountSpecial: profile ? profile.amountSpecial : null, posNo: null, - // root?.orgRootShortName && posMaster?.posMasterNo - // ? `${root?.orgRootShortName} ${posMaster?.posMasterNo}` - // : "", }; + const _numPart = posMaster ? [posMaster.posMasterNoPrefix, posMaster.posMasterNo, posMaster.posMasterNoSuffix].filter((p) => p !== null && p !== undefined && p !== '').join(' ') : ''; if (_profile.child4Id != null) { _profile.node = 4; _profile.nodeId = _profile.child4Id; _profile.nodeDnaId = _profile.child4DnaId; _profile.nodeShortName = _profile.child4ShortName; - _profile.posNo = `${_profile.child4ShortName} ${_profile.posMasterNo}`; + _profile.posNo = `${_profile.child4ShortName} ${_numPart}`; } else if (_profile.child3Id != null) { _profile.node = 3; _profile.nodeId = _profile.child3Id; _profile.nodeDnaId = _profile.child3DnaId; _profile.nodeShortName = _profile.child3ShortName; - _profile.posNo = `${_profile.child3ShortName} ${_profile.posMasterNo}`; + _profile.posNo = `${_profile.child3ShortName} ${_numPart}`; } else if (_profile.child2Id != null) { _profile.node = 2; _profile.nodeId = _profile.child2Id; _profile.nodeDnaId = _profile.child2DnaId; _profile.nodeShortName = _profile.child2ShortName; - _profile.posNo = `${_profile.child2ShortName} ${_profile.posMasterNo}`; + _profile.posNo = `${_profile.child2ShortName} ${_numPart}`; } else if (_profile.child1Id != null) { _profile.node = 1; _profile.nodeId = _profile.child1Id; _profile.nodeDnaId = _profile.child1DnaId; _profile.nodeShortName = _profile.child1ShortName; - _profile.posNo = `${_profile.child1ShortName} ${_profile.posMasterNo}`; + _profile.posNo = `${_profile.child1ShortName} ${_numPart}`; } else if (_profile.rootId != null) { _profile.node = 0; _profile.nodeId = _profile.rootId; _profile.nodeDnaId = _profile.rootDnaId; _profile.nodeShortName = _profile.rootShortName; - _profile.posNo = `${_profile.rootShortName} ${_profile.posMasterNo}`; + _profile.posNo = `${_profile.rootShortName} ${_numPart}`; } return new HttpSuccess(_profile); } @@ -4158,7 +4153,7 @@ export class ProfileEmployeeController extends Controller { ? _data.child1[0] != null ? `current_holders.orgChild1Id IN (:...child1)` : // : `current_holders.orgChild1Id is ${_data.privilege == "PARENT" ? "not null" : "null"}` - `current_holders.orgChild1Id is null` + `current_holders.orgChild1Id is null` : "1=1", { child1: _data.child1 }, ) @@ -4202,32 +4197,32 @@ export class ProfileEmployeeController extends Controller { item.current_holders.length == 0 ? null : item.current_holders.find((x) => x.orgRevisionId == findRevision.id) != null && - item.current_holders.find((x) => x.orgRevisionId == findRevision.id)?.orgChild4 != - null + item.current_holders.find((x) => x.orgRevisionId == findRevision.id)?.orgChild4 != + null ? `${item.current_holders.find((x) => x.orgRevisionId == findRevision.id)?.orgChild4.orgChild4ShortName} ${item.current_holders.find((x) => x.orgRevisionId == findRevision.id)?.posMasterNo}` : item.current_holders.find((x) => x.orgRevisionId == findRevision.id) != null && - item.current_holders.find((x) => x.orgRevisionId == findRevision.id)?.orgChild3 != - null + item.current_holders.find((x) => x.orgRevisionId == findRevision.id)?.orgChild3 != + null ? `${item.current_holders.find((x) => x.orgRevisionId == findRevision.id)?.orgChild3.orgChild3ShortName} ${item.current_holders.find((x) => x.orgRevisionId == findRevision.id)?.posMasterNo}` : item.current_holders.find((x) => x.orgRevisionId == findRevision.id) != null && - item.current_holders.find((x) => x.orgRevisionId == findRevision.id) - ?.orgChild2 != null + item.current_holders.find((x) => x.orgRevisionId == findRevision.id) + ?.orgChild2 != null ? `${item.current_holders.find((x) => x.orgRevisionId == findRevision.id)?.orgChild2.orgChild2ShortName} ${item.current_holders.find((x) => x.orgRevisionId == findRevision.id)?.posMasterNo}` : item.current_holders.find((x) => x.orgRevisionId == findRevision.id) != null && - item.current_holders.find((x) => x.orgRevisionId == findRevision.id) - ?.orgChild1 != null + item.current_holders.find((x) => x.orgRevisionId == findRevision.id) + ?.orgChild1 != null ? `${item.current_holders.find((x) => x.orgRevisionId == findRevision.id)?.orgChild1.orgChild1ShortName} ${item.current_holders.find((x) => x.orgRevisionId == findRevision.id)?.posMasterNo}` : item.current_holders.find((x) => x.orgRevisionId == findRevision.id) != - null && - item.current_holders.find((x) => x.orgRevisionId == findRevision.id) - ?.orgRoot != null + null && + item.current_holders.find((x) => x.orgRevisionId == findRevision.id) + ?.orgRoot != null ? `${item.current_holders.find((x) => x.orgRevisionId == findRevision.id)?.orgRoot.orgRootShortName} ${item.current_holders.find((x) => x.orgRevisionId == findRevision.id)?.posMasterNo}` : null; const root = item.current_holders.length == 0 || - (item.current_holders.find((x) => x.orgRevisionId == findRevision.id) != null && - item.current_holders.find((x) => x.orgRevisionId == findRevision.id)?.orgRoot == null) + (item.current_holders.find((x) => x.orgRevisionId == findRevision.id) != null && + item.current_holders.find((x) => x.orgRevisionId == findRevision.id)?.orgRoot == null) ? null : item.current_holders.find((x) => x.orgRevisionId == findRevision.id)?.orgRoot; @@ -4717,7 +4712,7 @@ export class ProfileEmployeeController extends Controller { ? _data.child1[0] != null ? `current_holders.orgChild1Id IN (:...child1)` : // : `current_holders.orgChild1Id is ${_data.privilege == "PARENT" ? "not null" : "null"}` - `current_holders.orgChild1Id is null` + `current_holders.orgChild1Id is null` : "1=1", { child1: _data.child1, @@ -4788,54 +4783,54 @@ export class ProfileEmployeeController extends Controller { isProbation: item.isProbation, orgRootName: item.current_holders == null || - item.current_holders.find((x) => x.orgRevisionId == orgRevisionActive?.id) == null || - item.current_holders.find((x) => x.orgRevisionId == orgRevisionActive?.id)?.orgRoot == + item.current_holders.find((x) => x.orgRevisionId == orgRevisionActive?.id) == null || + item.current_holders.find((x) => x.orgRevisionId == orgRevisionActive?.id)?.orgRoot == null || - item.current_holders.find((x) => x.orgRevisionId == orgRevisionActive?.id)?.orgRoot - ?.orgRootName == null + item.current_holders.find((x) => x.orgRevisionId == orgRevisionActive?.id)?.orgRoot + ?.orgRootName == null ? null : item.current_holders.find((x) => x.orgRevisionId == orgRevisionActive?.id)?.orgRoot - ?.orgRootName, + ?.orgRootName, orgChild1Name: item.current_holders == null || - item.current_holders.find((x) => x.orgRevisionId == orgRevisionActive?.id) == null || - item.current_holders.find((x) => x.orgRevisionId == orgRevisionActive?.id)?.orgChild1 == + item.current_holders.find((x) => x.orgRevisionId == orgRevisionActive?.id) == null || + item.current_holders.find((x) => x.orgRevisionId == orgRevisionActive?.id)?.orgChild1 == null || - item.current_holders.find((x) => x.orgRevisionId == orgRevisionActive?.id)?.orgChild1 - ?.orgChild1Name == null + item.current_holders.find((x) => x.orgRevisionId == orgRevisionActive?.id)?.orgChild1 + ?.orgChild1Name == null ? null : item.current_holders.find((x) => x.orgRevisionId == orgRevisionActive?.id) - ?.orgChild1?.orgChild1Name, + ?.orgChild1?.orgChild1Name, orgChild2Name: item.current_holders == null || - item.current_holders.find((x) => x.orgRevisionId == orgRevisionActive?.id) == null || - item.current_holders.find((x) => x.orgRevisionId == orgRevisionActive?.id)?.orgChild2 == + item.current_holders.find((x) => x.orgRevisionId == orgRevisionActive?.id) == null || + item.current_holders.find((x) => x.orgRevisionId == orgRevisionActive?.id)?.orgChild2 == null || - item.current_holders.find((x) => x.orgRevisionId == orgRevisionActive?.id)?.orgChild2 - ?.orgChild2Name == null + item.current_holders.find((x) => x.orgRevisionId == orgRevisionActive?.id)?.orgChild2 + ?.orgChild2Name == null ? null : item.current_holders.find((x) => x.orgRevisionId == orgRevisionActive?.id) - ?.orgChild2?.orgChild2Name, + ?.orgChild2?.orgChild2Name, orgChild3Name: item.current_holders == null || - item.current_holders.find((x) => x.orgRevisionId == orgRevisionActive?.id) == null || - item.current_holders.find((x) => x.orgRevisionId == orgRevisionActive?.id)?.orgChild3 == + item.current_holders.find((x) => x.orgRevisionId == orgRevisionActive?.id) == null || + item.current_holders.find((x) => x.orgRevisionId == orgRevisionActive?.id)?.orgChild3 == null || - item.current_holders.find((x) => x.orgRevisionId == orgRevisionActive?.id)?.orgChild3 - ?.orgChild3Name == null + item.current_holders.find((x) => x.orgRevisionId == orgRevisionActive?.id)?.orgChild3 + ?.orgChild3Name == null ? null : item.current_holders.find((x) => x.orgRevisionId == orgRevisionActive?.id) - ?.orgChild3?.orgChild3Name, + ?.orgChild3?.orgChild3Name, orgChild4Name: item.current_holders == null || - item.current_holders.find((x) => x.orgRevisionId == orgRevisionActive?.id) == null || - item.current_holders.find((x) => x.orgRevisionId == orgRevisionActive?.id)?.orgChild4 == + item.current_holders.find((x) => x.orgRevisionId == orgRevisionActive?.id) == null || + item.current_holders.find((x) => x.orgRevisionId == orgRevisionActive?.id)?.orgChild4 == null || - item.current_holders.find((x) => x.orgRevisionId == orgRevisionActive?.id)?.orgChild4 - ?.orgChild4Name == null + item.current_holders.find((x) => x.orgRevisionId == orgRevisionActive?.id)?.orgChild4 + ?.orgChild4Name == null ? null : item.current_holders.find((x) => x.orgRevisionId == orgRevisionActive?.id) - ?.orgChild4?.orgChild4Name, + ?.orgChild4?.orgChild4Name, }; }), ); @@ -4912,49 +4907,49 @@ export class ProfileEmployeeController extends Controller { findProfile.map(async (item: ProfileEmployee) => { const posMaster = item.current_holders == null || - item.current_holders.length == 0 || - item.current_holders.find((x) => x.orgRevisionId == orgRevisionActive.id) == null + item.current_holders.length == 0 || + item.current_holders.find((x) => x.orgRevisionId == orgRevisionActive.id) == null ? null : item.current_holders.find((x) => x.orgRevisionId == orgRevisionActive.id); const position = posMaster == null || - item.current_holders.find((x) => x.orgRevisionId == orgRevisionActive.id)?.positions == + item.current_holders.find((x) => x.orgRevisionId == orgRevisionActive.id)?.positions == null || - item.current_holders?.find((x) => x.orgRevisionId == orgRevisionActive.id)?.positions - .length == 0 || - item.current_holders - .find((x) => x.orgRevisionId == orgRevisionActive.id) - ?.positions?.find((position) => position.positionIsSelected == true) == null + item.current_holders?.find((x) => x.orgRevisionId == orgRevisionActive.id)?.positions + .length == 0 || + item.current_holders + .find((x) => x.orgRevisionId == orgRevisionActive.id) + ?.positions?.find((position) => position.positionIsSelected == true) == null ? null : item.current_holders - .find((x) => x.orgRevisionId == orgRevisionActive.id) - ?.positions?.find((position) => position.positionIsSelected == true); + .find((x) => x.orgRevisionId == orgRevisionActive.id) + ?.positions?.find((position) => position.positionIsSelected == true); const shortName = item.current_holders.length == 0 ? null : item.current_holders.find((x) => x.orgRevisionId == orgRevisionActive.id) != null && - item.current_holders.find((x) => x.orgRevisionId == orgRevisionActive.id) - ?.orgChild4 != null + item.current_holders.find((x) => x.orgRevisionId == orgRevisionActive.id) + ?.orgChild4 != null ? `${item.current_holders.find((x) => x.orgRevisionId == orgRevisionActive.id)?.orgChild4.orgChild4ShortName} ${item.current_holders.find((x) => x.orgRevisionId == orgRevisionActive.id)?.posMasterNo}` : item.current_holders.find((x) => x.orgRevisionId == orgRevisionActive.id) != null && - item.current_holders.find((x) => x.orgRevisionId == orgRevisionActive.id) - ?.orgChild3 != null + item.current_holders.find((x) => x.orgRevisionId == orgRevisionActive.id) + ?.orgChild3 != null ? `${item.current_holders.find((x) => x.orgRevisionId == orgRevisionActive.id)?.orgChild3.orgChild3ShortName} ${item.current_holders.find((x) => x.orgRevisionId == orgRevisionActive.id)?.posMasterNo}` : item.current_holders.find((x) => x.orgRevisionId == orgRevisionActive.id) != - null && - item.current_holders.find((x) => x.orgRevisionId == orgRevisionActive.id) - ?.orgChild2 != null + null && + item.current_holders.find((x) => x.orgRevisionId == orgRevisionActive.id) + ?.orgChild2 != null ? `${item.current_holders.find((x) => x.orgRevisionId == orgRevisionActive.id)?.orgChild2.orgChild2ShortName} ${item.current_holders.find((x) => x.orgRevisionId == orgRevisionActive.id)?.posMasterNo}` : item.current_holders.find((x) => x.orgRevisionId == orgRevisionActive.id) != - null && - item.current_holders.find((x) => x.orgRevisionId == orgRevisionActive.id) - ?.orgChild1 != null + null && + item.current_holders.find((x) => x.orgRevisionId == orgRevisionActive.id) + ?.orgChild1 != null ? `${item.current_holders.find((x) => x.orgRevisionId == orgRevisionActive.id)?.orgChild1.orgChild1ShortName} ${item.current_holders.find((x) => x.orgRevisionId == orgRevisionActive.id)?.posMasterNo}` : item.current_holders.find((x) => x.orgRevisionId == orgRevisionActive.id) != - null && - item.current_holders.find((x) => x.orgRevisionId == orgRevisionActive.id) - ?.orgRoot != null + null && + item.current_holders.find((x) => x.orgRevisionId == orgRevisionActive.id) + ?.orgRoot != null ? `${item.current_holders.find((x) => x.orgRevisionId == orgRevisionActive.id)?.orgRoot.orgRootShortName} ${item.current_holders.find((x) => x.orgRevisionId == orgRevisionActive.id)?.posMasterNo}` : null; @@ -4972,54 +4967,54 @@ export class ProfileEmployeeController extends Controller { isProbation: item.isProbation, orgRootName: item.current_holders == null || - item.current_holders.find((x) => x.orgRevisionId == orgRevisionActive?.id) == null || - item.current_holders.find((x) => x.orgRevisionId == orgRevisionActive?.id)?.orgRoot == + item.current_holders.find((x) => x.orgRevisionId == orgRevisionActive?.id) == null || + item.current_holders.find((x) => x.orgRevisionId == orgRevisionActive?.id)?.orgRoot == null || - item.current_holders.find((x) => x.orgRevisionId == orgRevisionActive?.id)?.orgRoot - ?.orgRootName == null + item.current_holders.find((x) => x.orgRevisionId == orgRevisionActive?.id)?.orgRoot + ?.orgRootName == null ? null : item.current_holders.find((x) => x.orgRevisionId == orgRevisionActive?.id)?.orgRoot - ?.orgRootName, + ?.orgRootName, orgChild1Name: item.current_holders == null || - item.current_holders.find((x) => x.orgRevisionId == orgRevisionActive?.id) == null || - item.current_holders.find((x) => x.orgRevisionId == orgRevisionActive?.id)?.orgChild1 == + item.current_holders.find((x) => x.orgRevisionId == orgRevisionActive?.id) == null || + item.current_holders.find((x) => x.orgRevisionId == orgRevisionActive?.id)?.orgChild1 == null || - item.current_holders.find((x) => x.orgRevisionId == orgRevisionActive?.id)?.orgChild1 - ?.orgChild1Name == null + item.current_holders.find((x) => x.orgRevisionId == orgRevisionActive?.id)?.orgChild1 + ?.orgChild1Name == null ? null : item.current_holders.find((x) => x.orgRevisionId == orgRevisionActive?.id) - ?.orgChild1?.orgChild1Name, + ?.orgChild1?.orgChild1Name, orgChild2Name: item.current_holders == null || - item.current_holders.find((x) => x.orgRevisionId == orgRevisionActive?.id) == null || - item.current_holders.find((x) => x.orgRevisionId == orgRevisionActive?.id)?.orgChild2 == + item.current_holders.find((x) => x.orgRevisionId == orgRevisionActive?.id) == null || + item.current_holders.find((x) => x.orgRevisionId == orgRevisionActive?.id)?.orgChild2 == null || - item.current_holders.find((x) => x.orgRevisionId == orgRevisionActive?.id)?.orgChild2 - ?.orgChild2Name == null + item.current_holders.find((x) => x.orgRevisionId == orgRevisionActive?.id)?.orgChild2 + ?.orgChild2Name == null ? null : item.current_holders.find((x) => x.orgRevisionId == orgRevisionActive?.id) - ?.orgChild2?.orgChild2Name, + ?.orgChild2?.orgChild2Name, orgChild3Name: item.current_holders == null || - item.current_holders.find((x) => x.orgRevisionId == orgRevisionActive?.id) == null || - item.current_holders.find((x) => x.orgRevisionId == orgRevisionActive?.id)?.orgChild3 == + item.current_holders.find((x) => x.orgRevisionId == orgRevisionActive?.id) == null || + item.current_holders.find((x) => x.orgRevisionId == orgRevisionActive?.id)?.orgChild3 == null || - item.current_holders.find((x) => x.orgRevisionId == orgRevisionActive?.id)?.orgChild3 - ?.orgChild3Name == null + item.current_holders.find((x) => x.orgRevisionId == orgRevisionActive?.id)?.orgChild3 + ?.orgChild3Name == null ? null : item.current_holders.find((x) => x.orgRevisionId == orgRevisionActive?.id) - ?.orgChild3?.orgChild3Name, + ?.orgChild3?.orgChild3Name, orgChild4Name: item.current_holders == null || - item.current_holders.find((x) => x.orgRevisionId == orgRevisionActive?.id) == null || - item.current_holders.find((x) => x.orgRevisionId == orgRevisionActive?.id)?.orgChild4 == + item.current_holders.find((x) => x.orgRevisionId == orgRevisionActive?.id) == null || + item.current_holders.find((x) => x.orgRevisionId == orgRevisionActive?.id)?.orgChild4 == null || - item.current_holders.find((x) => x.orgRevisionId == orgRevisionActive?.id)?.orgChild4 - ?.orgChild4Name == null + item.current_holders.find((x) => x.orgRevisionId == orgRevisionActive?.id)?.orgChild4 + ?.orgChild4Name == null ? null : item.current_holders.find((x) => x.orgRevisionId == orgRevisionActive?.id) - ?.orgChild4?.orgChild4Name, + ?.orgChild4?.orgChild4Name, }; }), ); @@ -5312,7 +5307,7 @@ export class ProfileEmployeeController extends Controller { isLeave: false, isRetired: item.current_holder.birthDate == null || - calculateRetireDate(item.current_holder.birthDate).getFullYear() != body.year + calculateRetireDate(item.current_holder.birthDate).getFullYear() != body.year ? false : true, isSpecial: false, @@ -5366,98 +5361,98 @@ export class ProfileEmployeeController extends Controller { posTypeId: profile.posType == null ? null : profile.posType.id, rootId: profile.current_holders == null || - profile.current_holders.find((x) => x.orgRevisionId == revisionId) == null || - profile.current_holders.find((x) => x.orgRevisionId == revisionId)?.orgRoot == null + profile.current_holders.find((x) => x.orgRevisionId == revisionId) == null || + profile.current_holders.find((x) => x.orgRevisionId == revisionId)?.orgRoot == null ? null : profile.current_holders.find((x) => x.orgRevisionId == revisionId)?.orgRootId, rootDnaId: profile.current_holders == null || - profile.current_holders.find((x) => x.orgRevisionId == revisionId) == null || - profile.current_holders.find((x) => x.orgRevisionId == revisionId)?.orgRoot == null + profile.current_holders.find((x) => x.orgRevisionId == revisionId) == null || + profile.current_holders.find((x) => x.orgRevisionId == revisionId)?.orgRoot == null ? null : profile.current_holders.find((x) => x.orgRevisionId == revisionId)?.ancestorDNA, root: profile.current_holders == null || - profile.current_holders.find((x) => x.orgRevisionId == revisionId) == null || - profile.current_holders.find((x) => x.orgRevisionId == revisionId)?.orgRoot == null + profile.current_holders.find((x) => x.orgRevisionId == revisionId) == null || + profile.current_holders.find((x) => x.orgRevisionId == revisionId)?.orgRoot == null ? null : profile.current_holders.find((x) => x.orgRevisionId == revisionId)?.orgRoot.orgRootName, child1Id: profile.current_holders == null || - profile.current_holders.find((x) => x.orgRevisionId == revisionId) == null || - profile.current_holders.find((x) => x.orgRevisionId == revisionId)?.orgChild1 == null + profile.current_holders.find((x) => x.orgRevisionId == revisionId) == null || + profile.current_holders.find((x) => x.orgRevisionId == revisionId)?.orgChild1 == null ? null : profile.current_holders.find((x) => x.orgRevisionId == revisionId)?.orgChild1Id, child1DnaId: profile.current_holders == null || - profile.current_holders.find((x) => x.orgRevisionId == revisionId) == null || - profile.current_holders.find((x) => x.orgRevisionId == revisionId)?.orgChild1 == null + profile.current_holders.find((x) => x.orgRevisionId == revisionId) == null || + profile.current_holders.find((x) => x.orgRevisionId == revisionId)?.orgChild1 == null ? null : profile.current_holders.find((x) => x.orgRevisionId == revisionId)?.ancestorDNA, child1: profile.current_holders == null || - profile.current_holders.find((x) => x.orgRevisionId == revisionId) == null || - profile.current_holders.find((x) => x.orgRevisionId == revisionId)?.orgChild1 == null + profile.current_holders.find((x) => x.orgRevisionId == revisionId) == null || + profile.current_holders.find((x) => x.orgRevisionId == revisionId)?.orgChild1 == null ? null : profile.current_holders.find((x) => x.orgRevisionId == revisionId)?.orgChild1 - .orgChild1Name, + .orgChild1Name, child2Id: profile.current_holders == null || - profile.current_holders.find((x) => x.orgRevisionId == revisionId) == null || - profile.current_holders.find((x) => x.orgRevisionId == revisionId)?.orgChild2 == null + profile.current_holders.find((x) => x.orgRevisionId == revisionId) == null || + profile.current_holders.find((x) => x.orgRevisionId == revisionId)?.orgChild2 == null ? null : profile.current_holders.find((x) => x.orgRevisionId == revisionId)?.orgChild2Id, child2DnaId: profile.current_holders == null || - profile.current_holders.find((x) => x.orgRevisionId == revisionId) == null || - profile.current_holders.find((x) => x.orgRevisionId == revisionId)?.orgChild2 == null + profile.current_holders.find((x) => x.orgRevisionId == revisionId) == null || + profile.current_holders.find((x) => x.orgRevisionId == revisionId)?.orgChild2 == null ? null : profile.current_holders.find((x) => x.orgRevisionId == revisionId)?.ancestorDNA, child2: profile.current_holders == null || - profile.current_holders.find((x) => x.orgRevisionId == revisionId) == null || - profile.current_holders.find((x) => x.orgRevisionId == revisionId)?.orgChild2 == null + profile.current_holders.find((x) => x.orgRevisionId == revisionId) == null || + profile.current_holders.find((x) => x.orgRevisionId == revisionId)?.orgChild2 == null ? null : profile.current_holders.find((x) => x.orgRevisionId == revisionId)?.orgChild2 - .orgChild2Name, + .orgChild2Name, child3Id: profile.current_holders == null || - profile.current_holders.find((x) => x.orgRevisionId == revisionId) == null || - profile.current_holders.find((x) => x.orgRevisionId == revisionId)?.orgChild3 == null + profile.current_holders.find((x) => x.orgRevisionId == revisionId) == null || + profile.current_holders.find((x) => x.orgRevisionId == revisionId)?.orgChild3 == null ? null : profile.current_holders.find((x) => x.orgRevisionId == revisionId)?.orgChild3Id, child3DnaId: profile.current_holders == null || - profile.current_holders.find((x) => x.orgRevisionId == revisionId) == null || - profile.current_holders.find((x) => x.orgRevisionId == revisionId)?.orgChild3 == null + profile.current_holders.find((x) => x.orgRevisionId == revisionId) == null || + profile.current_holders.find((x) => x.orgRevisionId == revisionId)?.orgChild3 == null ? null : profile.current_holders.find((x) => x.orgRevisionId == revisionId)?.ancestorDNA, child3: profile.current_holders == null || - profile.current_holders.find((x) => x.orgRevisionId == revisionId) == null || - profile.current_holders.find((x) => x.orgRevisionId == revisionId)?.orgChild3 == null + profile.current_holders.find((x) => x.orgRevisionId == revisionId) == null || + profile.current_holders.find((x) => x.orgRevisionId == revisionId)?.orgChild3 == null ? null : profile.current_holders.find((x) => x.orgRevisionId == revisionId)?.orgChild3 - .orgChild3Name, + .orgChild3Name, child4Id: profile.current_holders == null || - profile.current_holders.find((x) => x.orgRevisionId == revisionId) == null || - profile.current_holders.find((x) => x.orgRevisionId == revisionId)?.orgChild4 == null + profile.current_holders.find((x) => x.orgRevisionId == revisionId) == null || + profile.current_holders.find((x) => x.orgRevisionId == revisionId)?.orgChild4 == null ? null : profile.current_holders.find((x) => x.orgRevisionId == revisionId)?.orgChild4Id, child4DnaId: profile.current_holders == null || - profile.current_holders.find((x) => x.orgRevisionId == revisionId) == null || - profile.current_holders.find((x) => x.orgRevisionId == revisionId)?.orgChild4 == null + profile.current_holders.find((x) => x.orgRevisionId == revisionId) == null || + profile.current_holders.find((x) => x.orgRevisionId == revisionId)?.orgChild4 == null ? null : profile.current_holders.find((x) => x.orgRevisionId == revisionId)?.ancestorDNA, child4: profile.current_holders == null || - profile.current_holders.find((x) => x.orgRevisionId == revisionId) == null || - profile.current_holders.find((x) => x.orgRevisionId == revisionId)?.orgChild4 == null + profile.current_holders.find((x) => x.orgRevisionId == revisionId) == null || + profile.current_holders.find((x) => x.orgRevisionId == revisionId)?.orgChild4 == null ? null : profile.current_holders.find((x) => x.orgRevisionId == revisionId)?.orgChild4 - .orgChild4Name, + .orgChild4Name, }; return new HttpSuccess(_profile); } @@ -5509,8 +5504,8 @@ export class ProfileEmployeeController extends Controller { const formattedData = profiles.map((item) => { const posMaster = item.current_holders == null || - item.current_holders.length == 0 || - item.current_holders.find((x) => x.orgRevisionId == findRevision.id) == null + item.current_holders.length == 0 || + item.current_holders.find((x) => x.orgRevisionId == findRevision.id) == null ? null : item.current_holders.find((x) => x.orgRevisionId == findRevision.id); @@ -5518,49 +5513,49 @@ export class ProfileEmployeeController extends Controller { item.current_holders.length == 0 ? null : item.current_holders.find((x) => x.orgRevisionId == findRevision.id) != null && - item.current_holders.find((x) => x.orgRevisionId == findRevision.id)?.orgChild4 != - null + item.current_holders.find((x) => x.orgRevisionId == findRevision.id)?.orgChild4 != + null ? `${item.current_holders.find((x) => x.orgRevisionId == findRevision.id)?.orgChild4.orgChild4ShortName} ${item.current_holders.find((x) => x.orgRevisionId == findRevision.id)?.posMasterNo}` : item.current_holders.find((x) => x.orgRevisionId == findRevision.id) != null && - item.current_holders.find((x) => x.orgRevisionId == findRevision.id)?.orgChild3 != - null + item.current_holders.find((x) => x.orgRevisionId == findRevision.id)?.orgChild3 != + null ? `${item.current_holders.find((x) => x.orgRevisionId == findRevision.id)?.orgChild3.orgChild3ShortName} ${item.current_holders.find((x) => x.orgRevisionId == findRevision.id)?.posMasterNo}` : item.current_holders.find((x) => x.orgRevisionId == findRevision.id) != null && - item.current_holders.find((x) => x.orgRevisionId == findRevision.id)?.orgChild2 != - null + item.current_holders.find((x) => x.orgRevisionId == findRevision.id)?.orgChild2 != + null ? `${item.current_holders.find((x) => x.orgRevisionId == findRevision.id)?.orgChild2.orgChild2ShortName} ${item.current_holders.find((x) => x.orgRevisionId == findRevision.id)?.posMasterNo}` : item.current_holders.find((x) => x.orgRevisionId == findRevision.id) != null && - item.current_holders.find((x) => x.orgRevisionId == findRevision.id) - ?.orgChild1 != null + item.current_holders.find((x) => x.orgRevisionId == findRevision.id) + ?.orgChild1 != null ? `${item.current_holders.find((x) => x.orgRevisionId == findRevision.id)?.orgChild1.orgChild1ShortName} ${item.current_holders.find((x) => x.orgRevisionId == findRevision.id)?.posMasterNo}` : item.current_holders.find((x) => x.orgRevisionId == findRevision.id) != null && - item.current_holders.find((x) => x.orgRevisionId == findRevision.id) - ?.orgRoot != null + item.current_holders.find((x) => x.orgRevisionId == findRevision.id) + ?.orgRoot != null ? `${item.current_holders.find((x) => x.orgRevisionId == findRevision.id)?.orgRoot.orgRootShortName} ${item.current_holders.find((x) => x.orgRevisionId == findRevision.id)?.posMasterNo}` : null; const root = item.current_holders == null || - item.current_holders.find((x) => x.orgRevisionId == findRevision.id)?.orgRoot == null + item.current_holders.find((x) => x.orgRevisionId == findRevision.id)?.orgRoot == null ? null : item.current_holders.find((x) => x.orgRevisionId == findRevision.id)?.orgRoot; const child1 = item.current_holders == null || - item.current_holders.find((x) => x.orgRevisionId == findRevision.id)?.orgChild1 == null + item.current_holders.find((x) => x.orgRevisionId == findRevision.id)?.orgChild1 == null ? null : item.current_holders.find((x) => x.orgRevisionId == findRevision.id)?.orgChild1; const child2 = item.current_holders == null || - item.current_holders.find((x) => x.orgRevisionId == findRevision.id)?.orgChild2 == null + item.current_holders.find((x) => x.orgRevisionId == findRevision.id)?.orgChild2 == null ? null : item.current_holders.find((x) => x.orgRevisionId == findRevision.id)?.orgChild2; const child3 = item.current_holders == null || - item.current_holders.find((x) => x.orgRevisionId == findRevision.id)?.orgChild3 == null + item.current_holders.find((x) => x.orgRevisionId == findRevision.id)?.orgChild3 == null ? null : item.current_holders.find((x) => x.orgRevisionId == findRevision.id)?.orgChild3; const child4 = item.current_holders == null || - item.current_holders.find((x) => x.orgRevisionId == findRevision.id)?.orgChild4 == null + item.current_holders.find((x) => x.orgRevisionId == findRevision.id)?.orgChild4 == null ? null : item.current_holders.find((x) => x.orgRevisionId == findRevision.id)?.orgChild4; @@ -5683,24 +5678,24 @@ export class ProfileEmployeeController extends Controller { !profile.current_holders || profile.current_holders.length == 0 ? null : profile.current_holders.find((x) => x.orgRevisionId == orgRevision?.id) != null && - profile.current_holders.find((x) => x.orgRevisionId == orgRevision?.id)?.orgChild4 != - null + profile.current_holders.find((x) => x.orgRevisionId == orgRevision?.id)?.orgChild4 != + null ? `${profile.current_holders.find((x) => x.orgRevisionId == orgRevision?.id)?.orgChild4.orgChild4ShortName}` : profile.current_holders.find((x) => x.orgRevisionId == orgRevision?.id) != null && - profile.current_holders.find((x) => x.orgRevisionId == orgRevision?.id)?.orgChild3 != - null + profile.current_holders.find((x) => x.orgRevisionId == orgRevision?.id)?.orgChild3 != + null ? `${profile.current_holders.find((x) => x.orgRevisionId == orgRevision?.id)?.orgChild3.orgChild3ShortName}` : profile.current_holders.find((x) => x.orgRevisionId == orgRevision?.id) != null && - profile.current_holders.find((x) => x.orgRevisionId == orgRevision?.id) - ?.orgChild2 != null + profile.current_holders.find((x) => x.orgRevisionId == orgRevision?.id) + ?.orgChild2 != null ? `${profile.current_holders.find((x) => x.orgRevisionId == orgRevision?.id)?.orgChild2.orgChild2ShortName}` : profile.current_holders.find((x) => x.orgRevisionId == orgRevision?.id) != null && - profile.current_holders.find((x) => x.orgRevisionId == orgRevision?.id) - ?.orgChild1 != null + profile.current_holders.find((x) => x.orgRevisionId == orgRevision?.id) + ?.orgChild1 != null ? `${profile.current_holders.find((x) => x.orgRevisionId == orgRevision?.id)?.orgChild1.orgChild1ShortName}` : profile.current_holders.find((x) => x.orgRevisionId == orgRevision?.id) != null && - profile.current_holders.find((x) => x.orgRevisionId == orgRevision?.id) - ?.orgRoot != null + profile.current_holders.find((x) => x.orgRevisionId == orgRevision?.id) + ?.orgRoot != null ? `${profile.current_holders.find((x) => x.orgRevisionId == orgRevision?.id)?.orgRoot.orgRootShortName}` : null; const dest_item = await this.salaryRepo.findOne({ @@ -6161,7 +6156,7 @@ export class ProfileEmployeeController extends Controller { positionId: profile.positionIdTemp, profileId: profile.id, }) - .then(async () => {}); + .then(async () => { }); } }), ); @@ -6255,33 +6250,33 @@ export class ProfileEmployeeController extends Controller { item.current_holders.length == 0 ? null : item.current_holders.find((x) => x.orgRevisionId == findRevision.id) != null && - item.current_holders.find((x) => x.orgRevisionId == findRevision.id)?.orgChild4 != - null + item.current_holders.find((x) => x.orgRevisionId == findRevision.id)?.orgChild4 != + null ? `${item.current_holders.find((x) => x.orgRevisionId == findRevision.id)?.orgChild4.orgChild4ShortName} ${item.current_holders.find((x) => x.orgRevisionId == findRevision.id)?.posMasterNo}` : item.current_holders.find((x) => x.orgRevisionId == findRevision.id) != null && - item.current_holders.find((x) => x.orgRevisionId == findRevision.id) - ?.orgChild3 != null + item.current_holders.find((x) => x.orgRevisionId == findRevision.id) + ?.orgChild3 != null ? `${item.current_holders.find((x) => x.orgRevisionId == findRevision.id)?.orgChild3.orgChild3ShortName} ${item.current_holders.find((x) => x.orgRevisionId == findRevision.id)?.posMasterNo}` : item.current_holders.find((x) => x.orgRevisionId == findRevision.id) != null && - item.current_holders.find((x) => x.orgRevisionId == findRevision.id) - ?.orgChild2 != null + item.current_holders.find((x) => x.orgRevisionId == findRevision.id) + ?.orgChild2 != null ? `${item.current_holders.find((x) => x.orgRevisionId == findRevision.id)?.orgChild2.orgChild2ShortName} ${item.current_holders.find((x) => x.orgRevisionId == findRevision.id)?.posMasterNo}` : item.current_holders.find((x) => x.orgRevisionId == findRevision.id) != - null && - item.current_holders.find((x) => x.orgRevisionId == findRevision.id) - ?.orgChild1 != null + null && + item.current_holders.find((x) => x.orgRevisionId == findRevision.id) + ?.orgChild1 != null ? `${item.current_holders.find((x) => x.orgRevisionId == findRevision.id)?.orgChild1.orgChild1ShortName} ${item.current_holders.find((x) => x.orgRevisionId == findRevision.id)?.posMasterNo}` : item.current_holders.find((x) => x.orgRevisionId == findRevision.id) != - null && - item.current_holders.find((x) => x.orgRevisionId == findRevision.id) - ?.orgRoot != null + null && + item.current_holders.find((x) => x.orgRevisionId == findRevision.id) + ?.orgRoot != null ? `${item.current_holders.find((x) => x.orgRevisionId == findRevision.id)?.orgRoot.orgRootShortName} ${item.current_holders.find((x) => x.orgRevisionId == findRevision.id)?.posMasterNo}` : null; root = item.current_holders.length == 0 || - (item.current_holders.find((x) => x.orgRevisionId == findRevision.id) != null && - item.current_holders.find((x) => x.orgRevisionId == findRevision.id)?.orgRoot == null) + (item.current_holders.find((x) => x.orgRevisionId == findRevision.id) != null && + item.current_holders.find((x) => x.orgRevisionId == findRevision.id)?.orgRoot == null) ? null : item.current_holders.find((x) => x.orgRevisionId == findRevision.id)?.orgRoot; root = root == null ? null : root.orgRootName; @@ -6432,66 +6427,40 @@ export class ProfileEmployeeController extends Controller { }); const posMaster = profile.current_holders == null || - profile.current_holders.length == 0 || - profile.current_holders.find((x) => x.orgRevisionId == orgRevisionPublish.id) == null + profile.current_holders.length == 0 || + profile.current_holders.find((x) => x.orgRevisionId == orgRevisionPublish.id) == null ? null : profile.current_holders.find((x) => x.orgRevisionId == orgRevisionPublish.id); const root = profile.current_holders == null || - profile.current_holders.find((x) => x.orgRevisionId == orgRevisionPublish.id)?.orgRoot == null + profile.current_holders.find((x) => x.orgRevisionId == orgRevisionPublish.id)?.orgRoot == null ? null : profile.current_holders.find((x) => x.orgRevisionId == orgRevisionPublish.id)?.orgRoot; const child1 = profile.current_holders == null || - profile.current_holders.find((x) => x.orgRevisionId == orgRevisionPublish.id)?.orgChild1 == + profile.current_holders.find((x) => x.orgRevisionId == orgRevisionPublish.id)?.orgChild1 == null ? null : profile.current_holders.find((x) => x.orgRevisionId == orgRevisionPublish.id)?.orgChild1; const child2 = profile.current_holders == null || - profile.current_holders.find((x) => x.orgRevisionId == orgRevisionPublish.id)?.orgChild2 == + profile.current_holders.find((x) => x.orgRevisionId == orgRevisionPublish.id)?.orgChild2 == null ? null : profile.current_holders.find((x) => x.orgRevisionId == orgRevisionPublish.id)?.orgChild2; const child3 = profile.current_holders == null || - profile.current_holders.find((x) => x.orgRevisionId == orgRevisionPublish.id)?.orgChild3 == + profile.current_holders.find((x) => x.orgRevisionId == orgRevisionPublish.id)?.orgChild3 == null ? null : profile.current_holders.find((x) => x.orgRevisionId == orgRevisionPublish.id)?.orgChild3; const child4 = profile.current_holders == null || - profile.current_holders.find((x) => x.orgRevisionId == orgRevisionPublish.id)?.orgChild4 == + profile.current_holders.find((x) => x.orgRevisionId == orgRevisionPublish.id)?.orgChild4 == null ? null : profile.current_holders.find((x) => x.orgRevisionId == orgRevisionPublish.id)?.orgChild4; - const shortName = - profile.current_holders.length == 0 - ? null - : profile.current_holders.find((x) => x.orgRevisionId == orgRevisionPublish.id) != null && - profile.current_holders.find((x) => x.orgRevisionId == orgRevisionPublish.id) - ?.orgChild4 != null - ? `${profile.current_holders.find((x) => x.orgRevisionId == orgRevisionPublish.id)?.orgChild4.orgChild4ShortName} ${profile.current_holders.find((x) => x.orgRevisionId == orgRevisionPublish.id)?.posMasterNo}` - : profile.current_holders.find((x) => x.orgRevisionId == orgRevisionPublish.id) != null && - profile.current_holders.find((x) => x.orgRevisionId == orgRevisionPublish.id) - ?.orgChild3 != null - ? `${profile.current_holders.find((x) => x.orgRevisionId == orgRevisionPublish.id)?.orgChild3.orgChild3ShortName} ${profile.current_holders.find((x) => x.orgRevisionId == orgRevisionPublish.id)?.posMasterNo}` - : profile.current_holders.find((x) => x.orgRevisionId == orgRevisionPublish.id) != - null && - profile.current_holders.find((x) => x.orgRevisionId == orgRevisionPublish.id) - ?.orgChild2 != null - ? `${profile.current_holders.find((x) => x.orgRevisionId == orgRevisionPublish.id)?.orgChild2.orgChild2ShortName} ${profile.current_holders.find((x) => x.orgRevisionId == orgRevisionPublish.id)?.posMasterNo}` - : profile.current_holders.find((x) => x.orgRevisionId == orgRevisionPublish.id) != - null && - profile.current_holders.find((x) => x.orgRevisionId == orgRevisionPublish.id) - ?.orgChild1 != null - ? `${profile.current_holders.find((x) => x.orgRevisionId == orgRevisionPublish.id)?.orgChild1.orgChild1ShortName} ${profile.current_holders.find((x) => x.orgRevisionId == orgRevisionPublish.id)?.posMasterNo}` - : profile.current_holders.find((x) => x.orgRevisionId == orgRevisionPublish.id) != - null && - profile.current_holders.find((x) => x.orgRevisionId == orgRevisionPublish.id) - ?.orgRoot != null - ? `${profile.current_holders.find((x) => x.orgRevisionId == orgRevisionPublish.id)?.orgRoot.orgRootShortName} ${profile.current_holders.find((x) => x.orgRevisionId == orgRevisionPublish.id)?.posMasterNo}` - : null; + const _numPart = posMaster ? [posMaster.posMasterNoPrefix, posMaster.posMasterNo, posMaster.posMasterNoSuffix].filter((p) => p !== null && p !== undefined && p !== '').join(' ') : ''; const _profile: any = { profileId: profile.id, prefix: profile.prefix, @@ -6533,7 +6502,7 @@ export class ProfileEmployeeController extends Controller { child4ShortName: child4 == null ? null : child4.orgChild4ShortName, node: null, nodeId: null, - posNo: shortName, + posNo: null, salary: profile.amount, education: profile && profile.profileEducations.length > 0 @@ -6548,22 +6517,27 @@ export class ProfileEmployeeController extends Controller { _profile.node = 4; _profile.nodeId = _profile.child4Id; _profile.nodeShortName = _profile.child4ShortName; + _profile.posNo = `${_profile.child4ShortName} ${_numPart}`; } else if (_profile.child3Id != null) { _profile.node = 3; _profile.nodeId = _profile.child3Id; _profile.nodeShortName = _profile.child3ShortName; + _profile.posNo = `${_profile.child3ShortName} ${_numPart}`; } else if (_profile.child2Id != null) { _profile.node = 2; _profile.nodeId = _profile.child2Id; _profile.nodeShortName = _profile.child2ShortName; + _profile.posNo = `${_profile.child2ShortName} ${_numPart}`; } else if (_profile.child1Id != null) { _profile.node = 1; _profile.nodeId = _profile.child1Id; _profile.nodeShortName = _profile.child1ShortName; + _profile.posNo = `${_profile.child1ShortName} ${_numPart}`; } else if (_profile.rootId != null) { _profile.node = 0; _profile.nodeId = _profile.rootId; _profile.nodeShortName = _profile.rootShortName; + _profile.posNo = `${_profile.rootShortName} ${_numPart}`; } return new HttpSuccess(_profile); } diff --git a/src/controllers/ProfileEmployeeTempController.ts b/src/controllers/ProfileEmployeeTempController.ts index a8c017ae..406dce69 100644 --- a/src/controllers/ProfileEmployeeTempController.ts +++ b/src/controllers/ProfileEmployeeTempController.ts @@ -1001,6 +1001,24 @@ export class ProfileEmployeeTempController extends Controller { } const record = await this.profileRepo.findOneBy({ id }); + const before = structuredClone(record); + // เช็คว่ามี profileHistory ของ profile นี้หรือไม่ + const historyCount = await this.profileHistoryRepo.count({ + where: { profileEmployeeId: id }, + }); + + // ถ้าไม่มีเลย ให้บันทึกข้อมูลเริ่มต้น (ก่อน update) ลงไปก่อน + if (historyCount === 0) { + await this.profileHistoryRepo.save( + Object.assign(new ProfileEmployeeHistory(), { + ...before, + birthDateOld: before?.birthDate, + profileEmployeeId: id, + id: undefined, + }), + ); + } + if (!record) throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูลโปรไฟล์นี้"); if (body.employeeClass == null || body.employeeClass == undefined || body.employeeClass == "") { diff --git a/src/controllers/ProfileGovernmentEmployeeController.ts b/src/controllers/ProfileGovernmentEmployeeController.ts index d97a452a..7fdeb9a7 100644 --- a/src/controllers/ProfileGovernmentEmployeeController.ts +++ b/src/controllers/ProfileGovernmentEmployeeController.ts @@ -115,7 +115,7 @@ export class ProfileGovernmentEmployeeController extends Controller { record.posType == null && record.posLevel == null ? null : `${record.posType.posTypeShortName} ${record.posLevel.posLevelName}`, //ระดับ - posMasterNo: posMaster == null ? null : `${orgShortName} ${posMaster.posMasterNoPrefix ?? ''}${posMaster.posMasterNo ?? ''}${posMaster.posMasterNoSuffix ?? ''}`, //เลขที่ตำแหน่ง + posMasterNo: posMaster == null ? null : `${orgShortName} ${[posMaster.posMasterNoPrefix, posMaster.posMasterNo, posMaster.posMasterNoSuffix].filter((p) => p !== null && p !== undefined && p !== '').join(' ')}`, //เลขที่ตำแหน่ง posType: record.posType == null ? null : record.posType.posTypeName, //ประเภท dateLeave: record.birthDate == null ? null : calculateRetireDate(record.birthDate), dateRetireLaw: record.dateRetireLaw ?? null, @@ -281,7 +281,7 @@ export class ProfileGovernmentEmployeeController extends Controller { record?.isLeave == false ? posMaster == null ? null - : `${orgShortName} ${posMaster.posMasterNoPrefix ?? ''}${posMaster.posMasterNo ?? ''}${posMaster.posMasterNoSuffix ?? ''}` + : `${orgShortName} ${[posMaster.posMasterNoPrefix, posMaster.posMasterNo, posMaster.posMasterNoSuffix].filter((p) => p !== null && p !== undefined && p !== '').join(' ')}` : posNoLeave /*record && record?.profileSalary.length > 0 ? `${record?.profileSalary[0].posNoAbb} ${record?.profileSalary[0].posNo}` : null*/, //เลขที่ตำแหน่ง @@ -441,7 +441,7 @@ export class ProfileGovernmentEmployeeController extends Controller { record?.isLeave == false ? posMaster == null ? null - : `${orgShortName} ${posMaster.posMasterNoPrefix ?? ''}${posMaster.posMasterNo ?? ''}${posMaster.posMasterNoSuffix ?? ''}` + : `${orgShortName} ${[posMaster.posMasterNoPrefix, posMaster.posMasterNo, posMaster.posMasterNoSuffix].filter((p) => p !== null && p !== undefined && p !== '').join(' ')}` : posNoLeave /*record && record.profileSalary.length > 0 ? `${record?.profileSalary[0].posNoAbb} ${record?.profileSalary[0].posNo}` : null*/, //เลขที่ตำแหน่ง diff --git a/src/controllers/ProfileSalaryController.ts b/src/controllers/ProfileSalaryController.ts index 277ac081..8abe9aa2 100644 --- a/src/controllers/ProfileSalaryController.ts +++ b/src/controllers/ProfileSalaryController.ts @@ -23,12 +23,21 @@ import { ProfileEmployee } from "../entities/ProfileEmployee"; import { In, IsNull, LessThan, MoreThan, Not } from "typeorm"; import permission from "../interfaces/permission"; import { setLogDataDiff } from "../interfaces/utils"; -import { calculateTenure } from "../utils/tenure"; -import { TenurePositionOfficer } from "../entities/TenurePositionOfficer"; -import { TenureLevelOfficer } from "../entities/TenureLevelOfficer"; -import { TenurePositionEmployee } from "../entities/TenurePositionEmployee"; -import { TenureLevelEmployee } from "../entities/TenureLevelEmployee"; -import { TenurePositionExecutiveOfficer } from "../entities/TenurePositionExecutiveOfficer"; +import { normalizeDurationSumSimple } from "../utils/tenure"; +import { + TenurePositionOfficer, + CreateTenurePositionOfficer, +} from "../entities/TenurePositionOfficer"; +import { TenureLevelOfficer, CreateTenureLevelOfficer } from "../entities/TenureLevelOfficer"; +import { + TenurePositionEmployee, + CreateTenurePositionEmployee, +} from "../entities/TenurePositionEmployee"; +import { TenureLevelEmployee, CreateTenureLevelEmployee } from "../entities/TenureLevelEmployee"; +import { + TenurePositionExecutiveOfficer, + CreateTenurePositionExecutiveOfficer, +} from "../entities/TenurePositionExecutiveOfficer"; import { Command } from "../entities/Command"; import { OrgRoot } from "../entities/OrgRoot"; import { OrgRevision } from "../entities/OrgRevision"; @@ -46,138 +55,303 @@ export class ProfileSalaryController extends Controller { private profileEmployeeRepo = AppDataSource.getRepository(ProfileEmployee); private salaryRepo = AppDataSource.getRepository(ProfileSalary); private salaryHistoryRepo = AppDataSource.getRepository(ProfileSalaryHistory); - private positionOfficerRepo = AppDataSource.getRepository(TenurePositionOfficer); - private positionEmployeeRepo = AppDataSource.getRepository(TenurePositionEmployee); - private levelOfficerRepo = AppDataSource.getRepository(TenureLevelOfficer); - private levelEmployeeRepo = AppDataSource.getRepository(TenureLevelEmployee); - private positionExecutiveOfficerRepo = AppDataSource.getRepository( - TenurePositionExecutiveOfficer, - ); private commandRepository = AppDataSource.getRepository(Command); private orgRootRepository = AppDataSource.getRepository(OrgRoot); - private orgRevisionRepository = AppDataSource.getRepository(OrgRevision); - private positionRepo = AppDataSource.getRepository(Position); private registryRepo = AppDataSource.getRepository(Registry); private registryEmployeeRepo = AppDataSource.getRepository(RegistryEmployee); @Get("TenurePositionOfficer") public async cronjobTenurePositionOfficer() { - let data: any = []; - await this.positionOfficerRepo.clear(); - const profile = await this.profileRepo.find(); const CURRENT_DATE = await AppDataSource.query("SELECT CURRENT_DATE() as today"); const baseCurrentDate = CURRENT_DATE[0].today; - for await (const x of profile) { - // Use leave date if available and valid, otherwise use current date - let _currentDate = baseCurrentDate; - if (x.isLeave && x.leaveDate) { - _currentDate = Extension.toDateOnlyString(x.leaveDate); + + const profiles = await this.profileRepo.find({ + select: ["id", "position", "isLeave", "leaveDate"], + where: { position: Not(IsNull()) }, + }); + + const BATCH_SIZE = 50; + let successCount = 0; + let failCount = 0; + const allData: CreateTenurePositionOfficer[] = []; + + for (let i = 0; i < profiles.length; i += BATCH_SIZE) { + const batch = profiles.slice(i, Math.min(i + BATCH_SIZE, profiles.length)); + const results = await Promise.allSettled( + batch.map((profile) => + this.processSingleProfileForTenurePositionOfficer(profile, baseCurrentDate), + ), + ); + + results.forEach((result) => { + if (result.status === "fulfilled" && result.value) { + allData.push(result.value); + successCount++; + } else { + failCount++; + } + }); + } + + await AppDataSource.transaction(async (transactionalEntityManager) => { + await transactionalEntityManager.delete(TenurePositionOfficer, {}); + if (allData.length > 0) { + const entities = allData.map((data) => { + const entity = new TenurePositionOfficer(); + Object.assign(entity, data); + return entity; + }); + await transactionalEntityManager.save(TenurePositionOfficer, entities); } + }); + + return new HttpSuccess({ + message: `อัปเดต tenure position officer สำเร็จ`, + total: profiles.length, + success: successCount, + failed: failCount, + }); + } + + private async processSingleProfileForTenurePositionOfficer( + profile: Pick, + baseCurrentDate: string, + ): Promise { + try { + let _currentDate = baseCurrentDate; + if (profile.isLeave && profile.leaveDate) { + _currentDate = Extension.toDateOnlyString(profile.leaveDate); + } + const position = await AppDataSource.query("CALL GetProfileSalaryPosition(?, ?)", [ - x.id, + profile.id, _currentDate, ]); const _position = position.length > 0 ? position[0] : []; + const mapPosition = _position.length > 1 ? _position.slice(1).map((curr: any, index: number) => ({ - days_diff: curr.days_diff, positionName: _position[index]?.positionName, + year: + curr.Years !== null && curr.Years !== undefined + ? Math.floor(Number(curr.Years)) + : 0, + month: + curr.Months !== null && curr.Months !== undefined + ? Math.floor(Number(curr.Months)) + : 0, + day: + curr.Days !== null && curr.Days !== undefined ? Math.floor(Number(curr.Days)) : 0, })) : []; - const calDayDiff = mapPosition - .filter((curr: any) => curr.positionName == x.position) - .reduce( - (acc: any, curr: any) => { - acc.days_diff += Number(curr.days_diff) || 0; - acc.positionName = curr.positionName; - return acc; - }, - { days_diff: 0, positionName: null }, - ); - const { year, month, day } = calculateTenure(calDayDiff.days_diff); - const mapData: any = { - profileId: x.id, - positionName: calDayDiff.positionName, - days_diff: calDayDiff.days_diff, - Years: year, - Months: month, - Days: day, - }; - data.push(mapData); - } - await this.positionOfficerRepo.save(data); - return new HttpSuccess(); + const currentTenure = mapPosition.find((curr: any) => curr.positionName === profile.position); + + if (currentTenure) { + const normalized = normalizeDurationSumSimple( + currentTenure.year, + currentTenure.month, + currentTenure.day, + ); + return { + profileId: profile.id, + positionName: currentTenure.positionName, + days_diff: null, + Years: normalized.years, + Months: normalized.months, + Days: normalized.days, + }; + } + return null; + } catch (error) { + return null; + } } @Get("TenurePositionEmployee") public async cronjobTenurePositionEmployee() { - let data: any = []; - await this.positionEmployeeRepo.clear(); const CURRENT_DATE = await AppDataSource.query("SELECT CURRENT_DATE() as today"); const baseCurrentDate = CURRENT_DATE[0].today; - const profile = await this.profileEmployeeRepo.find(); - for await (const x of profile) { - // Use leave date if available and valid, otherwise use current date - let _currentDate = baseCurrentDate; - if (x?.isLeave && x.leaveDate) { - _currentDate = Extension.toDateOnlyString(x.leaveDate); + + const profiles = await this.profileEmployeeRepo.find({ + select: ["id", "position", "isLeave", "leaveDate"], + where: { position: Not(IsNull()) }, + }); + + const BATCH_SIZE = 50; + let successCount = 0; + let failCount = 0; + const allData: CreateTenurePositionEmployee[] = []; + + for (let i = 0; i < profiles.length; i += BATCH_SIZE) { + const batch = profiles.slice(i, Math.min(i + BATCH_SIZE, profiles.length)); + const results = await Promise.allSettled( + batch.map((profile) => + this.processSingleProfileForTenurePositionEmployee(profile, baseCurrentDate), + ), + ); + + results.forEach((result) => { + if (result.status === "fulfilled" && result.value) { + allData.push(result.value); + successCount++; + } else { + failCount++; + } + }); + } + + await AppDataSource.transaction(async (transactionalEntityManager) => { + await transactionalEntityManager.delete(TenurePositionEmployee, {}); + if (allData.length > 0) { + const entities = allData.map((data) => { + const entity = new TenurePositionEmployee(); + Object.assign(entity, data); + return entity; + }); + await transactionalEntityManager.save(TenurePositionEmployee, entities); } + }); + + return new HttpSuccess({ + message: `อัปเดต tenure position employee สำเร็จ`, + total: profiles.length, + success: successCount, + failed: failCount, + }); + } + + private async processSingleProfileForTenurePositionEmployee( + profile: Pick, + baseCurrentDate: string, + ): Promise { + try { + let _currentDate = baseCurrentDate; + if (profile.isLeave && profile.leaveDate) { + _currentDate = Extension.toDateOnlyString(profile.leaveDate); + } + const position = await AppDataSource.query("CALL GetProfileEmployeeSalaryPosition(?, ?)", [ - x.id, + profile.id, _currentDate, ]); const _position = position.length > 0 ? position[0] : []; + const mapPosition = _position.length > 1 ? _position.slice(1).map((curr: any, index: number) => ({ - days_diff: curr.days_diff, positionName: _position[index]?.positionName, + year: + curr.Years !== null && curr.Years !== undefined + ? Math.floor(Number(curr.Years)) + : 0, + month: + curr.Months !== null && curr.Months !== undefined + ? Math.floor(Number(curr.Months)) + : 0, + day: + curr.Days !== null && curr.Days !== undefined ? Math.floor(Number(curr.Days)) : 0, })) : []; - const calDayDiff = mapPosition - .filter((curr: any) => curr.positionName == x.position) - .reduce( - (acc: any, curr: any) => { - acc.days_diff += Number(curr.days_diff) || 0; - acc.positionName = curr.positionName; - return acc; - }, - { days_diff: 0, positionName: null }, - ); - const { year, month, day } = calculateTenure(calDayDiff.days_diff); - const mapData: any = { - profileEmployeeId: x.id, - positionName: calDayDiff.positionName, - days_diff: calDayDiff.days_diff, - Years: year, - Months: month, - Days: day, - }; - data.push(mapData); - } - await this.positionEmployeeRepo.save(data); - return new HttpSuccess(); + const currentTenure = mapPosition.find((curr: any) => curr.positionName === profile.position); + + if (currentTenure) { + const normalized = normalizeDurationSumSimple( + currentTenure.year, + currentTenure.month, + currentTenure.day, + ); + return { + profileEmployeeId: profile.id, + positionName: currentTenure.positionName, + days_diff: null, + Years: normalized.years, + Months: normalized.months, + Days: normalized.days, + }; + } + return null; + } catch (error) { + return null; + } } @Get("TenureLevelOfficer") public async cronjobTenureLevelOfficer() { - let data: any = []; - await this.levelOfficerRepo.clear(); - const profile = await this.profileRepo.find({ relations: ["posLevel", "posType"] }); const CURRENT_DATE = await AppDataSource.query("SELECT CURRENT_DATE() as today"); const baseCurrentDate = CURRENT_DATE[0].today; - for await (const x of profile) { - // Use leave date if available and valid, otherwise use current date - let _currentDate = baseCurrentDate; - if (x?.isLeave && x.leaveDate) { - _currentDate = Extension.toDateOnlyString(x.leaveDate); + + const profiles = await this.profileRepo.find({ + relations: ["posLevel", "posType"], + select: ["id", "isLeave", "leaveDate", "posLevel", "posType"], + where: { + posLevel: Not(IsNull()), + posType: Not(IsNull()), + }, + }); + + const BATCH_SIZE = 50; + let successCount = 0; + let failCount = 0; + const allData: CreateTenureLevelOfficer[] = []; + + for (let i = 0; i < profiles.length; i += BATCH_SIZE) { + const batch = profiles.slice(i, Math.min(i + BATCH_SIZE, profiles.length)); + const results = await Promise.allSettled( + batch.map((profile) => + this.processSingleProfileForTenureLevelOfficer(profile, baseCurrentDate), + ), + ); + + results.forEach((result) => { + if (result.status === "fulfilled" && result.value) { + allData.push(result.value); + successCount++; + } else { + failCount++; + } + }); + } + + await AppDataSource.transaction(async (transactionalEntityManager) => { + await transactionalEntityManager.delete(TenureLevelOfficer, {}); + if (allData.length > 0) { + const entities = allData.map((data) => { + const entity = new TenureLevelOfficer(); + Object.assign(entity, data); + return entity; + }); + await transactionalEntityManager.save(TenureLevelOfficer, entities); } + }); + + return new HttpSuccess({ + message: `อัปเดต tenure level officer สำเร็จ`, + total: profiles.length, + success: successCount, + failed: failCount, + }); + } + + private async processSingleProfileForTenureLevelOfficer( + profile: Pick & { + posLevel?: { posLevelName?: string } | null; + posType?: { posTypeName?: string } | null; + }, + baseCurrentDate: string, + ): Promise { + try { + let _currentDate = baseCurrentDate; + if (profile.isLeave && profile.leaveDate) { + _currentDate = Extension.toDateOnlyString(profile.leaveDate); + } + const positionLevel = await AppDataSource.query("CALL GetProfileSalaryLevel(?, ?)", [ - x.id, + profile.id, _currentDate, ]); const _positionLevel = positionLevel.length > 0 ? positionLevel[0] : []; + const mapPositionLevel = _positionLevel.length > 1 ? _positionLevel.slice(1).map((curr: any, index: number) => ({ @@ -185,13 +359,24 @@ export class ProfileSalaryController extends Controller { positionType: _positionLevel[index]?.positionType, positionLevel: _positionLevel[index]?.positionLevel, positionCee: _positionLevel[index]?.positionCee, + year: + curr.Years !== null && curr.Years !== undefined + ? Math.floor(Number(curr.Years)) + : 0, + month: + curr.Months !== null && curr.Months !== undefined + ? Math.floor(Number(curr.Months)) + : 0, + day: + curr.Days !== null && curr.Days !== undefined ? Math.floor(Number(curr.Days)) : 0, })) : []; + const calDayDiff = mapPositionLevel .filter( (curr: any) => - curr.positionLevel == (x.posLevel?.posLevelName ?? null) && - curr.positionType == (x.posType?.posTypeName ?? null), + curr.positionLevel === (profile.posLevel?.posLevelName ?? null) && + curr.positionType === (profile.posType?.posTypeName ?? null), ) .reduce( (acc: any, curr: any) => { @@ -199,45 +384,118 @@ export class ProfileSalaryController extends Controller { acc.positionType = curr.positionType; acc.positionLevel = curr.positionLevel; acc.positionCee = curr.positionCee; + acc.year += curr.year; + acc.month += curr.month; + acc.day += curr.day; return acc; }, - { days_diff: 0, positionType: null, positionLevel: null, positionCee: null }, + { + days_diff: 0, + positionType: null, + positionLevel: null, + positionCee: null, + year: 0, + month: 0, + day: 0, + }, ); - const { year, month, day } = calculateTenure(calDayDiff.days_diff); - const mapData: any = { - profileId: x.id, + + const normalized = normalizeDurationSumSimple( + calDayDiff.year, + calDayDiff.month, + calDayDiff.day, + ); + + return { + profileId: profile.id, positionType: calDayDiff.positionType, positionLevel: calDayDiff.positionLevel, positionCee: calDayDiff.positionCee, days_diff: calDayDiff.days_diff, - Years: x.posLevel == null ? 0 : year.toFixed(4), - Months: x.posLevel == null ? 0 : month.toFixed(4), - Days: x.posLevel == null ? 0 : day.toFixed(4), + Years: profile.posLevel == null ? 0 : normalized.years, + Months: profile.posLevel == null ? 0 : normalized.months, + Days: profile.posLevel == null ? 0 : normalized.days, }; - data.push(mapData); + } catch (error) { + return null; } - await this.levelOfficerRepo.save(data); - - return new HttpSuccess(); } @Get("TenureLevelEmployee") public async cronjobTenureLevelEmployee() { - let data: any = []; - await this.levelEmployeeRepo.clear(); - const profile = await this.profileEmployeeRepo.find({ relations: ["posLevel", "posType"] }); const CURRENT_DATE = await AppDataSource.query("SELECT CURRENT_DATE() as today"); const baseCurrentDate = CURRENT_DATE[0].today; - for await (const x of profile) { - // Use leave date if available and valid, otherwise use current date - let _currentDate = baseCurrentDate; - if (x?.isLeave && x.leaveDate) { - _currentDate = Extension.toDateOnlyString(x.leaveDate); + + const profiles = await this.profileEmployeeRepo.find({ + relations: ["posLevel", "posType"], + select: ["id", "isLeave", "leaveDate", "posLevel", "posType"], + where: { + posLevel: Not(IsNull()), + posType: Not(IsNull()), + }, + }); + + const BATCH_SIZE = 50; + let successCount = 0; + let failCount = 0; + const allData: CreateTenureLevelEmployee[] = []; + + for (let i = 0; i < profiles.length; i += BATCH_SIZE) { + const batch = profiles.slice(i, Math.min(i + BATCH_SIZE, profiles.length)); + const results = await Promise.allSettled( + batch.map((profile) => + this.processSingleProfileForTenureLevelEmployee(profile, baseCurrentDate), + ), + ); + + results.forEach((result) => { + if (result.status === "fulfilled" && result.value) { + allData.push(result.value); + successCount++; + } else { + failCount++; + } + }); + } + + await AppDataSource.transaction(async (transactionalEntityManager) => { + await transactionalEntityManager.delete(TenureLevelEmployee, {}); + if (allData.length > 0) { + const entities = allData.map((data) => { + const entity = new TenureLevelEmployee(); + Object.assign(entity, data); + return entity; + }); + await transactionalEntityManager.save(TenureLevelEmployee, entities); } + }); + + return new HttpSuccess({ + message: `อัปเดต tenure level employee สำเร็จ`, + total: profiles.length, + success: successCount, + failed: failCount, + }); + } + + private async processSingleProfileForTenureLevelEmployee( + profile: Pick & { + posLevel?: { posLevelName?: string } | null; + posType?: { posTypeName?: string } | null; + }, + baseCurrentDate: string, + ): Promise { + try { + let _currentDate = baseCurrentDate; + if (profile.isLeave && profile.leaveDate) { + _currentDate = Extension.toDateOnlyString(profile.leaveDate); + } + const positionLevel = await AppDataSource.query("CALL GetProfileEmployeeSalaryLevel(?, ?)", [ - x.id, + profile.id, _currentDate, ]); const _positionLevel = positionLevel.length > 0 ? positionLevel[0] : []; + const mapPositionLevel = _positionLevel.length > 1 ? _positionLevel.slice(1).map((curr: any, index: number) => ({ @@ -245,13 +503,24 @@ export class ProfileSalaryController extends Controller { positionType: _positionLevel[index]?.positionType, positionLevel: _positionLevel[index]?.positionLevel, positionCee: _positionLevel[index]?.positionCee, + year: + curr.Years !== null && curr.Years !== undefined + ? Math.floor(Number(curr.Years)) + : 0, + month: + curr.Months !== null && curr.Months !== undefined + ? Math.floor(Number(curr.Months)) + : 0, + day: + curr.Days !== null && curr.Days !== undefined ? Math.floor(Number(curr.Days)) : 0, })) : []; + const calDayDiff = mapPositionLevel .filter( (curr: any) => - curr.positionLevel == (x.posLevel?.posLevelName ?? null) && - curr.positionType == (x.posType?.posTypeName ?? null), + curr.positionLevel === (profile.posLevel?.posLevelName ?? null) && + curr.positionType === (profile.posType?.posTypeName ?? null), ) .reduce( (acc: any, curr: any) => { @@ -259,97 +528,161 @@ export class ProfileSalaryController extends Controller { acc.positionType = curr.positionType; acc.positionLevel = curr.positionLevel; acc.positionCee = curr.positionCee; + acc.year += curr.year; + acc.month += curr.month; + acc.day += curr.day; return acc; }, - { days_diff: 0, positionType: null, positionLevel: null, positionCee: null }, + { + days_diff: 0, + positionType: null, + positionLevel: null, + positionCee: null, + year: 0, + month: 0, + day: 0, + }, ); - const { year, month, day } = calculateTenure(calDayDiff.days_diff); - const mapData: any = { - profileEmployeeId: x.id, + + const normalized = normalizeDurationSumSimple( + calDayDiff.year, + calDayDiff.month, + calDayDiff.day, + ); + + return { + profileEmployeeId: profile.id, positionType: calDayDiff.positionType, positionLevel: calDayDiff.positionLevel, positionCee: calDayDiff.positionCee, days_diff: calDayDiff.days_diff, - Years: x.posLevel == null ? 0 : year.toFixed(4), - Months: x.posLevel == null ? 0 : month.toFixed(4), - Days: x.posLevel == null ? 0 : day.toFixed(4), + Years: profile.posLevel == null ? 0 : normalized.years, + Months: profile.posLevel == null ? 0 : normalized.months, + Days: profile.posLevel == null ? 0 : normalized.days, }; - data.push(mapData); + } catch (error) { + return null; } - await this.levelEmployeeRepo.save(data); - - return new HttpSuccess(); } @Get("TenurePositionExecutiveOfficer") public async cronjobTenureExecutivePositionOfficer() { - let data: any = []; - await this.positionExecutiveOfficerRepo.clear(); - const profile = await this.profileRepo.find(); - const orgRevision = await this.orgRevisionRepository.findOne({ - select: ["id"], - where: { - orgRevisionIsDraft: false, - orgRevisionIsCurrent: true, - }, - }); const CURRENT_DATE = await AppDataSource.query("SELECT CURRENT_DATE() as today"); const baseCurrentDate = CURRENT_DATE[0].today; - for await (const x of profile) { - // Use leave date if available and valid, otherwise use current date - let _currentDate = baseCurrentDate; - if (x?.isLeave && x.leaveDate) { - _currentDate = Extension.toDateOnlyString(x.leaveDate); - } - const position = await this.positionRepo.findOne({ - where: { - positionIsSelected: true, - posMaster: { - orgRevisionId: orgRevision?.id, - current_holderId: x.id, - }, - }, - order: { createdAt: "DESC" }, - relations: { - posExecutive: true, - }, + + const profiles = await this.profileRepo.find({ + select: ["id", "posExecutive", "isLeave", "leaveDate"], + where: { posExecutive: Not(IsNull()) }, + }); + + const BATCH_SIZE = 50; + let successCount = 0; + let failCount = 0; + const allData: CreateTenurePositionExecutiveOfficer[] = []; + + for (let i = 0; i < profiles.length; i += BATCH_SIZE) { + const batch = profiles.slice(i, Math.min(i + BATCH_SIZE, profiles.length)); + const results = await Promise.allSettled( + batch.map((profile) => + this.processSingleProfileForTenureExecutivePositionOfficer(profile, baseCurrentDate), + ), + ); + + results.forEach((result) => { + if (result.status === "fulfilled" && result.value) { + allData.push(result.value); + successCount++; + } else { + failCount++; + } }); + } + + await AppDataSource.transaction(async (transactionalEntityManager) => { + await transactionalEntityManager.delete(TenurePositionExecutiveOfficer, {}); + if (allData.length > 0) { + const entities = allData.map((data) => { + const entity = new TenurePositionExecutiveOfficer(); + Object.assign(entity, data); + return entity; + }); + await transactionalEntityManager.save(TenurePositionExecutiveOfficer, entities); + } + }); + + return new HttpSuccess({ + message: `อัปเดต tenure executive position officer สำเร็จ`, + total: profiles.length, + success: successCount, + failed: failCount, + }); + } + + private async processSingleProfileForTenureExecutivePositionOfficer( + profile: Pick, + baseCurrentDate: string, + ): Promise { + try { + let _currentDate = baseCurrentDate; + if (profile.isLeave && profile.leaveDate) { + _currentDate = Extension.toDateOnlyString(profile.leaveDate); + } + const positionExecutive = await AppDataSource.query("CALL GetProfileSalaryExecutive(?, ?)", [ - x.id, + profile.id, _currentDate, ]); const _position = positionExecutive.length > 0 ? positionExecutive[0] : []; + const mapPosition = _position.length > 1 ? _position.slice(1).map((curr: any, index: number) => ({ days_diff: curr.days_diff, positionExecutive: _position[index]?.positionExecutive, + year: + curr.Years !== null && curr.Years !== undefined + ? Math.floor(Number(curr.Years)) + : 0, + month: + curr.Months !== null && curr.Months !== undefined + ? Math.floor(Number(curr.Months)) + : 0, + day: + curr.Days !== null && curr.Days !== undefined ? Math.floor(Number(curr.Days)) : 0, })) : []; - const _posExecutiveName = position?.posExecutive?.posExecutiveName; + const calDayDiff = mapPosition - .filter((curr: any) => _posExecutiveName && curr.positionExecutive == _posExecutiveName) + .filter((curr: any) => curr.positionExecutive === profile.posExecutive) .reduce( (acc: any, curr: any) => { acc.days_diff += Number(curr.days_diff) || 0; acc.positionExecutive = curr.positionExecutive; + acc.year += curr.year; + acc.month += curr.month; + acc.day += curr.day; return acc; }, - { days_diff: 0, positionExecutive: null }, + { days_diff: 0, positionExecutive: null, year: 0, month: 0, day: 0 }, ); - const { year, month, day } = calculateTenure(calDayDiff.days_diff); - const mapData: any = { - profileId: x.id, + + const normalized = normalizeDurationSumSimple( + calDayDiff.year, + calDayDiff.month, + calDayDiff.day, + ); + + return { + profileId: profile.id, positionExecutiveName: calDayDiff.positionExecutive, days_diff: calDayDiff.days_diff, - Years: year.toFixed(4), - Months: month.toFixed(4), - Days: day.toFixed(4), + Years: normalized.years, + Months: normalized.months, + Days: normalized.days, }; - data.push(mapData); + } catch (error) { + return null; } - await this.positionExecutiveOfficerRepo.save(data); - return new HttpSuccess(); } @Get("Registry") @@ -596,8 +929,12 @@ export class ProfileSalaryController extends Controller { ? _position.slice(1).map((curr: any, index: number) => ({ days: curr.days_diff ? Number(curr.days_diff) : 0, // Use stored procedure's calculated values (calendar arithmetic) - year: curr.Years !== null && curr.Years !== undefined ? Math.floor(Number(curr.Years)) : 0, - month: curr.Months !== null && curr.Months !== undefined ? Math.floor(Number(curr.Months)) : 0, + year: + curr.Years !== null && curr.Years !== undefined ? Math.floor(Number(curr.Years)) : 0, + month: + curr.Months !== null && curr.Months !== undefined + ? Math.floor(Number(curr.Months)) + : 0, day: curr.Days !== null && curr.Days !== undefined ? Math.floor(Number(curr.Days)) : 0, name: _position[index]?.positionName, })) @@ -613,19 +950,21 @@ export class ProfileSalaryController extends Controller { existing.month += curr.month; existing.day += curr.day; } else { - existing = { name: curr.name, days: curr.days, year: curr.year, month: curr.month, day: curr.day }; + existing = { + name: curr.name, + days: curr.days, + year: curr.year, + month: curr.month, + day: curr.day, + }; acc.push(existing); } - // Normalize the summed values (convert excess days to months, months to years) - while (existing.day >= 30) { - existing.month += Math.floor(existing.day / 30); - existing.day = existing.day % 30; - } - while (existing.month >= 12) { - existing.year += Math.floor(existing.month / 12); - existing.month = existing.month % 12; - } + // Normalize the summed values using calendar arithmetic + const normalized = normalizeDurationSumSimple(existing.year, existing.month, existing.day); + existing.year = normalized.years; + existing.month = normalized.months; + existing.day = normalized.days; return acc; }, @@ -641,8 +980,12 @@ export class ProfileSalaryController extends Controller { ? _posLevel.slice(1).map((curr: any, index: number) => ({ days: curr.days_diff ? Number(curr.days_diff) : 0, // Use stored procedure's calculated values (calendar arithmetic) - year: curr.Years !== null && curr.Years !== undefined ? Math.floor(Number(curr.Years)) : 0, - month: curr.Months !== null && curr.Months !== undefined ? Math.floor(Number(curr.Months)) : 0, + year: + curr.Years !== null && curr.Years !== undefined ? Math.floor(Number(curr.Years)) : 0, + month: + curr.Months !== null && curr.Months !== undefined + ? Math.floor(Number(curr.Months)) + : 0, day: curr.Days !== null && curr.Days !== undefined ? Math.floor(Number(curr.Days)) : 0, name: !_posLevel[index]?.positionType && _posLevel[index]?.positionCee @@ -664,19 +1007,21 @@ export class ProfileSalaryController extends Controller { existing.month += curr.month; existing.day += curr.day; } else { - existing = { name: curr.name, days: curr.days, year: curr.year, month: curr.month, day: curr.day }; + existing = { + name: curr.name, + days: curr.days, + year: curr.year, + month: curr.month, + day: curr.day, + }; acc.push(existing); } - // Normalize the summed values (convert excess days to months, months to years) - while (existing.day >= 30) { - existing.month += Math.floor(existing.day / 30); - existing.day = existing.day % 30; - } - while (existing.month >= 12) { - existing.year += Math.floor(existing.month / 12); - existing.month = existing.month % 12; - } + // Normalize the summed values using calendar arithmetic + const normalized = normalizeDurationSumSimple(existing.year, existing.month, existing.day); + existing.year = normalized.years; + existing.month = normalized.months; + existing.day = normalized.days; return acc; }, @@ -693,8 +1038,12 @@ export class ProfileSalaryController extends Controller { ? _posExecutive.slice(1).map((curr: any, index: number) => ({ days: curr.days_diff ? Number(curr.days_diff) : 0, // Use stored procedure's calculated values (calendar arithmetic) - year: curr.Years !== null && curr.Years !== undefined ? Math.floor(Number(curr.Years)) : 0, - month: curr.Months !== null && curr.Months !== undefined ? Math.floor(Number(curr.Months)) : 0, + year: + curr.Years !== null && curr.Years !== undefined ? Math.floor(Number(curr.Years)) : 0, + month: + curr.Months !== null && curr.Months !== undefined + ? Math.floor(Number(curr.Months)) + : 0, day: curr.Days !== null && curr.Days !== undefined ? Math.floor(Number(curr.Days)) : 0, name: _posExecutive[index]?.positionExecutive, })) @@ -710,19 +1059,21 @@ export class ProfileSalaryController extends Controller { existing.month += curr.month; existing.day += curr.day; } else { - existing = { name: curr.name, days: curr.days, year: curr.year, month: curr.month, day: curr.day }; + existing = { + name: curr.name, + days: curr.days, + year: curr.year, + month: curr.month, + day: curr.day, + }; acc.push(existing); } - // Normalize the summed values (convert excess days to months, months to years) - while (existing.day >= 30) { - existing.month += Math.floor(existing.day / 30); - existing.day = existing.day % 30; - } - while (existing.month >= 12) { - existing.year += Math.floor(existing.month / 12); - existing.month = existing.month % 12; - } + // Normalize the summed values using calendar arithmetic + const normalized = normalizeDurationSumSimple(existing.year, existing.month, existing.day); + existing.year = normalized.years; + existing.month = normalized.months; + existing.day = normalized.days; return acc; }, @@ -766,8 +1117,12 @@ export class ProfileSalaryController extends Controller { ? _position.slice(1).map((curr: any, index: number) => ({ days: curr.days_diff ? Number(curr.days_diff) : 0, // Use stored procedure's calculated values (calendar arithmetic) - year: curr.Years !== null && curr.Years !== undefined ? Math.floor(Number(curr.Years)) : 0, - month: curr.Months !== null && curr.Months !== undefined ? Math.floor(Number(curr.Months)) : 0, + year: + curr.Years !== null && curr.Years !== undefined ? Math.floor(Number(curr.Years)) : 0, + month: + curr.Months !== null && curr.Months !== undefined + ? Math.floor(Number(curr.Months)) + : 0, day: curr.Days !== null && curr.Days !== undefined ? Math.floor(Number(curr.Days)) : 0, name: _position[index]?.positionName, })) @@ -783,19 +1138,21 @@ export class ProfileSalaryController extends Controller { existing.month += curr.month; existing.day += curr.day; } else { - existing = { name: curr.name, days: curr.days, year: curr.year, month: curr.month, day: curr.day }; + existing = { + name: curr.name, + days: curr.days, + year: curr.year, + month: curr.month, + day: curr.day, + }; acc.push(existing); } - // Normalize the summed values (convert excess days to months, months to years) - while (existing.day >= 30) { - existing.month += Math.floor(existing.day / 30); - existing.day = existing.day % 30; - } - while (existing.month >= 12) { - existing.year += Math.floor(existing.month / 12); - existing.month = existing.month % 12; - } + // Normalize the summed values using calendar arithmetic + const normalized = normalizeDurationSumSimple(existing.year, existing.month, existing.day); + existing.year = normalized.years; + existing.month = normalized.months; + existing.day = normalized.days; return acc; }, @@ -812,8 +1169,12 @@ export class ProfileSalaryController extends Controller { ? _posLevel.slice(1).map((curr: any, index: number) => ({ days: curr.days_diff ? Number(curr.days_diff) : 0, // Use stored procedure's calculated values (calendar arithmetic) - year: curr.Years !== null && curr.Years !== undefined ? Math.floor(Number(curr.Years)) : 0, - month: curr.Months !== null && curr.Months !== undefined ? Math.floor(Number(curr.Months)) : 0, + year: + curr.Years !== null && curr.Years !== undefined ? Math.floor(Number(curr.Years)) : 0, + month: + curr.Months !== null && curr.Months !== undefined + ? Math.floor(Number(curr.Months)) + : 0, day: curr.Days !== null && curr.Days !== undefined ? Math.floor(Number(curr.Days)) : 0, name: !_posLevel[index]?.positionType && _posLevel[index]?.positionCee @@ -835,19 +1196,21 @@ export class ProfileSalaryController extends Controller { existing.month += curr.month; existing.day += curr.day; } else { - existing = { name: curr.name, days: curr.days, year: curr.year, month: curr.month, day: curr.day }; + existing = { + name: curr.name, + days: curr.days, + year: curr.year, + month: curr.month, + day: curr.day, + }; acc.push(existing); } - // Normalize the summed values (convert excess days to months, months to years) - while (existing.day >= 30) { - existing.month += Math.floor(existing.day / 30); - existing.day = existing.day % 30; - } - while (existing.month >= 12) { - existing.year += Math.floor(existing.month / 12); - existing.month = existing.month % 12; - } + // Normalize the summed values using calendar arithmetic + const normalized = normalizeDurationSumSimple(existing.year, existing.month, existing.day); + existing.year = normalized.years; + existing.month = normalized.months; + existing.day = normalized.days; return acc; }, @@ -864,8 +1227,12 @@ export class ProfileSalaryController extends Controller { ? _posExecutive.slice(1).map((curr: any, index: number) => ({ days: curr.days_diff ? Number(curr.days_diff) : 0, // Use stored procedure's calculated values (calendar arithmetic) - year: curr.Years !== null && curr.Years !== undefined ? Math.floor(Number(curr.Years)) : 0, - month: curr.Months !== null && curr.Months !== undefined ? Math.floor(Number(curr.Months)) : 0, + year: + curr.Years !== null && curr.Years !== undefined ? Math.floor(Number(curr.Years)) : 0, + month: + curr.Months !== null && curr.Months !== undefined + ? Math.floor(Number(curr.Months)) + : 0, day: curr.Days !== null && curr.Days !== undefined ? Math.floor(Number(curr.Days)) : 0, name: _posExecutive[index]?.positionExecutive, })) @@ -881,19 +1248,21 @@ export class ProfileSalaryController extends Controller { existing.month += curr.month; existing.day += curr.day; } else { - existing = { name: curr.name, days: curr.days, year: curr.year, month: curr.month, day: curr.day }; + existing = { + name: curr.name, + days: curr.days, + year: curr.year, + month: curr.month, + day: curr.day, + }; acc.push(existing); } - // Normalize the summed values (convert excess days to months, months to years) - while (existing.day >= 30) { - existing.month += Math.floor(existing.day / 30); - existing.day = existing.day % 30; - } - while (existing.month >= 12) { - existing.year += Math.floor(existing.month / 12); - existing.month = existing.month % 12; - } + // Normalize the summed values using calendar arithmetic + const normalized = normalizeDurationSumSimple(existing.year, existing.month, existing.day); + existing.year = normalized.years; + existing.month = normalized.months; + existing.day = normalized.days; return acc; }, diff --git a/src/controllers/ProfileSalaryEmployeeController.ts b/src/controllers/ProfileSalaryEmployeeController.ts index 44b93a5d..5f2ea997 100644 --- a/src/controllers/ProfileSalaryEmployeeController.ts +++ b/src/controllers/ProfileSalaryEmployeeController.ts @@ -27,7 +27,7 @@ import { Profile } from "../entities/Profile"; import { In, LessThan, IsNull, MoreThan } from "typeorm"; import permission from "../interfaces/permission"; import { setLogDataDiff } from "../interfaces/utils"; -import { calculateTenure } from "../utils/tenure"; +import { normalizeDurationSumSimple } from "../utils/tenure"; import { Command } from "../entities/Command"; import { OrgRoot } from "../entities/OrgRoot"; import Extension from "../interfaces/extension"; @@ -161,6 +161,14 @@ export class ProfileSalaryEmployeeController extends Controller { _position.length > 1 ? _position.slice(1).map((curr: any, index: number) => ({ days: curr.days_diff ? Number(curr.days_diff) : 0, + // Use stored procedure's calculated values (calendar arithmetic) + year: + curr.Years !== null && curr.Years !== undefined ? Math.floor(Number(curr.Years)) : 0, + month: + curr.Months !== null && curr.Months !== undefined + ? Math.floor(Number(curr.Months)) + : 0, + day: curr.Days !== null && curr.Days !== undefined ? Math.floor(Number(curr.Days)) : 0, name: _position[index]?.positionName, })) : []; @@ -171,15 +179,25 @@ export class ProfileSalaryEmployeeController extends Controller { if (existing) { existing.days += curr.days; + existing.year += curr.year; + existing.month += curr.month; + existing.day += curr.day; } else { - existing = { name: curr.name, days: curr.days }; + existing = { + name: curr.name, + days: curr.days, + year: curr.year, + month: curr.month, + day: curr.day, + }; acc.push(existing); } - const { year, month, day } = calculateTenure(existing.days); - existing.year = year; - existing.month = month; - existing.day = day; + // Normalize the summed values using calendar arithmetic + const normalized = normalizeDurationSumSimple(existing.year, existing.month, existing.day); + existing.year = normalized.years; + existing.month = normalized.months; + existing.day = normalized.days; return acc; }, @@ -195,6 +213,14 @@ export class ProfileSalaryEmployeeController extends Controller { _posLevel.length > 1 ? _posLevel.slice(1).map((curr: any, index: number) => ({ days: curr.days_diff ? Number(curr.days_diff) : 0, + // Use stored procedure's calculated values (calendar arithmetic) + year: + curr.Years !== null && curr.Years !== undefined ? Math.floor(Number(curr.Years)) : 0, + month: + curr.Months !== null && curr.Months !== undefined + ? Math.floor(Number(curr.Months)) + : 0, + day: curr.Days !== null && curr.Days !== undefined ? Math.floor(Number(curr.Days)) : 0, name: !_posLevel[index]?.positionType && _posLevel[index]?.positionCee ? `ระดับ ${_posLevel[index]?.positionCee.trim()}` @@ -208,15 +234,25 @@ export class ProfileSalaryEmployeeController extends Controller { if (existing) { existing.days += curr.days; + existing.year += curr.year; + existing.month += curr.month; + existing.day += curr.day; } else { - existing = { name: curr.name, days: curr.days }; + existing = { + name: curr.name, + days: curr.days, + year: curr.year, + month: curr.month, + day: curr.day, + }; acc.push(existing); } - const { year, month, day } = calculateTenure(existing.days); - existing.year = year; - existing.month = month; - existing.day = day; + // Normalize the summed values using calendar arithmetic + const normalized = normalizeDurationSumSimple(existing.year, existing.month, existing.day); + existing.year = normalized.years; + existing.month = normalized.months; + existing.day = normalized.days; return acc; }, @@ -254,6 +290,14 @@ export class ProfileSalaryEmployeeController extends Controller { _position.length > 1 ? _position.slice(1).map((curr: any, index: number) => ({ days: curr.days_diff ? Number(curr.days_diff) : 0, + // Use stored procedure's calculated values (calendar arithmetic) + year: + curr.Years !== null && curr.Years !== undefined ? Math.floor(Number(curr.Years)) : 0, + month: + curr.Months !== null && curr.Months !== undefined + ? Math.floor(Number(curr.Months)) + : 0, + day: curr.Days !== null && curr.Days !== undefined ? Math.floor(Number(curr.Days)) : 0, name: _position[index]?.positionName, })) : []; @@ -264,15 +308,25 @@ export class ProfileSalaryEmployeeController extends Controller { if (existing) { existing.days += curr.days; + existing.year += curr.year; + existing.month += curr.month; + existing.day += curr.day; } else { - existing = { name: curr.name, days: curr.days }; + existing = { + name: curr.name, + days: curr.days, + year: curr.year, + month: curr.month, + day: curr.day, + }; acc.push(existing); } - const { year, month, day } = calculateTenure(existing.days); - existing.year = year; - existing.month = month; - existing.day = day; + // Normalize the summed values using calendar arithmetic + const normalized = normalizeDurationSumSimple(existing.year, existing.month, existing.day); + existing.year = normalized.years; + existing.month = normalized.months; + existing.day = normalized.days; return acc; }, @@ -288,6 +342,14 @@ export class ProfileSalaryEmployeeController extends Controller { _posLevel.length > 1 ? _posLevel.slice(1).map((curr: any, index: number) => ({ days: curr.days_diff ? Number(curr.days_diff) : 0, + // Use stored procedure's calculated values (calendar arithmetic) + year: + curr.Years !== null && curr.Years !== undefined ? Math.floor(Number(curr.Years)) : 0, + month: + curr.Months !== null && curr.Months !== undefined + ? Math.floor(Number(curr.Months)) + : 0, + day: curr.Days !== null && curr.Days !== undefined ? Math.floor(Number(curr.Days)) : 0, name: !_posLevel[index]?.positionType && _posLevel[index]?.positionCee ? `ระดับ ${_posLevel[index]?.positionCee.trim()}` @@ -301,15 +363,25 @@ export class ProfileSalaryEmployeeController extends Controller { if (existing) { existing.days += curr.days; + existing.year += curr.year; + existing.month += curr.month; + existing.day += curr.day; } else { - existing = { name: curr.name, days: curr.days }; + existing = { + name: curr.name, + days: curr.days, + year: curr.year, + month: curr.month, + day: curr.day, + }; acc.push(existing); } - const { year, month, day } = calculateTenure(existing.days); - existing.year = year; - existing.month = month; - existing.day = day; + // Normalize the summed values using calendar arithmetic + const normalized = normalizeDurationSumSimple(existing.year, existing.month, existing.day); + existing.year = normalized.years; + existing.month = normalized.months; + existing.day = normalized.days; return acc; }, diff --git a/src/controllers/ProfileSalaryTempController.ts b/src/controllers/ProfileSalaryTempController.ts index 35279fbc..c166d22d 100644 --- a/src/controllers/ProfileSalaryTempController.ts +++ b/src/controllers/ProfileSalaryTempController.ts @@ -1791,4 +1791,56 @@ export class ProfileSalaryTempController extends Controller { await this.salaryRepo.save(sortLevel); return new HttpSuccess(); } + + /** + * API เรียงลำดับทะเบียนประวัติและเงินเดือนที่กำลังแก้ไขตามวันที่คำสั่งมีผล + * @summary API เรียงลำดับทะเบียนประวัติและเงินเดือนที่กำลังแก้ไขตามวันที่คำสั่งมีผล + */ + @Put("sort-order") + public async reorderSalaryByCommandDate( + @Request() req: RequestWithUser, + @Body() body: { profileId: string; type: "OFFICER" | "EMPLOYEE" }, + ) { + const isOfficer = body.type.toUpperCase() === "OFFICER"; + + // Step 1: SELECT ข้อมูลตาม profileId และ type + const salaryTemps = await this.salaryRepo.find({ + where: isOfficer ? { profileId: body.profileId } : { profileEmployeeId: body.profileId }, + }); + + if (salaryTemps.length === 0) { + throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูลตำแหน่งเงินเดือน"); + } + + // Step 2: เรียงลำดับตาม commandDateAffect (ASC) + // ถ้า commandDateAffect เท่ากัน ให้ใช้ order เดิมเป็น secondary sort + const sortedSalary = salaryTemps.sort((a, b) => { + // ถ้า commandDateAffect เป็น null ให้ถือว่าเป็นค่าน้อยสุด + const dateA = a.commandDateAffect ? new Date(a.commandDateAffect).getTime() : 0; + const dateB = b.commandDateAffect ? new Date(b.commandDateAffect).getTime() : 0; + + if (dateA !== dateB) { + return dateA - dateB; // เรียงตามวันที่คำสั่งมีผล + } + + // ถ้าวันที่เท่ากัน ให้ใช้ order เดิม + const orderA = a.order ?? 0; + const orderB = b.order ?? 0; + return orderA - orderB; + }); + + // Step 3: UPDATE ฟิลด์ order ตามการเรียงใหม่ + const dateNow = new Date(); + const updatedSalary = sortedSalary.map((item, index) => ({ + ...item, + order: index + 1, + lastUpdateUserId: req.user.sub, + lastUpdateFullName: req.user.name, + lastUpdatedAt: dateNow, + })); + + await this.salaryRepo.save(updatedSalary); + + return new HttpSuccess(); + } } diff --git a/src/controllers/ScriptProfileOrgController.ts b/src/controllers/ScriptProfileOrgController.ts index aa6908e2..0494be98 100644 --- a/src/controllers/ScriptProfileOrgController.ts +++ b/src/controllers/ScriptProfileOrgController.ts @@ -38,6 +38,10 @@ export class ScriptProfileOrgController extends Controller { process.env.CRONJOB_UPDATE_WINDOW_HOURS || "24", 10, ); + private readonly LEAVE_SERVICE_BATCH_SIZE = parseInt( + process.env.LEAVE_SERVICE_BATCH_SIZE || "50", + 10, + ); /** * Script to update profile's organizational structure in leave service and sync to Keycloak @@ -45,7 +49,7 @@ export class ScriptProfileOrgController extends Controller { * @summary Update org structure for profiles updated within a certain time window and sync to Keycloak */ @Post("update-org") - public async cronjobUpdateOrg(@Request() request: RequestWithUser) { + public async cronjobUpdateOrg(@Request() _request: RequestWithUser) { // Idempotency check - prevent concurrent runs if (this.isRunning) { console.log("cronjobUpdateOrg: Job already running, skipping this execution"); @@ -176,21 +180,6 @@ export class ScriptProfileOrgController extends Controller { }); } - // Update profile's org structure in leave service by calling API - console.log("cronjobUpdateOrg: Calling leave service API", { - payloadCount: payloads.length, - }); - - await axios.put(`${process.env.API_URL}/leave-beginning/schedule/update-dna`, payloads, { - headers: { - "Content-Type": "application/json", - api_key: process.env.API_KEY, - }, - timeout: 30000, // 30 second timeout - }); - - console.log("cronjobUpdateOrg: Leave service API call successful"); - // Group profile IDs by type for proper syncing const profileIdsByType = this.groupProfileIdsByType(payloads); @@ -256,16 +245,90 @@ export class ScriptProfileOrgController extends Controller { syncResults.failed += typeResult.failed; } + // Update profile's org structure in leave service by calling API + console.log("cronjobUpdateOrg: Calling leave service API with chunking", { + payloadCount: payloads.length, + batchSize: this.LEAVE_SERVICE_BATCH_SIZE, + expectedBatches: Math.ceil(payloads.length / this.LEAVE_SERVICE_BATCH_SIZE), + }); + + const chunks = this.chunkArray(payloads, this.LEAVE_SERVICE_BATCH_SIZE); + const leaveServiceResults = { + total: payloads.length, + success: 0, + failed: 0, + batchesCompleted: 0, + batchesFailed: 0, + }; + + for (let i = 0; i < chunks.length; i++) { + const chunk = chunks[i]; + const batchNumber = i + 1; + + console.log( + `cronjobUpdateOrg: Processing leave service batch ${batchNumber}/${chunks.length}`, + { + batchSize: chunk.length, + batchRange: `${i * this.LEAVE_SERVICE_BATCH_SIZE + 1}-${Math.min( + (batchNumber + 1) * this.LEAVE_SERVICE_BATCH_SIZE, + payloads.length, + )}`, + }, + ); + + try { + await axios.put( + `${process.env.API_URL}/leave-beginning/schedule/update-dna`, + chunk, + { + headers: { + "Content-Type": "application/json", + api_key: process.env.API_KEY, + }, + timeout: 120000, // 120 second timeout per chunk + }, + ); + + leaveServiceResults.success += chunk.length; + leaveServiceResults.batchesCompleted++; + + console.log(`cronjobUpdateOrg: Leave service batch ${batchNumber}/${chunks.length} completed`, { + success: chunk.length, + }); + } catch (error: any) { + leaveServiceResults.failed += chunk.length; + leaveServiceResults.batchesFailed++; + + console.error( + `cronjobUpdateOrg: Leave service batch ${batchNumber}/${chunks.length} failed`, + { + error: error.message, + batchSize: chunk.length, + responseStatus: error.response?.status, + responseData: error.response?.data, + }, + ); + + // Continue processing remaining batches + } + } + + console.log("cronjobUpdateOrg: Leave service API call completed", { + ...leaveServiceResults, + }); + const duration = Date.now() - startTime; console.log("cronjobUpdateOrg: Job completed", { duration: `${duration}ms`, processed: payloads.length, + leaveServiceResults, syncResults, }); return new HttpSuccess({ message: "Update org completed", processed: payloads.length, + leaveServiceResults, syncResults, duration: `${duration}ms`, }); diff --git a/src/controllers/SocketController.ts b/src/controllers/SocketController.ts index c975336f..13bdde1d 100644 --- a/src/controllers/SocketController.ts +++ b/src/controllers/SocketController.ts @@ -36,13 +36,26 @@ export class SocketController extends Controller { }, @Request() req: RequestWithUser, ) { + const toArray = (value?: string | string[]) => { + if (Array.isArray(value)) return value.filter(Boolean); + if (typeof value === "string" && value.trim()) return [value]; + return [] as string[]; + }; + + const targetUserIds = toArray(payload.targetUserId); + const targetRoles = toArray(payload.roles); + + // If caller provides explicit user targets, do not combine with role targeting. + // This prevents accidental broad notifications when roles include common roles. + const recipients = + targetUserIds.length > 0 + ? { userId: targetUserIds, roles: [] as string[] } + : { userId: [req.user.sub], roles: targetRoles }; + sendWebSocket( "socket-notification", { success: !payload.error, message: payload.message }, - { - roles: payload.roles || req.user.role || [], - userId: payload.targetUserId || req.user.sub || [], - }, + recipients, ); } } diff --git a/src/controllers/UserController.ts b/src/controllers/UserController.ts index 2120dcff..4902ce0f 100644 --- a/src/controllers/UserController.ts +++ b/src/controllers/UserController.ts @@ -580,18 +580,27 @@ export class KeycloakController extends Controller { new Brackets((qb) => { qb.orWhere( body.keyword != null && body.keyword != "" - ? `profile.citizenId like '%${body.keyword}%'` + ? `profile.citizenId LIKE :keyword` : "1=1", + { + keyword: `%${body.keyword}%`, + } ) .orWhere( body.keyword != null && body.keyword != "" - ? `profile.email like '%${body.keyword}%'` + ? `profile.email LIKE :keyword` : "1=1", + { + keyword: `%${body.keyword}%`, + } ) .orWhere( body.keyword != null && body.keyword != "" - ? `CONCAT(profile.prefix, profile.firstName," ",profile.lastName) like '%${body.keyword}%'` + ? `CONCAT(profile.prefix, profile.firstName," ",profile.lastName) LIKE :keyword` : "1=1", + { + keyword: `%${body.keyword}%`, + } ); }), ) @@ -625,18 +634,27 @@ export class KeycloakController extends Controller { new Brackets((qb) => { qb.orWhere( body.keyword != null && body.keyword != "" - ? `profileEmployee.citizenId like '%${body.keyword}%'` + ? `profileEmployee.citizenId LIKE :keyword` : "1=1", + { + keyword: `%${body.keyword}%`, + } ) .orWhere( body.keyword != null && body.keyword != "" - ? `profileEmployee.email like '%${body.keyword}%'` + ? `profileEmployee.email LIKE :keyword` : "1=1", + { + keyword: `%${body.keyword}%`, + } ) .orWhere( body.keyword != null && body.keyword != "" - ? `CONCAT(profileEmployee.prefix, profileEmployee.firstName," ",profileEmployee.lastName) like '%${body.keyword}%'` + ? `CONCAT(profileEmployee.prefix, profileEmployee.firstName," ",profileEmployee.lastName) LIKE :keyword` : "1=1", + { + keyword: `%${body.keyword}%`, + } ); }), ) diff --git a/src/controllers/WorkflowController.ts b/src/controllers/WorkflowController.ts index ae5c8859..d8aa0b77 100644 --- a/src/controllers/WorkflowController.ts +++ b/src/controllers/WorkflowController.ts @@ -865,6 +865,7 @@ export class WorkflowController extends Controller { type?: string | null; sortBy?: string | null; descending?: boolean; + isAllDirector?: boolean; }, ) { const userKeycloak = body.keycloakId ?? request.user.sub; @@ -953,38 +954,46 @@ export class WorkflowController extends Controller { let mainConditions: any[] = []; if (type.trim().toUpperCase() === "OPERATE" || body.type === "employee") { - mainConditions = [ - { ...baseCondition, orgRootId: In(roodIds), orgChild1Id: IsNull() }, - { - ...baseCondition, - orgRootId: In(roodIds), - orgChild1Id: posMasterUser.orgChild1Id, - orgChild2Id: IsNull(), - }, - { - ...baseCondition, - orgRootId: In(roodIds), - orgChild1Id: posMasterUser.orgChild1Id, - orgChild2Id: posMasterUser.orgChild2Id, - orgChild3Id: IsNull(), - }, - { - ...baseCondition, - orgRootId: In(roodIds), - orgChild1Id: posMasterUser.orgChild1Id, - orgChild2Id: posMasterUser.orgChild2Id, - orgChild3Id: posMasterUser.orgChild3Id, - orgChild4Id: IsNull(), - }, - { - ...baseCondition, - orgRootId: In(roodIds), - orgChild1Id: posMasterUser.orgChild1Id, - orgChild2Id: posMasterUser.orgChild2Id, - orgChild3Id: posMasterUser.orgChild3Id, - orgChild4Id: posMasterUser.orgChild4Id, - }, - ]; + + if (body.isAllDirector === true) { + mainConditions = [ + { ...baseCondition, orgRootId: In(roodIds) } + ]; + } + else { + mainConditions = [ + { ...baseCondition, orgRootId: In(roodIds), orgChild1Id: IsNull() }, + { + ...baseCondition, + orgRootId: In(roodIds), + orgChild1Id: posMasterUser.orgChild1Id, + orgChild2Id: IsNull(), + }, + { + ...baseCondition, + orgRootId: In(roodIds), + orgChild1Id: posMasterUser.orgChild1Id, + orgChild2Id: posMasterUser.orgChild2Id, + orgChild3Id: IsNull(), + }, + { + ...baseCondition, + orgRootId: In(roodIds), + orgChild1Id: posMasterUser.orgChild1Id, + orgChild2Id: posMasterUser.orgChild2Id, + orgChild3Id: posMasterUser.orgChild3Id, + orgChild4Id: IsNull(), + }, + { + ...baseCondition, + orgRootId: In(roodIds), + orgChild1Id: posMasterUser.orgChild1Id, + orgChild2Id: posMasterUser.orgChild2Id, + orgChild3Id: posMasterUser.orgChild3Id, + orgChild4Id: posMasterUser.orgChild4Id, + }, + ]; + } } else if (isLowLevel) { mainConditions = [ { diff --git a/src/entities/Command.ts b/src/entities/Command.ts index c6b26626..e6af1be8 100644 --- a/src/entities/Command.ts +++ b/src/entities/Command.ts @@ -34,6 +34,14 @@ export class Command extends EntityBase { }) issue: string; + @Column({ + nullable: true, + comment: "ชื่อย่อหน่วยงานที่ออกคำสั่ง", + length: 16, + default: null, + }) + shortName: string; + @Column({ nullable: true, comment: "เลขที่คำสั่ง", diff --git a/src/entities/OrgChild1.ts b/src/entities/OrgChild1.ts index e322df8b..ef2a9be6 100644 --- a/src/entities/OrgChild1.ts +++ b/src/entities/OrgChild1.ts @@ -174,6 +174,46 @@ export class OrgChild1 extends EntityBase { }) JOB_CODE: string; + @Column({ + nullable: true, + length: 3, + comment: "ROOT_CODE", + default: null, + }) + ROOT_CODE: string; + + @Column({ + nullable: true, + length: 3, + comment: "CHILD1_CODE", + default: null, + }) + CHILD1_CODE: string; + + @Column({ + nullable: true, + length: 3, + comment: "CHILD2_CODE", + default: null, + }) + CHILD2_CODE: string; + + @Column({ + nullable: true, + length: 3, + comment: "CHILD3_CODE", + default: null, + }) + CHILD3_CODE: string; + + @Column({ + nullable: true, + length: 3, + comment: "CHILD4_CODE", + default: null, + }) + CHILD4_CODE: string; + @ManyToOne(() => OrgRoot, (orgRoot) => orgRoot.orgChild1s) @JoinColumn({ name: "orgRootId" }) orgRoot: OrgRoot; @@ -228,6 +268,21 @@ export class CreateOrgChild1 { @Column() JOB_CODE?: string | null; + @Column() + ROOT_CODE?: string | null; + + @Column() + CHILD1_CODE?: string | null; + + @Column() + CHILD2_CODE?: string | null; + + @Column() + CHILD3_CODE?: string | null; + + @Column() + CHILD4_CODE?: string | null; + @Column() orgChild1PhoneEx?: string; @@ -283,6 +338,21 @@ export class UpdateOrgChild1 { @Column() JOB_CODE?: string | null; + @Column() + ROOT_CODE?: string | null; + + @Column() + CHILD1_CODE?: string | null; + + @Column() + CHILD2_CODE?: string | null; + + @Column() + CHILD3_CODE?: string | null; + + @Column() + CHILD4_CODE?: string | null; + @Column() orgChild1PhoneEx?: string; diff --git a/src/entities/OrgChild2.ts b/src/entities/OrgChild2.ts index b2ed6a0c..50f40769 100644 --- a/src/entities/OrgChild2.ts +++ b/src/entities/OrgChild2.ts @@ -146,6 +146,46 @@ export class OrgChild2 extends EntityBase { }) JOB_CODE: string; + @Column({ + nullable: true, + length: 3, + comment: "ROOT_CODE", + default: null, + }) + ROOT_CODE: string; + + @Column({ + nullable: true, + length: 3, + comment: "CHILD1_CODE", + default: null, + }) + CHILD1_CODE: string; + + @Column({ + nullable: true, + length: 3, + comment: "CHILD2_CODE", + default: null, + }) + CHILD2_CODE: string; + + @Column({ + nullable: true, + length: 3, + comment: "CHILD3_CODE", + default: null, + }) + CHILD3_CODE: string; + + @Column({ + nullable: true, + length: 3, + comment: "CHILD4_CODE", + default: null, + }) + CHILD4_CODE: string; + @Column({ nullable: true, length: 40, @@ -222,6 +262,21 @@ export class CreateOrgChild2 { @Column() JOB_CODE?: string; + @Column() + ROOT_CODE?: string; + + @Column() + CHILD1_CODE?: string; + + @Column() + CHILD2_CODE?: string; + + @Column() + CHILD3_CODE?: string; + + @Column() + CHILD4_CODE?: string; + @Column() orgChild2PhoneEx?: string; @@ -269,6 +324,21 @@ export class UpdateOrgChild2 { @Column() JOB_CODE?: string | null; + @Column() + ROOT_CODE?: string | null; + + @Column() + CHILD1_CODE?: string | null; + + @Column() + CHILD2_CODE?: string | null; + + @Column() + CHILD3_CODE?: string | null; + + @Column() + CHILD4_CODE?: string | null; + @Column() orgChild2PhoneEx?: string; diff --git a/src/entities/OrgChild3.ts b/src/entities/OrgChild3.ts index 4c376fb6..72a29336 100644 --- a/src/entities/OrgChild3.ts +++ b/src/entities/OrgChild3.ts @@ -153,6 +153,46 @@ export class OrgChild3 extends EntityBase { }) JOB_CODE: string; + @Column({ + nullable: true, + length: 3, + comment: "ROOT_CODE", + default: null, + }) + ROOT_CODE: string; + + @Column({ + nullable: true, + length: 3, + comment: "CHILD1_CODE", + default: null, + }) + CHILD1_CODE: string; + + @Column({ + nullable: true, + length: 3, + comment: "CHILD2_CODE", + default: null, + }) + CHILD2_CODE: string; + + @Column({ + nullable: true, + length: 3, + comment: "CHILD3_CODE", + default: null, + }) + CHILD3_CODE: string; + + @Column({ + nullable: true, + length: 3, + comment: "CHILD4_CODE", + default: null, + }) + CHILD4_CODE: string; + @Column({ nullable: true, length: 40, @@ -230,6 +270,21 @@ export class CreateOrgChild3 { @Column() JOB_CODE?: string; + @Column() + ROOT_CODE?: string; + + @Column() + CHILD1_CODE?: string; + + @Column() + CHILD2_CODE?: string; + + @Column() + CHILD3_CODE?: string; + + @Column() + CHILD4_CODE?: string; + @Column() orgChild3PhoneEx?: string; @@ -279,6 +334,21 @@ export class UpdateOrgChild3 { @Column() JOB_CODE?: string | null; + @Column() + ROOT_CODE?: string | null; + + @Column() + CHILD1_CODE?: string | null; + + @Column() + CHILD2_CODE?: string | null; + + @Column() + CHILD3_CODE?: string | null; + + @Column() + CHILD4_CODE?: string | null; + @Column() orgChild3PhoneEx?: string; diff --git a/src/entities/OrgChild4.ts b/src/entities/OrgChild4.ts index 314c9393..4f053b00 100644 --- a/src/entities/OrgChild4.ts +++ b/src/entities/OrgChild4.ts @@ -158,6 +158,46 @@ export class OrgChild4 extends EntityBase { }) JOB_CODE: string; + @Column({ + nullable: true, + length: 3, + comment: "ROOT_CODE", + default: null, + }) + ROOT_CODE: string; + + @Column({ + nullable: true, + length: 3, + comment: "CHILD1_CODE", + default: null, + }) + CHILD1_CODE: string; + + @Column({ + nullable: true, + length: 3, + comment: "CHILD2_CODE", + default: null, + }) + CHILD2_CODE: string; + + @Column({ + nullable: true, + length: 3, + comment: "CHILD3_CODE", + default: null, + }) + CHILD3_CODE: string; + + @Column({ + nullable: true, + length: 3, + comment: "CHILD4_CODE", + default: null, + }) + CHILD4_CODE: string; + @Column({ nullable: true, length: 40, @@ -236,6 +276,21 @@ export class CreateOrgChild4 { @Column() JOB_CODE?: string; + @Column() + ROOT_CODE?: string; + + @Column() + CHILD1_CODE?: string; + + @Column() + CHILD2_CODE?: string; + + @Column() + CHILD3_CODE?: string; + + @Column() + CHILD4_CODE?: string; + @Column() orgChild4PhoneEx?: string; @@ -283,6 +338,21 @@ export class UpdateOrgChild4 { @Column() JOB_CODE?: string | null; + @Column() + ROOT_CODE?: string | null; + + @Column() + CHILD1_CODE?: string | null; + + @Column() + CHILD2_CODE?: string | null; + + @Column() + CHILD3_CODE?: string | null; + + @Column() + CHILD4_CODE?: string | null; + @Column() orgChild4PhoneEx?: string; diff --git a/src/entities/OrgRoot.ts b/src/entities/OrgRoot.ts index 3aefad6e..02e22bb8 100644 --- a/src/entities/OrgRoot.ts +++ b/src/entities/OrgRoot.ts @@ -166,6 +166,46 @@ export class OrgRoot extends EntityBase { }) JOB_CODE: string; + @Column({ + nullable: true, + length: 3, + comment: "ROOT_CODE", + default: null, + }) + ROOT_CODE: string; + + @Column({ + nullable: true, + length: 3, + comment: "CHILD1_CODE", + default: null, + }) + CHILD1_CODE: string; + + @Column({ + nullable: true, + length: 3, + comment: "CHILD2_CODE", + default: null, + }) + CHILD2_CODE: string; + + @Column({ + nullable: true, + length: 3, + comment: "CHILD3_CODE", + default: null, + }) + CHILD3_CODE: string; + + @Column({ + nullable: true, + length: 3, + comment: "CHILD4_CODE", + default: null, + }) + CHILD4_CODE: string; + @ManyToOne(() => OrgRevision, (orgRevision) => orgRevision.orgRoots) @JoinColumn({ name: "orgRevisionId" }) orgRevision: OrgRevision; @@ -226,6 +266,21 @@ export class CreateOrgRoot { @Column() JOB_CODE?: string; + @Column() + ROOT_CODE?: string; + + @Column() + CHILD1_CODE?: string; + + @Column() + CHILD2_CODE?: string; + + @Column() + CHILD3_CODE?: string; + + @Column() + CHILD4_CODE?: string; + @Column() orgRootPhoneEx?: string; @@ -281,6 +336,21 @@ export class UpdateOrgRoot { @Column() JOB_CODE?: string | null; + @Column() + ROOT_CODE?: string | null; + + @Column() + CHILD1_CODE?: string | null; + + @Column() + CHILD2_CODE?: string | null; + + @Column() + CHILD3_CODE?: string | null; + + @Column() + CHILD4_CODE?: string | null; + @Column() orgRootPhoneEx?: string; diff --git a/src/entities/PosMasterEmployeeHistory.ts b/src/entities/PosMasterEmployeeHistory.ts index b0418644..e0aa7853 100644 --- a/src/entities/PosMasterEmployeeHistory.ts +++ b/src/entities/PosMasterEmployeeHistory.ts @@ -99,51 +99,51 @@ export class PosMasterEmployeeHistory extends EntityBase { }) ancestorDNA: string; - // @Column({ - // nullable: true, - // length: 40, - // comment: "คีย์นอก(FK)ของตาราง profile", - // default: null, - // }) - // profileId: string; + @Column({ + nullable: true, + length: 40, + comment: "คีย์นอก(FK)ของตาราง profileEmployee", + default: null, + }) + profileEmployeeId: string; - // @Column({ - // nullable: true, - // length: 40, - // comment: "dna ของตาราง orgRoot", - // default: null, - // }) - // rootDnaId: string; + @Column({ + nullable: true, + length: 40, + comment: "dna ของตาราง orgRoot", + default: null, + }) + rootDnaId: string; - // @Column({ - // nullable: true, - // length: 40, - // comment: "dna ของตาราง orgChild1", - // default: null, - // }) - // child1DnaId: string; + @Column({ + nullable: true, + length: 40, + comment: "dna ของตาราง orgChild1", + default: null, + }) + child1DnaId: string; - // @Column({ - // nullable: true, - // length: 40, - // comment: "dna ของตาราง orgChild2", - // default: null, - // }) - // child2DnaId: string; + @Column({ + nullable: true, + length: 40, + comment: "dna ของตาราง orgChild2", + default: null, + }) + child2DnaId: string; - // @Column({ - // nullable: true, - // length: 40, - // comment: "dna ของตาราง orgChild3", - // default: null, - // }) - // child3DnaId: string; + @Column({ + nullable: true, + length: 40, + comment: "dna ของตาราง orgChild3", + default: null, + }) + child3DnaId: string; - // @Column({ - // nullable: true, - // length: 40, - // comment: "dna ของตาราง orgChild4", - // default: null, - // }) - // child4DnaId: string; + @Column({ + nullable: true, + length: 40, + comment: "dna ของตาราง orgChild4", + default: null, + }) + child4DnaId: string; } diff --git a/src/entities/ProfileChangeName.ts b/src/entities/ProfileChangeName.ts index b02117fc..61e64c24 100644 --- a/src/entities/ProfileChangeName.ts +++ b/src/entities/ProfileChangeName.ts @@ -31,6 +31,14 @@ export class ProfileChangeName extends EntityBase { }) prefix: string; + @Column({ + nullable: true, + comment: "ยศ", + length: 40, + default: null, + }) + rank: string; + @Column({ nullable: true, length: 100, @@ -103,6 +111,7 @@ export class CreateProfileChangeName { profileId: string | null; prefixId: string | null; prefix: string | null; + rank: string | null; firstName: string | null; lastName: string | null; status: string | null; @@ -113,6 +122,7 @@ export class CreateProfileChangeNameEmployee { profileEmployeeId: string | null; prefixId: string | null; prefix: string | null; + rank: string | null; firstName: string | null; lastName: string | null; status: string | null; @@ -122,6 +132,7 @@ export class CreateProfileChangeNameEmployee { export type UpdateProfileChangeName = { prefixId?: string | null; prefix?: string | null; + rank?: string | null; firstName?: string | null; lastName?: string | null; status?: string | null; diff --git a/src/entities/ProfileChangeNameHistory.ts b/src/entities/ProfileChangeNameHistory.ts index 5d296161..5d58a9f7 100644 --- a/src/entities/ProfileChangeNameHistory.ts +++ b/src/entities/ProfileChangeNameHistory.ts @@ -20,6 +20,14 @@ export class ProfileChangeNameHistory extends EntityBase { }) prefix: string; + @Column({ + nullable: true, + comment: "ยศ", + length: 40, + default: null, + }) + rank: string; + @Column({ nullable: true, length: 100, @@ -79,6 +87,7 @@ export class CreateProfileChangeNameHistory { profileChangeNameId: string | null; prefixId: string | null; prefix: string | null; + rank: string | null; firstName: string | null; lastName: string | null; status: string | null; @@ -89,6 +98,7 @@ export type UpdateProfileChangeNameHistory = { profileChangeNameId?: string | null; prefixId?: string | null; prefix?: string | null; + rank?: string | null; firstName?: string | null; lastName?: string | null; status?: string | null; diff --git a/src/entities/ProfileDiscipline.ts b/src/entities/ProfileDiscipline.ts index 0065528b..7a5f463d 100644 --- a/src/entities/ProfileDiscipline.ts +++ b/src/entities/ProfileDiscipline.ts @@ -61,6 +61,14 @@ export class ProfileDiscipline extends EntityBase { default: null, }) refCommandNo: string; + + @Column({ + nullable: true, + length: 40, + comment: "คีย์นอก(FK)ของตาราง command", + default: null, + }) + refCommandId: string; @Column({ nullable: true, diff --git a/src/entities/ProfileDisciplineHistory.ts b/src/entities/ProfileDisciplineHistory.ts index e7dfee58..d99a7263 100644 --- a/src/entities/ProfileDisciplineHistory.ts +++ b/src/entities/ProfileDisciplineHistory.ts @@ -51,6 +51,14 @@ export class ProfileDisciplineHistory extends EntityBase { }) refCommandNo: string; + @Column({ + nullable: true, + length: 40, + comment: "คีย์นอก(FK)ของตาราง command", + default: null, + }) + refCommandId: string; + @Column({ nullable: true, comment: "ล้างมลทิน", diff --git a/src/entities/ProfileEmployee.ts b/src/entities/ProfileEmployee.ts index 11c2159b..45f5e159 100644 --- a/src/entities/ProfileEmployee.ts +++ b/src/entities/ProfileEmployee.ts @@ -1014,7 +1014,7 @@ export class UpdateInformationProfileEmployee { } export type UpdateProfileEmployee = { - prefix: string; + prefix?: string | null; rank?: string | null; firstName: string; lastName: string; diff --git a/src/entities/TenureLevelEmployee.ts b/src/entities/TenureLevelEmployee.ts index 36ae0176..5654e306 100644 --- a/src/entities/TenureLevelEmployee.ts +++ b/src/entities/TenureLevelEmployee.ts @@ -74,7 +74,7 @@ export class TenureLevelEmployee extends EntityBase { positionLevel: string; } -export class CreateTenureLevelOfficer { +export class CreateTenureLevelEmployee { profileEmployeeId: string; positionCee: string | null; days_diff: number | null; diff --git a/src/interfaces/permission.ts b/src/interfaces/permission.ts index 4c3063de..5d22d274 100644 --- a/src/interfaces/permission.ts +++ b/src/interfaces/permission.ts @@ -39,7 +39,7 @@ class CheckAuth { } }); } - public async PermissionOrg(req: RequestWithUser, system: string, action: string) { + public async PermissionOrg(req: RequestWithUser, system: string, action: string, isDirector?: boolean) { if ( req.headers.hasOwnProperty("api_key") && req.headers["api_key"] && @@ -56,7 +56,7 @@ class CheckAuth { return await new CallAPI() .GetData(req, `/org/permission/org/${system}/${action}`) .then(async (x) => { - let privilege = x.privilege; + let privilege = isDirector && isDirector === true ? "CHILD" : x.privilege; let data: any = { root: [null], @@ -288,6 +288,9 @@ class CheckAuth { public async PermissionOrgList(req: RequestWithUser, system: string) { return await this.PermissionOrg(req, system, "LIST"); } + public async PermissionIsDirectorOrgList(req: RequestWithUser, system: string, isDirector: boolean) { + return await this.PermissionOrg(req, system, "LIST", isDirector); + } public async PermissionOrgUpdate(req: RequestWithUser, system: string) { return await this.PermissionOrg(req, system, "UPDATE"); } diff --git a/src/interfaces/utils.ts b/src/interfaces/utils.ts index 347f28af..7b55fd0f 100644 --- a/src/interfaces/utils.ts +++ b/src/interfaces/utils.ts @@ -280,7 +280,7 @@ export async function removeProfileInOrganize(profileId: string, type: string) { await AppDataSource.getRepository(PosMaster) .createQueryBuilder() .update(PosMaster) - .set({ current_holderId: null }) + .set({ current_holderId: null, isSit: false }) .where("id = :id", { id: findProfileInposMaster?.id }) .execute(); @@ -293,7 +293,7 @@ export async function removeProfileInOrganize(profileId: string, type: string) { await AppDataSource.getRepository(PosMaster) .createQueryBuilder() .update(PosMaster) - .set({ next_holderId: null }) + .set({ next_holderId: null, isSit: false }) .where("id = :id", { id: findProfileInposMasterDraft?.id }) .execute(); @@ -326,7 +326,7 @@ export async function removeProfileInOrganize(profileId: string, type: string) { await AppDataSource.getRepository(EmployeePosMaster) .createQueryBuilder() .update(EmployeePosMaster) - .set({ current_holderId: null }) + .set({ current_holderId: null, isSit: false }) .where("id = :id", { id: findProfileInEmpPosMaster?.id }) .execute(); @@ -395,43 +395,6 @@ export async function checkReturnCommandType(commandId: string) { return true; } -export async function checkExceptCommandType(commandId: string) { - const commandRepository = AppDataSource.getRepository(Command); - const commandReciveRepository = AppDataSource.getRepository(CommandRecive); - const _type = await commandRepository.findOne({ - where: { - id: commandId, - }, - relations: ["commandType"], - }); - if (!["C-PM-25", "C-PM-26"].includes(String(_type?.commandType.code))) { - return { status: false, LeaveType: null, leaveRemark: null }; - } - const _commandRecive = await commandReciveRepository.findOne({ - where: { commandId: commandId }, - }); - - let _leaveType: string = ""; - switch (String(_type?.commandType.code)) { - case "C-PM-25": { - _leaveType = "DISCIPLINE_SUSPEND"; //คำสั่งพักจากราชการ - break; - } - case "C-PM-26": { - _leaveType = "DISCIPLINE_TEMP_SUSPEND"; //คำสั่งให้ออกจากราชการไว้ก่อน - break; - } - default: { - _leaveType = ""; - } - } - return { - status: true, - LeaveType: _leaveType, - leaveRemark: _commandRecive ? _commandRecive.remarkVertical : null, - }; -} - export async function checkCommandType(commandId: string) { const commandRepository = AppDataSource.getRepository(Command); const commandReciveRepository = AppDataSource.getRepository(CommandRecive); @@ -451,7 +414,10 @@ export async function checkCommandType(commandId: string) { "C-PM-23", "C-PM-19", "C-PM-20", + "C-PM-25", + "C-PM-26", "C-PM-43", + "C-PM-48" ].includes(String(_type?.commandType.code)) ) { // return false; @@ -500,11 +466,26 @@ export async function checkCommandType(commandId: string) { _retireTypeName = "ลาออกจากราชการ"; break; } + case "C-PM-25": { + _leaveType = "DISCIPLINE_SUSPEND"; + _retireTypeName = "พักจากราชการ"; + break; + } + case "C-PM-26": { + _leaveType = "DISCIPLINE_TEMP_SUSPEND"; + _retireTypeName = "ให้ออกจากราชการไว้ก่อน"; + break; + } case "C-PM-43": { _leaveType = "RETIRE_OUT_EMP"; _retireTypeName = "ให้ออกจากราชการ"; break; } + case "C-PM-48": { + _leaveType = "RETIRE_MILITARY"; + _retireTypeName = "รับราชการทหาร"; + break; + } default: { _leaveType = ""; _retireTypeName = ""; @@ -728,6 +709,8 @@ export function commandTypePath(commandCode: string): string | null { return "/salary/report/command36/employee/report"; //SALARY case "C-PM-47": return "/placement/appointment/gazette/report"; + case "C-PM-48": + return "/retirement/resign/command48/report"; default: return null; } @@ -751,4 +734,23 @@ export function resolveNodeId(data: any) { data.rootDnaId ?? null ); +} + +export function logPositionIsSelectedChange( + positionId: string, + oldValue: boolean, + newValue: boolean, + context: { + posMasterId?: string; + userId?: string; + endpoint?: string; + action?: string; + } +) { + if (oldValue !== newValue) { + console.log(`[positionIsSelected-DEBUG] Position ${positionId}: ${oldValue} -> ${newValue}`, { + ...context, + timestamp: new Date().toISOString(), + }); + } } \ No newline at end of file diff --git a/src/keycloak/index.ts b/src/keycloak/index.ts index b59d5e81..f359a340 100644 --- a/src/keycloak/index.ts +++ b/src/keycloak/index.ts @@ -1019,7 +1019,9 @@ export async function resetPassword(username: string) { if (!users.ok) { const errorText = await users.text(); - console.error(`[resetPassword] Failed to search user. Status: ${users.status}, Error: ${errorText}`); + console.error( + `[resetPassword] Failed to search user. Status: ${users.status}, Error: ${errorText}`, + ); return false; } @@ -1047,7 +1049,9 @@ export async function resetPassword(username: string) { if (!resetResponse.ok) { const errorText = await resetResponse.text(); - console.error(`[resetPassword] Failed to send reset email. Status: ${resetResponse.status}, Error: ${errorText}`); + console.error( + `[resetPassword] Failed to send reset email. Status: ${resetResponse.status}, Error: ${errorText}`, + ); return false; } @@ -1117,7 +1121,7 @@ export async function updateUserAttributes( return false; } - console.log(`[updateUserAttributes] Successfully updated attributes for user ${userId}`); + // console.log(`[updateUserAttributes] Successfully updated attributes for user ${userId}`); return true; } catch (error) { console.error(`[updateUserAttributes] Error updating attributes for user ${userId}:`, error); diff --git a/src/middlewares/auth.ts b/src/middlewares/auth.ts index 9a571572..fc006b33 100644 --- a/src/middlewares/auth.ts +++ b/src/middlewares/auth.ts @@ -4,6 +4,7 @@ import { createDecoder, createVerifier } from "fast-jwt"; import HttpError from "../interfaces/http-error"; import HttpStatus from "../interfaces/http-status"; import { handleWebServiceAuth } from "./authWebService"; +import { handleInternalAuth } from "./authInternal"; if (!process.env.AUTH_PUBLIC_KEY && !process.env.AUTH_REALM_URL) { throw new Error("Require keycloak AUTH_PUBLIC_KEY or AUTH_REALM_URL."); @@ -39,6 +40,11 @@ export async function expressAuthentication( return { preferred_username: "bypassed" }; } + // เพิ่มการจัดการสำหรับ Internal Authentication (.NET service) + if (securityName === "internalAuth") { + return await handleInternalAuth(request); + } + // เพิ่มการจัดการสำหรับ Web Service Authentication if (securityName === "webServiceAuth") { return await handleWebServiceAuth(request); diff --git a/src/middlewares/authInternal.ts b/src/middlewares/authInternal.ts new file mode 100644 index 00000000..77da531c --- /dev/null +++ b/src/middlewares/authInternal.ts @@ -0,0 +1,30 @@ +import * as express from "express"; +import HttpError from "../interfaces/http-error"; +import HttpStatus from "../interfaces/http-status"; + +// Internal Authentication (สำหรับ Internal Service เช่น .NET) +// ตรวจสอบ API Key จาก Environment Variable (API_KEY) +export async function handleInternalAuth(request: express.Request) { + // รองรับ header หลายรูปแบบ + const apiKey = + request.headers["api-key"] || request.headers["api_key"] || request.headers["apikey"]; + + if (!apiKey || typeof apiKey !== "string") { + throw new HttpError(HttpStatus.UNAUTHORIZED, "API Key is required"); + } + + // ตรวจสอบ API Key จาก Environment Variable (API_KEY) + if (apiKey !== process.env.API_KEY) { + console.log(`[InternalAuth] Invalid API key attempt: ${apiKey.substring(0, 5)}...`); + throw new HttpError(HttpStatus.UNAUTHORIZED, "Invalid API Key"); + } + + // console.log(`[InternalAuth] Authentication successful`); + + return { + sub: "internal_service", + preferred_username: "internal_service", + name: "Internal Service", + internalKey: true, + }; +} diff --git a/src/middlewares/authWebService.ts b/src/middlewares/authWebService.ts index fa50b3fe..1f17b9cf 100644 --- a/src/middlewares/authWebService.ts +++ b/src/middlewares/authWebService.ts @@ -17,7 +17,17 @@ export async function handleWebServiceAuth(request: express.Request) { // ตรวจสอบ API Key กับฐานข้อมูล const apiKeyData = await AppDataSource.getRepository(ApiKey).findOne({ - select: { id: true, name: true, keyApi: true }, + select: { + id: true, + name: true, + keyApi: true, + accessType: true, + dnaRootId: true, + dnaChild1Id: true, + dnaChild2Id: true, + dnaChild3Id: true, + dnaChild4Id: true, + }, where: { keyApi: apiKey }, relations: ["apiNames"], }); @@ -40,6 +50,12 @@ export async function handleWebServiceAuth(request: express.Request) { name: apiKeyData.name, type: "web-service", accessApi: apiKeyData.apiNames.map((x) => x.id) ?? [], + accessType: apiKeyData.accessType, + dnaRootId: apiKeyData.dnaRootId, + dnaChild1Id: apiKeyData.dnaChild1Id, + dnaChild2Id: apiKeyData.dnaChild2Id, + dnaChild3Id: apiKeyData.dnaChild3Id, + dnaChild4Id: apiKeyData.dnaChild4Id, }; } diff --git a/src/middlewares/user.ts b/src/middlewares/user.ts index 75c84d01..09e32ef9 100644 --- a/src/middlewares/user.ts +++ b/src/middlewares/user.ts @@ -25,5 +25,11 @@ export type RequestWithUserWebService = Request & { id: string; name: string; accessApi: string[]; + accessType?: string; + dnaRootId?: string | null; + dnaChild1Id?: string | null; + dnaChild2Id?: string | null; + dnaChild3Id?: string | null; + dnaChild4Id?: string | null; }; }; diff --git a/src/migration/1779244154610-update_posMasterEmpHis_add_dna.ts b/src/migration/1779244154610-update_posMasterEmpHis_add_dna.ts new file mode 100644 index 00000000..5b7a4a1d --- /dev/null +++ b/src/migration/1779244154610-update_posMasterEmpHis_add_dna.ts @@ -0,0 +1,23 @@ +import { MigrationInterface, QueryRunner } from "typeorm"; + +export class UpdatePosMasterEmpHisAddDna1779244154610 implements MigrationInterface { + name = 'UpdatePosMasterEmpHisAddDna1779244154610' + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE \`posMasterEmployeeHistory\` ADD \`profileEmployeeId\` varchar(40) NULL COMMENT 'คีย์นอก(FK)ของตาราง profileEmployee'`); + await queryRunner.query(`ALTER TABLE \`posMasterEmployeeHistory\` ADD \`rootDnaId\` varchar(40) NULL COMMENT 'dna ของตาราง orgRoot'`); + await queryRunner.query(`ALTER TABLE \`posMasterEmployeeHistory\` ADD \`child1DnaId\` varchar(40) NULL COMMENT 'dna ของตาราง orgChild1'`); + await queryRunner.query(`ALTER TABLE \`posMasterEmployeeHistory\` ADD \`child2DnaId\` varchar(40) NULL COMMENT 'dna ของตาราง orgChild2'`); + await queryRunner.query(`ALTER TABLE \`posMasterEmployeeHistory\` ADD \`child3DnaId\` varchar(40) NULL COMMENT 'dna ของตาราง orgChild3'`); + await queryRunner.query(`ALTER TABLE \`posMasterEmployeeHistory\` ADD \`child4DnaId\` varchar(40) NULL COMMENT 'dna ของตาราง orgChild4'`); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE \`posMasterEmployeeHistory\` DROP COLUMN \`child4DnaId\``); + await queryRunner.query(`ALTER TABLE \`posMasterEmployeeHistory\` DROP COLUMN \`child3DnaId\``); + await queryRunner.query(`ALTER TABLE \`posMasterEmployeeHistory\` DROP COLUMN \`child2DnaId\``); + await queryRunner.query(`ALTER TABLE \`posMasterEmployeeHistory\` DROP COLUMN \`child1DnaId\``); + await queryRunner.query(`ALTER TABLE \`posMasterEmployeeHistory\` DROP COLUMN \`rootDnaId\``); + await queryRunner.query(`ALTER TABLE \`posMasterEmployeeHistory\` DROP COLUMN \`profileEmployeeId\``); + } +} diff --git a/src/migration/1779776860350-update_command_add_shortName.ts b/src/migration/1779776860350-update_command_add_shortName.ts new file mode 100644 index 00000000..8754d42b --- /dev/null +++ b/src/migration/1779776860350-update_command_add_shortName.ts @@ -0,0 +1,14 @@ +import { MigrationInterface, QueryRunner } from "typeorm"; + +export class UpdateCommandAddShortName1779776860350 implements MigrationInterface { + name = 'UpdateCommandAddShortName1779776860350' + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE \`command\` ADD \`shortName\` varchar(16) NULL COMMENT 'ชื่อย่อหน่วยงานที่ออกคำสั่ง'`); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE \`command\` DROP COLUMN \`shortName\``); + } + +} diff --git a/src/migration/1780634210221-update_profileDiscipline_add_refCommandId.ts b/src/migration/1780634210221-update_profileDiscipline_add_refCommandId.ts new file mode 100644 index 00000000..9e5d1851 --- /dev/null +++ b/src/migration/1780634210221-update_profileDiscipline_add_refCommandId.ts @@ -0,0 +1,15 @@ +import { MigrationInterface, QueryRunner } from "typeorm"; + +export class UpdateProfileDisciplineAddRefCommandId1780634210221 implements MigrationInterface { + name = 'UpdateProfileDisciplineAddRefCommandId1780634210221' + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE \`profileDisciplineHistory\` ADD \`refCommandId\` varchar(40) NULL COMMENT 'คีย์นอก(FK)ของตาราง command'`); + await queryRunner.query(`ALTER TABLE \`profileDiscipline\` ADD \`refCommandId\` varchar(40) NULL COMMENT 'คีย์นอก(FK)ของตาราง command'`); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE \`profileDiscipline\` DROP COLUMN \`refCommandId\``); + await queryRunner.query(`ALTER TABLE \`profileDisciplineHistory\` DROP COLUMN \`refCommandId\``); + } +} diff --git a/src/migration/1781174051201-update_profileChangeName_add_field_rank.ts b/src/migration/1781174051201-update_profileChangeName_add_field_rank.ts new file mode 100644 index 00000000..b3ec5f92 --- /dev/null +++ b/src/migration/1781174051201-update_profileChangeName_add_field_rank.ts @@ -0,0 +1,16 @@ +import { MigrationInterface, QueryRunner } from "typeorm"; + +export class UpdateProfileChangeNameAddFieldRank1781174051201 implements MigrationInterface { + name = 'UpdateProfileChangeNameAddFieldRank1781174051201' + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE \`profileChangeNameHistory\` ADD \`rank\` varchar(40) NULL COMMENT 'ยศ'`); + await queryRunner.query(`ALTER TABLE \`profileChangeName\` ADD \`rank\` varchar(40) NULL COMMENT 'ยศ'`); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE \`profileChangeName\` DROP COLUMN \`rank\``); + await queryRunner.query(`ALTER TABLE \`profileChangeNameHistory\` DROP COLUMN \`rank\``); + } + +} diff --git a/src/migration/1781517610929-update_root_c1_to_c4_add_field_code.ts b/src/migration/1781517610929-update_root_c1_to_c4_add_field_code.ts new file mode 100644 index 00000000..157ab487 --- /dev/null +++ b/src/migration/1781517610929-update_root_c1_to_c4_add_field_code.ts @@ -0,0 +1,22 @@ +import { MigrationInterface, QueryRunner } from "typeorm"; + +export class UpdateRootC1ToC4AddFieldCode1781517610929 implements MigrationInterface { + name = 'UpdateRootC1ToC4AddFieldCode1781517610929' + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE \`orgChild4\` ADD \`CHILD4_CODE\` varchar(3) NULL COMMENT 'CHILD4_CODE'`); + await queryRunner.query(`ALTER TABLE \`orgChild3\` ADD \`CHILD3_CODE\` varchar(3) NULL COMMENT 'CHILD3_CODE'`); + await queryRunner.query(`ALTER TABLE \`orgChild2\` ADD \`CHILD2_CODE\` varchar(3) NULL COMMENT 'CHILD2_CODE'`); + await queryRunner.query(`ALTER TABLE \`orgChild1\` ADD \`CHILD1_CODE\` varchar(3) NULL COMMENT 'CHILD1_CODE'`); + await queryRunner.query(`ALTER TABLE \`orgRoot\` ADD \`ROOT_CODE\` varchar(3) NULL COMMENT 'ROOT_CODE'`); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE \`orgRoot\` DROP COLUMN \`ROOT_CODE\``); + await queryRunner.query(`ALTER TABLE \`orgChild1\` DROP COLUMN \`CHILD1_CODE\``); + await queryRunner.query(`ALTER TABLE \`orgChild2\` DROP COLUMN \`CHILD2_CODE\``); + await queryRunner.query(`ALTER TABLE \`orgChild3\` DROP COLUMN \`CHILD3_CODE\``); + await queryRunner.query(`ALTER TABLE \`orgChild4\` DROP COLUMN \`CHILD4_CODE\``); + } + +} diff --git a/src/migration/1781577597453-update_add_field_code_all_tables.ts b/src/migration/1781577597453-update_add_field_code_all_tables.ts new file mode 100644 index 00000000..39ea6466 --- /dev/null +++ b/src/migration/1781577597453-update_add_field_code_all_tables.ts @@ -0,0 +1,51 @@ +import { MigrationInterface, QueryRunner } from "typeorm"; + +export class UpdateAddFieldCodeAllTables1781577597453 implements MigrationInterface { + name = 'UpdateAddFieldCodeAllTables1781577597453' + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE \`orgChild4\` ADD \`ROOT_CODE\` varchar(3) NULL COMMENT 'ROOT_CODE'`); + await queryRunner.query(`ALTER TABLE \`orgChild4\` ADD \`CHILD1_CODE\` varchar(3) NULL COMMENT 'CHILD1_CODE'`); + await queryRunner.query(`ALTER TABLE \`orgChild4\` ADD \`CHILD2_CODE\` varchar(3) NULL COMMENT 'CHILD2_CODE'`); + await queryRunner.query(`ALTER TABLE \`orgChild4\` ADD \`CHILD3_CODE\` varchar(3) NULL COMMENT 'CHILD3_CODE'`); + await queryRunner.query(`ALTER TABLE \`orgChild3\` ADD \`ROOT_CODE\` varchar(3) NULL COMMENT 'ROOT_CODE'`); + await queryRunner.query(`ALTER TABLE \`orgChild3\` ADD \`CHILD1_CODE\` varchar(3) NULL COMMENT 'CHILD1_CODE'`); + await queryRunner.query(`ALTER TABLE \`orgChild3\` ADD \`CHILD2_CODE\` varchar(3) NULL COMMENT 'CHILD2_CODE'`); + await queryRunner.query(`ALTER TABLE \`orgChild3\` ADD \`CHILD4_CODE\` varchar(3) NULL COMMENT 'CHILD4_CODE'`); + await queryRunner.query(`ALTER TABLE \`orgChild2\` ADD \`ROOT_CODE\` varchar(3) NULL COMMENT 'ROOT_CODE'`); + await queryRunner.query(`ALTER TABLE \`orgChild2\` ADD \`CHILD1_CODE\` varchar(3) NULL COMMENT 'CHILD1_CODE'`); + await queryRunner.query(`ALTER TABLE \`orgChild2\` ADD \`CHILD3_CODE\` varchar(3) NULL COMMENT 'CHILD3_CODE'`); + await queryRunner.query(`ALTER TABLE \`orgChild2\` ADD \`CHILD4_CODE\` varchar(3) NULL COMMENT 'CHILD4_CODE'`); + await queryRunner.query(`ALTER TABLE \`orgChild1\` ADD \`ROOT_CODE\` varchar(3) NULL COMMENT 'ROOT_CODE'`); + await queryRunner.query(`ALTER TABLE \`orgChild1\` ADD \`CHILD2_CODE\` varchar(3) NULL COMMENT 'CHILD2_CODE'`); + await queryRunner.query(`ALTER TABLE \`orgChild1\` ADD \`CHILD3_CODE\` varchar(3) NULL COMMENT 'CHILD3_CODE'`); + await queryRunner.query(`ALTER TABLE \`orgChild1\` ADD \`CHILD4_CODE\` varchar(3) NULL COMMENT 'CHILD4_CODE'`); + await queryRunner.query(`ALTER TABLE \`orgRoot\` ADD \`CHILD1_CODE\` varchar(3) NULL COMMENT 'CHILD1_CODE'`); + await queryRunner.query(`ALTER TABLE \`orgRoot\` ADD \`CHILD2_CODE\` varchar(3) NULL COMMENT 'CHILD2_CODE'`); + await queryRunner.query(`ALTER TABLE \`orgRoot\` ADD \`CHILD3_CODE\` varchar(3) NULL COMMENT 'CHILD3_CODE'`); + await queryRunner.query(`ALTER TABLE \`orgRoot\` ADD \`CHILD4_CODE\` varchar(3) NULL COMMENT 'CHILD4_CODE'`); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE \`orgRoot\` DROP COLUMN \`CHILD4_CODE\``); + await queryRunner.query(`ALTER TABLE \`orgRoot\` DROP COLUMN \`CHILD3_CODE\``); + await queryRunner.query(`ALTER TABLE \`orgRoot\` DROP COLUMN \`CHILD2_CODE\``); + await queryRunner.query(`ALTER TABLE \`orgRoot\` DROP COLUMN \`CHILD1_CODE\``); + await queryRunner.query(`ALTER TABLE \`orgChild1\` DROP COLUMN \`CHILD4_CODE\``); + await queryRunner.query(`ALTER TABLE \`orgChild1\` DROP COLUMN \`CHILD3_CODE\``); + await queryRunner.query(`ALTER TABLE \`orgChild1\` DROP COLUMN \`CHILD2_CODE\``); + await queryRunner.query(`ALTER TABLE \`orgChild1\` DROP COLUMN \`ROOT_CODE\``); + await queryRunner.query(`ALTER TABLE \`orgChild2\` DROP COLUMN \`CHILD4_CODE\``); + await queryRunner.query(`ALTER TABLE \`orgChild2\` DROP COLUMN \`CHILD3_CODE\``); + await queryRunner.query(`ALTER TABLE \`orgChild2\` DROP COLUMN \`CHILD1_CODE\``); + await queryRunner.query(`ALTER TABLE \`orgChild2\` DROP COLUMN \`ROOT_CODE\``); + await queryRunner.query(`ALTER TABLE \`orgChild3\` DROP COLUMN \`CHILD4_CODE\``); + await queryRunner.query(`ALTER TABLE \`orgChild3\` DROP COLUMN \`CHILD2_CODE\``); + await queryRunner.query(`ALTER TABLE \`orgChild3\` DROP COLUMN \`CHILD1_CODE\``); + await queryRunner.query(`ALTER TABLE \`orgChild3\` DROP COLUMN \`ROOT_CODE\``); + await queryRunner.query(`ALTER TABLE \`orgChild4\` DROP COLUMN \`CHILD3_CODE\``); + await queryRunner.query(`ALTER TABLE \`orgChild4\` DROP COLUMN \`CHILD2_CODE\``); + await queryRunner.query(`ALTER TABLE \`orgChild4\` DROP COLUMN \`CHILD1_CODE\``); + await queryRunner.query(`ALTER TABLE \`orgChild4\` DROP COLUMN \`ROOT_CODE\``); + } +} diff --git a/src/scripts/ClearOldOrgRevision.ts b/src/scripts/ClearOldOrgRevision.ts new file mode 100644 index 00000000..4d2be7a7 --- /dev/null +++ b/src/scripts/ClearOldOrgRevision.ts @@ -0,0 +1,27 @@ +import "dotenv/config"; +import "reflect-metadata"; +import { AppDataSource } from "../database/data-source"; +import { clearOldOrgRevisionData } from "../services/ClearOldOrgRevisionService"; + +// "clear:old-org-revision": "ts-node src/scripts/ClearOldOrgRevision.ts", + +const defaultOrgRevisionId = "24dacf63-d289-496c-8102-8b25079dbaf2"; + +async function main(): Promise { + const orgRevisionId = process.argv[2] || defaultOrgRevisionId; + + try { + await AppDataSource.initialize(); + const result = await clearOldOrgRevisionData(orgRevisionId); + console.info(JSON.stringify(result, null, 2)); + } catch (error) { + console.error("[ClearOldOrgRevision] Failed:", error); + process.exitCode = 1; + } finally { + if (AppDataSource.isInitialized) { + await AppDataSource.destroy(); + } + } +} + +void main(); diff --git a/src/services/ClearOldOrgRevisionService.ts b/src/services/ClearOldOrgRevisionService.ts new file mode 100644 index 00000000..056206f3 --- /dev/null +++ b/src/services/ClearOldOrgRevisionService.ts @@ -0,0 +1,232 @@ +import { EntityManager, EntityTarget, In } from "typeorm"; +import { AppDataSource } from "../database/data-source"; +import { OrgRevision } from "../entities/OrgRevision"; +import { PosMaster } from "../entities/PosMaster"; +import { Position } from "../entities/Position"; +import { OrgRoot } from "../entities/OrgRoot"; +import { OrgChild1 } from "../entities/OrgChild1"; +import { OrgChild2 } from "../entities/OrgChild2"; +import { OrgChild3 } from "../entities/OrgChild3"; +import { OrgChild4 } from "../entities/OrgChild4"; +import { PosMasterAct } from "../entities/PosMasterAct"; +import { PosMasterAssign } from "../entities/PosMasterAssign"; +import { PermissionOrg } from "../entities/PermissionOrg"; +import { PermissionProfile } from "../entities/PermissionProfile"; +import { EmployeePosMaster } from "../entities/EmployeePosMaster"; +import { EmployeeTempPosMaster } from "../entities/EmployeeTempPosMaster"; +import { EmployeePosition } from "../entities/EmployeePosition"; +import { orgStructureCache } from "../utils/OrgStructureCache"; + +export interface ClearOldOrgRevisionSummary { + orgRevisionId: string; + orgRevisionName: string; + deleted: { + positions: number; + employeePositionsByPosMaster: number; + employeePositionsByTempPosMaster: number; + posMasterActsByParent: number; + posMasterActsByChild: number; + posMasterAssigns: number; + posMasters: number; + employeePosMasters: number; + employeeTempPosMasters: number; + permissionOrgs: number; + permissionProfiles: number; + orgChild4s: number; + orgChild3s: number; + orgChild2s: number; + orgChild1s: number; + orgRoots: number; + orgRevisions: number; + }; +} + +interface OrgRevisionSnapshot { + id: string; + orgRevisionName: string; + orgRevisionIsCurrent: boolean; + orgRevisionIsDraft: boolean; +} + +export async function clearOldOrgRevisionData( + orgRevisionId: string, +): Promise { + const result = await AppDataSource.transaction(async (manager) => { + const orgRevision = await manager.findOne(OrgRevision, { + where: { id: orgRevisionId }, + select: ["id", "orgRevisionName", "orgRevisionIsCurrent", "orgRevisionIsDraft"], + }); + + if (!orgRevision) { + throw new Error(`ไม่พบ orgRevision ที่ต้องการล้างข้อมูล: ${orgRevisionId}`); + } + + validateOrgRevisionForDeletion(orgRevision); + + const [posMasters, orgRoots, employeePosMasters, employeeTempPosMasters] = await Promise.all([ + manager.find(PosMaster, { + where: { orgRevisionId }, + select: ["id"], + }), + manager.find(OrgRoot, { + where: { orgRevisionId }, + select: ["id"], + }), + manager.find(EmployeePosMaster, { + where: { orgRevisionId }, + select: ["id"], + }), + manager.find(EmployeeTempPosMaster, { + where: { orgRevisionId }, + select: ["id"], + }), + ]); + + const posMasterIds = posMasters.map((item) => item.id); + const orgRootIds = orgRoots.map((item) => item.id); + const employeePosMasterIds = employeePosMasters.map((item) => item.id); + const employeeTempPosMasterIds = employeeTempPosMasters.map((item) => item.id); + + const [ + positionsCount, + employeePositionsByPosMasterCount, + employeePositionsByTempPosMasterCount, + posMasterActsByParentCount, + posMasterActsByChildCount, + posMasterAssignsCount, + permissionOrgsCount, + permissionProfilesCount, + orgChild4sCount, + orgChild3sCount, + orgChild2sCount, + orgChild1sCount, + ] = await Promise.all([ + countByIds(manager, Position, "posMasterId", posMasterIds), + countByIds(manager, EmployeePosition, "posMasterId", employeePosMasterIds), + countByIds(manager, EmployeePosition, "posMasterTempId", employeeTempPosMasterIds), + countByIds(manager, PosMasterAct, "posMasterId", posMasterIds), + countByIds(manager, PosMasterAct, "posMasterChildId", posMasterIds), + countByIds(manager, PosMasterAssign, "posMasterId", posMasterIds), + countByIds(manager, PermissionOrg, "orgRootId", orgRootIds), + countByIds(manager, PermissionProfile, "orgRootId", orgRootIds), + manager.count(OrgChild4, { where: { orgRevisionId } }), + manager.count(OrgChild3, { where: { orgRevisionId } }), + manager.count(OrgChild2, { where: { orgRevisionId } }), + manager.count(OrgChild1, { where: { orgRevisionId } }), + ]); + + if (positionsCount > 0) { + await manager.delete(Position, { posMasterId: In(posMasterIds) }); + } + if (employeePositionsByPosMasterCount > 0) { + await manager.delete(EmployeePosition, { posMasterId: In(employeePosMasterIds) }); + } + if (employeePositionsByTempPosMasterCount > 0) { + await manager.delete(EmployeePosition, { posMasterTempId: In(employeeTempPosMasterIds) }); + } + if (posMasterActsByParentCount > 0) { + await manager.delete(PosMasterAct, { posMasterId: In(posMasterIds) }); + } + if (posMasterActsByChildCount > 0) { + await manager.delete(PosMasterAct, { posMasterChildId: In(posMasterIds) }); + } + if (posMasterAssignsCount > 0) { + await manager.delete(PosMasterAssign, { posMasterId: In(posMasterIds) }); + } + + const posMastersCount = posMasterIds.length; + const employeePosMastersCount = employeePosMasterIds.length; + const employeeTempPosMastersCount = employeeTempPosMasterIds.length; + + if (posMastersCount > 0) { + await manager.delete(PosMaster, { orgRevisionId }); + } + if (employeePosMastersCount > 0) { + await manager.delete(EmployeePosMaster, { orgRevisionId }); + } + if (employeeTempPosMastersCount > 0) { + await manager.delete(EmployeeTempPosMaster, { orgRevisionId }); + } + + if (permissionOrgsCount > 0) { + await manager.delete(PermissionOrg, { orgRootId: In(orgRootIds) }); + } + if (permissionProfilesCount > 0) { + await manager.delete(PermissionProfile, { orgRootId: In(orgRootIds) }); + } + + if (orgChild4sCount > 0) { + await manager.delete(OrgChild4, { orgRevisionId }); + } + if (orgChild3sCount > 0) { + await manager.delete(OrgChild3, { orgRevisionId }); + } + if (orgChild2sCount > 0) { + await manager.delete(OrgChild2, { orgRevisionId }); + } + if (orgChild1sCount > 0) { + await manager.delete(OrgChild1, { orgRevisionId }); + } + + const orgRootsCount = orgRootIds.length; + if (orgRootsCount > 0) { + await manager.delete(OrgRoot, { orgRevisionId }); + } + + await manager.delete(OrgRevision, { id: orgRevisionId }); + + return { + orgRevisionId: orgRevision.id, + orgRevisionName: orgRevision.orgRevisionName, + deleted: { + positions: positionsCount, + employeePositionsByPosMaster: employeePositionsByPosMasterCount, + employeePositionsByTempPosMaster: employeePositionsByTempPosMasterCount, + posMasterActsByParent: posMasterActsByParentCount, + posMasterActsByChild: posMasterActsByChildCount, + posMasterAssigns: posMasterAssignsCount, + posMasters: posMastersCount, + employeePosMasters: employeePosMastersCount, + employeeTempPosMasters: employeeTempPosMastersCount, + permissionOrgs: permissionOrgsCount, + permissionProfiles: permissionProfilesCount, + orgChild4s: orgChild4sCount, + orgChild3s: orgChild3sCount, + orgChild2s: orgChild2sCount, + orgChild1s: orgChild1sCount, + orgRoots: orgRootsCount, + orgRevisions: 1, + }, + }; + }); + + orgStructureCache.invalidate(orgRevisionId); + return result; +} + +function validateOrgRevisionForDeletion(orgRevision: OrgRevisionSnapshot): void { + if (orgRevision.orgRevisionIsCurrent) { + throw new Error(`ไม่สามารถลบ orgRevision ปัจจุบันได้: ${orgRevision.id}`); + } + + if (orgRevision.orgRevisionIsDraft) { + throw new Error(`ไม่สามารถลบ orgRevision แบบร่างได้ด้วยสคริปต์นี้: ${orgRevision.id}`); + } +} + +async function countByIds( + manager: EntityManager, + entity: EntityTarget, + field: keyof Entity, + ids: string[], +): Promise { + if (ids.length === 0) { + return 0; + } + + const alias = "entity"; + return manager + .createQueryBuilder(entity, alias) + .where(`${alias}.${String(field)} IN (:...ids)`, { ids }) + .getCount(); +} diff --git a/src/services/ExecuteOfficerProfileService.ts b/src/services/ExecuteOfficerProfileService.ts new file mode 100644 index 00000000..0d77719f --- /dev/null +++ b/src/services/ExecuteOfficerProfileService.ts @@ -0,0 +1,1350 @@ +import { In, Like } from "typeorm"; +import { AppDataSource } from "../database/data-source"; +import HttpError from "../interfaces/http-error"; +import HttpStatusCode from "../interfaces/http-status"; +import { CreateProfileAllFields, Profile } from "../entities/Profile"; +import { ProfileEmployee } from "../entities/ProfileEmployee"; +import { CreateProfileSalary, ProfileSalary } from "../entities/ProfileSalary"; +import { ProfileSalaryHistory } from "../entities/ProfileSalaryHistory"; +import { CreateProfileEducation, ProfileEducation } from "../entities/ProfileEducation"; +import { ProfileEducationHistory } from "../entities/ProfileEducationHistory"; +import { + CreateProfileCertificate, + ProfileCertificate, +} from "../entities/ProfileCertificate"; +import { ProfileCertificateHistory } from "../entities/ProfileCertificateHistory"; +import { ProfileFamilyCouple } from "../entities/ProfileFamilyCouple"; +import { ProfileFamilyCoupleHistory } from "../entities/ProfileFamilyCoupleHistory"; +import { ProfileFamilyFather } from "../entities/ProfileFamilyFather"; +import { ProfileFamilyFatherHistory } from "../entities/ProfileFamilyFatherHistory"; +import { ProfileFamilyMother } from "../entities/ProfileFamilyMother"; +import { ProfileFamilyMotherHistory } from "../entities/ProfileFamilyMotherHistory"; +import { CreateProfileInsignia, ProfileInsignia } from "../entities/ProfileInsignia"; +import { ProfileInsigniaHistory } from "../entities/ProfileInsigniaHistory"; +import { ProfileAvatar } from "../entities/ProfileAvatar"; +import { PosLevel } from "../entities/PosLevel"; +import { PosType } from "../entities/PosType"; +import { Province } from "../entities/Province"; +import { District } from "../entities/District"; +import { SubDistrict } from "../entities/SubDistrict"; +import { OrgRoot } from "../entities/OrgRoot"; +import { RoleKeycloak } from "../entities/RoleKeycloak"; +import { PosMaster } from "../entities/PosMaster"; +import { Position } from "../entities/Position"; +import { Command } from "../entities/Command"; +import { + calculateRetireDate, + calculateRetireLaw, + removeProfileInOrganize, + setLogDataDiff, +} from "../interfaces/utils"; +import { + addUserRoles, + createUser, + getRoleMappings, + getRoles, + getUserByUsername, + removeUserRoles, + updateUserAttributes, +} from "../keycloak"; +import { CreatePosMasterHistoryOfficer } from "./PositionService"; +import { getOrgFullName, getPosMasterNo } from "../utils/org-formatting"; +import CallAPI from "../interfaces/call-api"; + +/** + * Input: ตำแหน่งที่จะกำหนดให้กับ profile ใหม่ (ส่งมาจากฝั่งบรรจุ) + */ +export interface OfficerPositionInput { + posmasterId: string; + positionId: string; + positionName: string; + posTypeId: string; + posLevelId: string; + posExecutiveId: string | null; + positionField: string | null; + positionExecutiveField: string | null; + positionArea: string | null; +} + +/** + * Input: ข้อมูลคู่สมรส + */ +export interface OfficerMarryInput { + marry?: boolean | null; + marryPrefix?: string | null; + marryFirstName?: string | null; + marryLastName?: string | null; + marryOccupation?: string | null; + marryNationality?: string | null; +} + +/** + * Input: ข้อมูลบิดา + */ +export interface OfficerFatherInput { + fatherPrefix?: string | null; + fatherFirstName?: string | null; + fatherLastName?: string | null; + fatherOccupation?: string | null; + fatherNationality?: string | null; +} + +/** + * Input: ข้อมูลมารดา + */ +export interface OfficerMotherInput { + motherPrefix?: string | null; + motherFirstName?: string | null; + motherLastName?: string | null; + motherOccupation?: string | null; + motherNationality?: string | null; +} + +/** + * Input: ข้อมูล 1 คนที่จะบรรจุ/แต่งตั้ง (ตรงกับ body.data[i] ของ endpoint เดิม) + */ +export interface OfficerProfileItem { + bodyProfile: CreateProfileAllFields; + bodyEducations?: CreateProfileEducation[]; + bodyCertificates?: CreateProfileCertificate[]; + bodySalarys?: CreateProfileSalary | null; + bodyPosition?: OfficerPositionInput | null; + bodyMarry?: OfficerMarryInput | null; + bodyFather?: OfficerFatherInput | null; + bodyMother?: OfficerMotherInput | null; +} + +/** + * Context สำหรับ audit/log — แยกขาดจาก HTTP request เพื่อให้ service + * สามารถถูกเรียกได้ทั้งจาก endpoint (ผ่าน Express req) และจาก message consumer + * (ผ่าน pseudo-req ที่สร้างขึ้น) โดยไม่ผูกติดกับ HTTP layer + */ +export interface ExecutionContext { + /** user ที่ทริกเกอร์งานนี้ (sub = keycloak id, name = ชื่อเต็ม) */ + user: { sub: string; name: string }; + /** Express request (สำหรับ subscriber pattern: setLogDataDiff, save({data: req})) */ + req?: any; +} + +/** + * Service สำหรับสร้าง/อัปเดตทะเบียนประวัติข้าราชการ (Profile) หลังออกคำสั่งบรรจุ + * + * ถูกออกแบบมาเพื่อแก้ปัญหา "Circular Dependency" ระหว่าง API Org กับ API บรรจุ + * โดยให้ฝั่งบรรจุส่ง resultData กลับมา แล้วฝั่ง Org ประมวลผลสร้าง profile เอง + * ที่ต้นทาง (Linear Flow) แทนการเรียกซ้อนกันกลับไปมา + * + * - endpoint /org/command/excexute/create-officer-profile เรียกผ่าน service นี้ (thin wrapper) + * - consumer ใน rabbitmq handler เรียกผ่าน service นี้โดยตรง (no HTTP loopback) + * + * Behavior ทั้งหมด preserve จาก CommandController.CreateOfficeProfileExcecute ต้นฉบับ + */ +export class ExecuteOfficerProfileService { + private commandRepository = AppDataSource.getRepository(Command); + private profileRepository = AppDataSource.getRepository(Profile); + private profileEmployeeRepository = AppDataSource.getRepository(ProfileEmployee); + private salaryRepo = AppDataSource.getRepository(ProfileSalary); + private salaryHistoryRepo = AppDataSource.getRepository(ProfileSalaryHistory); + private posLevelRepo = AppDataSource.getRepository(PosLevel); + private posTypeRepo = AppDataSource.getRepository(PosType); + private provinceRepo = AppDataSource.getRepository(Province); + private districtRepo = AppDataSource.getRepository(District); + private subDistrictRepo = AppDataSource.getRepository(SubDistrict); + private orgRootRepository = AppDataSource.getRepository(OrgRoot); + private roleKeycloakRepo = AppDataSource.getRepository(RoleKeycloak); + private posMasterRepository = AppDataSource.getRepository(PosMaster); + private positionRepository = AppDataSource.getRepository(Position); + private profileEducationRepo = AppDataSource.getRepository(ProfileEducation); + private profileEducationHistoryRepo = AppDataSource.getRepository(ProfileEducationHistory); + private certificateRepo = AppDataSource.getRepository(ProfileCertificate); + private certificateHistoryRepo = AppDataSource.getRepository(ProfileCertificateHistory); + private profileFamilyCoupleRepo = AppDataSource.getRepository(ProfileFamilyCouple); + private profileFamilyCoupleHistoryRepo = AppDataSource.getRepository(ProfileFamilyCoupleHistory); + private profileFamilyFatherRepo = AppDataSource.getRepository(ProfileFamilyFather); + private profileFamilyFatherHistoryRepo = AppDataSource.getRepository(ProfileFamilyFatherHistory); + private profileFamilyMotherRepo = AppDataSource.getRepository(ProfileFamilyMother); + private profileFamilyMotherHistoryRepo = AppDataSource.getRepository(ProfileFamilyMotherHistory); + private insigniaRepo = AppDataSource.getRepository(ProfileInsignia); + private insigniaHistoryRepo = AppDataSource.getRepository(ProfileInsigniaHistory); + private avatarRepository = AppDataSource.getRepository(ProfileAvatar); + + /** + * ประมวลผลสร้าง/อัปเดท profile ข้าราชการ ตามข้อมูลที่ฝั่งบรรจุส่งมา + * + * @param data รายการข้อมูลที่จะประมวลผล (1 คำสั่งอาจมีหลายคน) + * @param ctx context สำหรับ audit/log + */ + async executeCreateOfficerProfile( + data: OfficerProfileItem[], + ctx: ExecutionContext, + ): Promise { + console.log("[ExecuteOfficerProfileService] Starting executeCreateOfficerProfile"); + console.log("[ExecuteOfficerProfileService] Request body count:", data?.length); + + // ───────────────────────────────────────────────────────────── + // Normalize date fields + // ผ่าน HTTP endpoint → tsoa แปลง ISO string → Date ให้อัตโนมัติ + // แต่ผ่าน RabbitMQ handler (axios) → จะได้ string → ต้องแปลงเอง + // ไม่งั้น calculateRetireDate/getFullYear ฯลฯ จะพัง + // ───────────────────────────────────────────────────────────── + const toDate = (v: any): Date | null => { + if (v == null || v === "") return null; + if (v instanceof Date) return isNaN(v.getTime()) ? null : v; + const d = new Date(v); + return isNaN(d.getTime()) ? null : d; + }; + for (const item of data ?? []) { + const bp = item.bodyProfile as any; + if (bp) { + bp.birthDate = toDate(bp.birthDate); + bp.dateStart = toDate(bp.dateStart); + bp.dateAppoint = toDate(bp.dateAppoint); + bp.dateRetire = toDate(bp.dateRetire); + } + const bs = item.bodySalarys as any; + if (bs) { + bs.commandDateAffect = toDate(bs.commandDateAffect); + bs.commandDateSign = toDate(bs.commandDateSign); + } + if (item.bodyEducations) { + for (const edu of item.bodyEducations as any[]) { + edu.startDate = toDate(edu.startDate); + edu.endDate = toDate(edu.endDate); + edu.finishDate = toDate(edu.finishDate); + } + } + if (item.bodyCertificates) { + for (const cer of item.bodyCertificates as any[]) { + cer.expireDate = toDate(cer.expireDate); + cer.issueDate = toDate(cer.issueDate); + } + } + } + + const req = ctx.req; + const roleKeycloak = await this.roleKeycloakRepo.findOne({ + where: { name: Like("USER") }, + }); + console.log("[ExecuteOfficerProfileService] roleKeycloak found:", !!roleKeycloak); + const list = await getRoles(); + console.log( + "[ExecuteOfficerProfileService] Roles list retrieved, length:", + Array.isArray(list) ? list.length : "not array", + ); + if (!Array.isArray(list)) { + console.error( + "[ExecuteOfficerProfileService] Failed - Cannot get role(s) data from the server", + ); + throw new Error("Failed. Cannot get role(s) data from the server."); + } + let _posNumCodeSit: string = ""; + let _posNumCodeSitAbb: string = ""; + console.log("[ExecuteOfficerProfileService] Getting command data"); + const _command = await this.commandRepository.findOne({ + where: { + id: data.find((x) => x.bodySalarys?.commandId)?.bodySalarys?.commandId ?? "", + }, + }); + console.log( + "[ExecuteOfficerProfileService] Command found:", + !!_command, + "isBangkok:", + _command?.isBangkok, + ); + if (_command) { + if (_command?.isBangkok?.toLocaleUpperCase() == "OFFICE") { + console.log("[ExecuteOfficerProfileService] Setting position codes for OFFICE"); + const orgRootDeputy = await this.orgRootRepository.findOne({ + where: { + isDeputy: true, + orgRevision: { + orgRevisionIsCurrent: true, + orgRevisionIsDraft: false, + }, + }, + relations: ["orgRevision"], + }); + _posNumCodeSit = orgRootDeputy + ? orgRootDeputy?.orgRootName + : "สำนักปลัดกรุงเทพมหานคร"; + _posNumCodeSitAbb = orgRootDeputy ? orgRootDeputy?.orgRootShortName : "สนป."; + console.log( + "[ExecuteOfficerProfileService] OFFICE position codes set:", + _posNumCodeSit, + _posNumCodeSitAbb, + ); + } else if (_command?.isBangkok?.toLocaleUpperCase() == "BANGKOK") { + console.log("[ExecuteOfficerProfileService] Setting position codes for BANGKOK"); + _posNumCodeSit = "กรุงเทพมหานคร"; + _posNumCodeSitAbb = "กทม."; + console.log( + "[ExecuteOfficerProfileService] BANGKOK position codes set:", + _posNumCodeSit, + _posNumCodeSitAbb, + ); + } else { + console.log("[ExecuteOfficerProfileService] Setting position codes from admin profile"); + let _profileAdmin = await this.profileRepository.findOne({ + where: { + keycloak: _command?.createdUserId.toString(), + current_holders: { + orgRevision: { + orgRevisionIsCurrent: true, + orgRevisionIsDraft: false, + }, + }, + }, + relations: [ + "current_holders", + "current_holders.orgRevision", + "current_holders.orgRoot", + ], + }); + _posNumCodeSit = + _profileAdmin?.current_holders.find((x) => x.orgRoot.orgRootName)?.orgRoot + .orgRootName ?? ""; + _posNumCodeSitAbb = + _profileAdmin?.current_holders.find((x) => x.orgRoot.orgRootShortName)?.orgRoot + .orgRootShortName ?? ""; + console.log( + "[ExecuteOfficerProfileService] Admin profile position codes set:", + _posNumCodeSit, + _posNumCodeSitAbb, + ); + } + } + const before = null; + const meta = { + createdUserId: ctx.user.sub, + createdFullName: ctx.user.name, + lastUpdateUserId: ctx.user.sub, + lastUpdateFullName: ctx.user.name, + createdAt: new Date(), + lastUpdatedAt: new Date(), + }; + console.log( + "[ExecuteOfficerProfileService] Starting to process", + data.length, + "profile(s)", + ); + await Promise.all( + data.map(async (item, index) => { + console.log( + "[ExecuteOfficerProfileService] Processing item", + index + 1, + "of", + data.length, + ); + const _null: any = null; + if (item.bodyProfile.posLevelId === "") item.bodyProfile.posLevelId = null; + if (item.bodyProfile.posTypeId === "") item.bodyProfile.posTypeId = null; + if ( + item.bodyProfile.posLevelId && + !(await this.posLevelRepo.findOneBy({ id: item.bodyProfile.posLevelId })) + ) { + console.error( + "[ExecuteOfficerProfileService] ไม่พบข้อมูลระดับตำแหน่งนี้ posLevelId:", + item.bodyProfile.posLevelId, + ); + throw new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบข้อมูลระดับตำแหน่งนี้"); + } + if ( + item.bodyProfile.posTypeId && + !(await this.posTypeRepo.findOneBy({ id: item.bodyProfile.posTypeId })) + ) { + console.error( + "[ExecuteOfficerProfileService] ไม่พบข้อมูลประเภทตำแหน่งนี้ posTypeId:", + item.bodyProfile.posTypeId, + ); + throw new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบข้อมูลประเภทตำแหน่งนี้"); + } + + console.log( + "[ExecuteOfficerProfileService] Processing citizenId:", + item.bodyProfile.citizenId, + ); + let registrationProvinceId = await this.provinceRepo.findOneBy({ + id: item.bodyProfile.registrationProvinceId ?? "", + }); + let registrationDistrictId = await this.districtRepo.findOneBy({ + id: item.bodyProfile.registrationDistrictId ?? "", + }); + let registrationSubDistrictId = await this.subDistrictRepo.findOneBy({ + id: item.bodyProfile.registrationSubDistrictId ?? "", + }); + let currentProvinceId = await this.provinceRepo.findOneBy({ + id: item.bodyProfile.currentProvinceId ?? "", + }); + let currentDistrictId = await this.districtRepo.findOneBy({ + id: item.bodyProfile.currentDistrictId ?? "", + }); + let currentSubDistrictId = await this.subDistrictRepo.findOneBy({ + id: item.bodyProfile.currentSubDistrictId ?? "", + }); + console.log("[ExecuteOfficerProfileService] Address validation completed"); + + let _dateRetire = + item.bodyProfile.birthDate == null + ? _null + : calculateRetireDate(item.bodyProfile.birthDate); + let _dateRetireLaw = + item.bodyProfile.birthDate == null + ? _null + : calculateRetireLaw(item.bodyProfile.birthDate); + + let userKeycloakId: any; + let result: any; + console.log( + "[ExecuteOfficerProfileService] Checking Keycloak user for citizenId:", + item.bodyProfile.citizenId, + ); + const checkUser = await getUserByUsername(item.bodyProfile.citizenId); + console.log( + "[ExecuteOfficerProfileService] Keycloak user exists:", + checkUser.length > 0, + ); + if (checkUser.length == 0) { + console.log("[ExecuteOfficerProfileService] Creating new Keycloak user"); + let password = item.bodyProfile.citizenId; + if (item.bodyProfile.birthDate != null) { + const _date = new Date(item.bodyProfile.birthDate.toDateString()) + .getDate() + .toString() + .padStart(2, "0"); + const _month = ( + new Date(item.bodyProfile.birthDate.toDateString()).getMonth() + 1 + ) + .toString() + .padStart(2, "0"); + const _year = new Date(item.bodyProfile.birthDate.toDateString()).getFullYear() + 543; + password = `${_date}${_month}${_year}`; + } + console.log( + "[ExecuteOfficerProfileService] Calling createUser for:", + item.bodyProfile.citizenId, + ); + console.log( + "[ExecuteOfficerProfileService] createUser data - firstName:", + item.bodyProfile.firstName, + "lastName:", + item.bodyProfile.lastName, + ); + // กรอง "." ออกจาก firstName ก่อนส่งไป keycloak (ป้องกัน . หรืออักขระอื่นๆ) + const sanitizedFirstName = item.bodyProfile.firstName?.replace(/\./g, "") ?? ""; + userKeycloakId = await createUser(item.bodyProfile.citizenId, password, { + firstName: sanitizedFirstName, + lastName: item.bodyProfile.lastName, + }); + if ( + userKeycloakId && + typeof userKeycloakId === "object" && + userKeycloakId.errorMessage + ) { + console.error( + "[ExecuteOfficerProfileService] createUser FAILED - field:", + userKeycloakId.field, + "errorMessage:", + userKeycloakId.errorMessage, + "params:", + userKeycloakId.params, + ); + throw new HttpError( + HttpStatusCode.BAD_REQUEST, + `Keycloak validation failed: ${userKeycloakId.field} - ${userKeycloakId.errorMessage}`, + ); + } + console.log( + "[ExecuteOfficerProfileService] User created in Keycloak, userKeycloakId:", + userKeycloakId, + ); + result = await addUserRoles( + userKeycloakId, + list + .filter((v) => v.name === "USER") + .map((x) => ({ + id: x.id, + name: x.name, + })), + ); + console.log( + "[ExecuteOfficerProfileService] USER role assigned to new user, result:", + result, + ); + } else { + console.log("[ExecuteOfficerProfileService] Updating existing Keycloak user"); + userKeycloakId = checkUser[0].id; + console.log( + "[ExecuteOfficerProfileService] Existing userKeycloakId:", + userKeycloakId, + ); + const rolesData = await getRoleMappings(userKeycloakId); + if (rolesData) { + const _delRole = rolesData.map((x: any) => ({ + id: x.id, + name: x.name, + })); + console.log( + "[ExecuteOfficerProfileService] Removing old roles:", + _delRole.length, + ); + await removeUserRoles(userKeycloakId, _delRole); + } + result = await addUserRoles( + userKeycloakId, + list + .filter((v) => v.name === "USER") + .map((x) => ({ + id: x.id, + name: x.name, + })), + ); + console.log( + "[ExecuteOfficerProfileService] USER role assigned to existing user", + ); + } + + let profile: any = await this.profileRepository.findOne({ + where: { citizenId: item.bodyProfile.citizenId /*, isActive: true */ }, + relations: ["roleKeycloaks", "profileInsignias", "profileAvatars"], + }); + console.log( + "[ExecuteOfficerProfileService] Profile found:", + !!profile, + "for citizenId:", + item.bodyProfile.citizenId, + ); + let _oldInsigniaIds: string[] = []; + let _oldSalaries: any[] = []; + //ลูกจ้างประจำ หรือ บุคคลภายนอก + if (!profile) { + console.log( + "[ExecuteOfficerProfileService] No existing profile found, creating new profile", + ); + //กรณีลูกจ้างประจำมาสอบเป็นข้าราชการ ต้อง update สถานะโปรไฟล์เดิม + let profileEmployee: any = await this.profileEmployeeRepository.findOne({ + where: { citizenId: item.bodyProfile.citizenId }, + relations: ["profileInsignias", "roleKeycloaks"], + }); + console.log( + "[ExecuteOfficerProfileService] Employee profile found:", + !!profileEmployee, + ); + if (profileEmployee) { + console.log( + "[ExecuteOfficerProfileService] Converting employee profile to officer profile", + ); + const _order = await this.salaryRepo.findOne({ + where: { profileEmployeeId: profileEmployee.id }, + order: { order: "DESC" }, + }); + const profileEmpSalary = new ProfileSalary(); + profileEmpSalary.posNumCodeSit = _posNumCodeSit; + profileEmpSalary.posNumCodeSitAbb = _posNumCodeSitAbb; + profileEmpSalary.order = _order == null ? 1 : _order.order + 1; + Object.assign(profileEmpSalary, { + ...item.bodySalarys, + ...meta, + profileEmployeeId: profileEmployee.id, + profileId: undefined, + }); + const history = new ProfileSalaryHistory(); + Object.assign(history, { ...profileEmpSalary, id: undefined }); + profileEmpSalary.dateGovernment = item.bodySalarys?.commandDateAffect ?? meta.createdAt; + (profileEmpSalary.profileId = _null), + await this.salaryRepo.save(profileEmpSalary, { data: req }); + setLogDataDiff(req, { before, after: profileEmpSalary }); + history.profileSalaryId = profileEmpSalary.id; + await this.salaryHistoryRepo.save(history, { data: req }); + + if (profileEmployee.profileInsignias.length > 0) { + _oldInsigniaIds = profileEmployee.profileInsignias?.map((x: any) => x.id) ?? []; + } + await removeProfileInOrganize(profileEmployee.id, "EMPLOYEE"); + if (profileEmployee.keycloak != null) { + // const delUserKeycloak = await deleteUser(profileEmployee.keycloak); + // if (delUserKeycloak) { + // Task #228 + // profileEmployee.keycloak = _null; + profileEmployee.roleKeycloaks = []; + profileEmployee.isActive = false; + // } + } + profileEmployee.isLeave = true; + profileEmployee.leaveReason = "บรรจุข้าราชการ"; + profileEmployee.lastUpdateUserId = ctx.user.sub; + profileEmployee.lastUpdateFullName = ctx.user.name; + profileEmployee.lastUpdatedAt = new Date(); + await this.profileEmployeeRepository.save(profileEmployee); + setLogDataDiff(req, { before, after: profileEmployee }); + } + profile = Object.assign({ ...item.bodyProfile, ...meta }); + profile.dateRetire = _dateRetire; + profile.dateRetireLaw = _dateRetireLaw; + profile.roleKeycloaks = result && roleKeycloak ? [roleKeycloak] : []; + profile.keycloak = + userKeycloakId && typeof userKeycloakId === "string" ? userKeycloakId : ""; + profile.registrationAddress = item.bodyProfile.registrationAddress; + profile.registrationProvinceId = registrationProvinceId + ? registrationProvinceId.id + : _null; + profile.registrationDistrictId = registrationDistrictId + ? registrationDistrictId.id + : _null; + profile.registrationSubDistrictId = registrationSubDistrictId + ? registrationSubDistrictId.id + : _null; + profile.registrationZipCode = item.bodyProfile.registrationZipCode; + profile.currentAddress = item.bodyProfile.currentAddress; + profile.currentProvinceId = currentProvinceId ? currentProvinceId.id : _null; + profile.currentDistrictId = currentDistrictId ? currentDistrictId.id : _null; + profile.currentSubDistrictId = currentSubDistrictId ? currentSubDistrictId.id : _null; + profile.currentZipCode = item.bodyProfile.currentZipCode; + profile.email = item.bodyProfile.email; + profile.dateStart = item.bodyProfile.dateStart; + profile.amount = item.bodyProfile.amount ?? null; + profile.amountSpecial = item.bodyProfile.amountSpecial ?? null; + profile.isProbation = item.bodyProfile.isProbation; + //เพิ่มใหม่จากรับโอน + profile.rank = item?.bodyProfile?.rank || null; + profile.prefix = item?.bodyProfile?.rank || item?.bodyProfile?.prefix || null; + profile.prefixMain = item?.bodyProfile?.prefix ?? null; + profile.firstName = item.bodyProfile.firstName ?? null; + profile.lastName = item.bodyProfile.lastName ?? null; + profile.birthDate = item.bodyProfile.birthDate ?? null; + profile.gender = item.bodyProfile.gender ?? null; + profile.relationship = item.bodyProfile.relationship ?? null; + profile.religion = item.bodyProfile.religion ?? null; + profile.ethnicity = item.bodyProfile.ethnicity; + profile.nationality = item.bodyProfile.nationality ?? null; + profile.bloodGroup = item.bodyProfile.bloodGroup ?? null; + profile.phone = item.bodyProfile.phone ?? null; + + console.log("[ExecuteOfficerProfileService] Saving new profile"); + await this.profileRepository.save(profile); + console.log( + "[ExecuteOfficerProfileService] New profile saved, profileId:", + profile.id, + ); + // update user attribute in keycloak + await updateUserAttributes(profile.keycloak ?? "", { + profileId: [profile.id], + prefix: [profile.prefix || ""], + }); + console.log("[ExecuteOfficerProfileService] Keycloak attributes updated"); + setLogDataDiff(req, { before, after: profile }); + } + //ขรก.ในระบบ หรือ ขรก.ในระบบที่สถานะพ้นจากราชการ + else { + console.log( + "[ExecuteOfficerProfileService] Existing profile found, isLeave:", + profile.isLeave, + "leaveType:", + profile.leaveType, + ); + //สร้างโปรไฟล์ใหม่ ถ้าสถานะพ้นราชการ คำสั่งโอนออกหรือคำสั่งขอลาออก + if ( + profile.isLeave && + ["PLACEMENT_TRANSFER", "RETIRE_RESIGN"].includes(profile.leaveType) + ) { + console.log( + "[ExecuteOfficerProfileService] Profile is leaving with eligible leave type, creating new profile record", + ); + //ดึง profileSalary เดิม + _oldSalaries = await this.salaryRepo.find({ + where: { profileId: profile.id }, + order: { order: "ASC" }, + }); + if (profile.profileInsignias.length > 0) { + _oldInsigniaIds = profile.profileInsignias?.map((x: any) => x.id) ?? []; + } + profile = Object.assign({ ...item.bodyProfile, ...meta }); + profile.dateRetire = _dateRetire; + profile.dateRetireLaw = _dateRetireLaw; + profile.roleKeycloaks = result && roleKeycloak ? [roleKeycloak] : []; + profile.keycloak = + userKeycloakId && typeof userKeycloakId === "string" ? userKeycloakId : ""; + profile.registrationAddress = item.bodyProfile.registrationAddress; + profile.registrationProvinceId = registrationProvinceId + ? registrationProvinceId.id + : _null; + profile.registrationDistrictId = registrationDistrictId + ? registrationDistrictId.id + : _null; + profile.registrationSubDistrictId = registrationSubDistrictId + ? registrationSubDistrictId.id + : _null; + profile.registrationZipCode = item.bodyProfile.registrationZipCode; + profile.currentAddress = item.bodyProfile.currentAddress; + profile.currentProvinceId = currentProvinceId ? currentProvinceId.id : _null; + profile.currentDistrictId = currentDistrictId ? currentDistrictId.id : _null; + profile.currentSubDistrictId = currentSubDistrictId ? currentSubDistrictId.id : _null; + profile.currentZipCode = item.bodyProfile.currentZipCode; + profile.email = item.bodyProfile.email; + profile.dateStart = item.bodyProfile.dateStart; + profile.amount = item.bodyProfile.amount ?? null; + profile.amountSpecial = item.bodyProfile.amountSpecial ?? null; + profile.isProbation = item.bodyProfile.isProbation; + profile.rank = item?.bodyProfile?.rank || null; + profile.prefix = item?.bodyProfile?.rank || item?.bodyProfile?.prefix || null; + profile.prefixMain = item?.bodyProfile?.prefix ?? null; + profile.firstName = item.bodyProfile.firstName ?? null; + profile.lastName = item.bodyProfile.lastName ?? null; + profile.birthDate = item.bodyProfile.birthDate ?? null; + profile.gender = item.bodyProfile.gender ?? null; + profile.relationship = item.bodyProfile.relationship ?? null; + profile.religion = item.bodyProfile.religion ?? null; + profile.ethnicity = item.bodyProfile.ethnicity; + profile.nationality = item.bodyProfile.nationality ?? null; + profile.bloodGroup = item.bodyProfile.bloodGroup ?? null; + profile.phone = item.bodyProfile.phone ?? null; + await this.profileRepository.save(profile); + console.log( + "[ExecuteOfficerProfileService] New profile record saved for leaving officer, profileId:", + profile.id, + ); + setLogDataDiff(req, { before, after: profile }); + } else { + console.log( + "[ExecuteOfficerProfileService] Updating existing active profile", + ); + profile.roleKeycloaks = result && roleKeycloak ? [roleKeycloak] : []; + profile.keycloak = + userKeycloakId && typeof userKeycloakId === "string" ? userKeycloakId : ""; + profile.isProbation = item.bodyProfile.isProbation; + profile.isLeave = item.bodyProfile.isLeave; + profile.isRetirement = false; + profile.isActive = true; + profile.isDelete = false; + profile.dateLeave = _null; + profile.dateRetire = _dateRetire; + profile.dateRetireLaw = _dateRetireLaw; + profile.registrationAddress = item.bodyProfile.registrationAddress; + profile.registrationProvinceId = registrationProvinceId + ? registrationProvinceId.id + : _null; + profile.registrationDistrictId = registrationDistrictId + ? registrationDistrictId.id + : _null; + profile.registrationSubDistrictId = registrationSubDistrictId + ? registrationSubDistrictId.id + : _null; + profile.registrationZipCode = item.bodyProfile.registrationZipCode; + profile.currentAddress = item.bodyProfile.currentAddress; + profile.currentProvinceId = currentProvinceId ? currentProvinceId.id : _null; + profile.currentDistrictId = currentDistrictId ? currentDistrictId.id : _null; + profile.currentSubDistrictId = currentSubDistrictId ? currentSubDistrictId.id : _null; + profile.currentZipCode = item.bodyProfile.currentZipCode; + profile.email = item.bodyProfile.email; + profile.telephoneNumber = item.bodyProfile.telephoneNumber; + profile.phone = item.bodyProfile.phone; + profile.dateStart = item.bodyProfile.dateStart; + profile.amount = item.bodyProfile.amount ?? null; + profile.amountSpecial = item.bodyProfile.amountSpecial ?? null; + profile.leaveCommandId = _null; + profile.leaveCommandNo = _null; + profile.leaveRemark = _null; + profile.leaveDate = _null; + profile.leaveType = _null; + profile.leaveReason = _null; + profile.lastUpdateUserId = ctx.user.sub; + profile.lastUpdateFullName = ctx.user.name; + profile.lastUpdatedAt = new Date(); + //เพิ่มใหม่จากรับโอน + profile.rank = item?.bodyProfile?.rank || null; + profile.prefix = item?.bodyProfile?.rank || item?.bodyProfile?.prefix || null; + profile.prefixMain = item?.bodyProfile?.prefix ?? null; + profile.firstName = + item.bodyProfile.firstName && item.bodyProfile.firstName != "" + ? item.bodyProfile.firstName + : profile.firstName; + profile.lastName = + item.bodyProfile.lastName && item.bodyProfile.lastName != "" + ? item.bodyProfile.lastName + : profile.lastName; + profile.birthDate = item.bodyProfile.birthDate + ? item.bodyProfile.birthDate + : profile.birthDate; + profile.gender = + item.bodyProfile.gender && item.bodyProfile.gender != "" + ? item.bodyProfile.gender + : profile.gender; + profile.relationship = + item.bodyProfile.relationship && item.bodyProfile.relationship != "" + ? item.bodyProfile.relationship + : profile.relationship; + profile.religion = + item.bodyProfile.religion && item.bodyProfile.religion != "" + ? item.bodyProfile.religion + : profile.religion; + profile.ethnicity = + item.bodyProfile.ethnicity && item.bodyProfile.ethnicity != "" + ? item.bodyProfile.ethnicity + : profile.ethnicity; + profile.nationality = + item.bodyProfile.nationality && item.bodyProfile.nationality != "" + ? item.bodyProfile.nationality + : profile.nationality; + profile.bloodGroup = + item.bodyProfile.bloodGroup && item.bodyProfile.bloodGroup != "" + ? item.bodyProfile.bloodGroup + : profile.bloodGroup; + profile.phone = + item.bodyProfile.phone && item.bodyProfile.phone != "" + ? item.bodyProfile.phone + : profile.phone; + await this.profileRepository.save(profile); + console.log( + "[ExecuteOfficerProfileService] Existing active profile updated, profileId:", + profile.id, + ); + setLogDataDiff(req, { before, after: profile }); + } + } + + if (profile && profile.id) { + console.log( + "[ExecuteOfficerProfileService] Processing additional data for profileId:", + profile.id, + ); + //Educations + if (item.bodyEducations && item.bodyEducations.length > 0) { + console.log( + "[ExecuteOfficerProfileService] Processing educations, count:", + item.bodyEducations.length, + ); + await Promise.all( + item.bodyEducations.map(async (education) => { + const profileEdu = new ProfileEducation(); + Object.assign(profileEdu, { ...education, ...meta }); + const eduHistory = new ProfileEducationHistory(); + Object.assign(eduHistory, { ...profileEdu, id: undefined }); + profileEdu.profileId = profile.id; + const educationLevel = await this.profileEducationRepo.findOne({ + select: ["id", "level", "profileId"], + where: { profileId: profile.id, isDeleted: false }, + order: { level: "DESC" }, + }); + profileEdu.level = educationLevel == null ? 1 : educationLevel.level + 1; + await this.profileEducationRepo.save(profileEdu, { data: req }); + setLogDataDiff(req, { before, after: profileEdu }); + eduHistory.profileEducationId = profileEdu.id; + await this.profileEducationHistoryRepo.save(eduHistory, { data: req }); + }), + ); + } + //Certificates + if (item.bodyCertificates && item.bodyCertificates.length > 0) { + console.log( + "[ExecuteOfficerProfileService] Processing certificates, count:", + item.bodyCertificates.length, + ); + await Promise.all( + item.bodyCertificates.map(async (cer) => { + const profileCer = new ProfileCertificate(); + Object.assign(profileCer, { ...cer, ...meta }); + const cerHistory = new ProfileCertificateHistory(); + Object.assign(cerHistory, { ...profileCer, id: undefined }); + profileCer.profileId = profile.id; + await this.certificateRepo.save(profileCer, { data: req }); + setLogDataDiff(req, { before, after: profileCer }); + cerHistory.profileCertificateId = profileCer.id; + await this.certificateHistoryRepo.save(cerHistory, { data: req }); + }), + ); + } + //FamilyCouple + if (item.bodyMarry != null) { + console.log("[ExecuteOfficerProfileService] Processing couple/marry data"); + const profileCouple = new ProfileFamilyCouple(); + const data = { + profileId: profile.id, + couple: item.bodyMarry.marry, + couplePrefix: item.bodyMarry.marryPrefix, + coupleFirstName: item.bodyMarry.marryFirstName, + coupleLastName: item.bodyMarry.marryLastName, + coupleCareer: item.bodyMarry.marryOccupation, + coupleLive: true, + }; + Object.assign(profileCouple, { ...data, ...meta }); + const coupleHistory = new ProfileFamilyCoupleHistory(); + Object.assign(coupleHistory, { ...profileCouple, id: undefined }); + profileCouple.profileId = profile.id; + await this.profileFamilyCoupleRepo.save(profileCouple, { data: req }); + setLogDataDiff(req, { before, after: profileCouple }); + coupleHistory.profileFamilyCoupleId = profileCouple.id; + await this.profileFamilyCoupleHistoryRepo.save(coupleHistory, { data: req }); + } + //FamilyFather + if (item.bodyFather != null) { + console.log("[ExecuteOfficerProfileService] Processing father data"); + const profileFather = new ProfileFamilyFather(); + const data = { + profileId: profile.id, + fatherPrefix: item.bodyFather.fatherPrefix, + fatherFirstName: item.bodyFather.fatherFirstName, + fatherLastName: item.bodyFather.fatherLastName, + fatherCareer: item.bodyFather.fatherOccupation, + fatherLive: true, + }; + Object.assign(profileFather, { ...data, ...meta }); + const fatherHistory = new ProfileFamilyFatherHistory(); + Object.assign(fatherHistory, { ...profileFather, id: undefined }); + profileFather.profileId = profile.id; + await this.profileFamilyFatherRepo.save(profileFather, { data: req }); + setLogDataDiff(req, { before, after: profileFather }); + fatherHistory.profileFamilyFatherId = profileFather.id; + await this.profileFamilyFatherHistoryRepo.save(fatherHistory, { data: req }); + } + //FamilyMother + if (item.bodyMother != null) { + console.log("[ExecuteOfficerProfileService] Processing mother data"); + const profileMother = new ProfileFamilyMother(); + const data = { + profileId: profile.id, + motherPrefix: item.bodyMother.motherPrefix, + motherFirstName: item.bodyMother.motherFirstName, + motherLastName: item.bodyMother.motherLastName, + motherCareer: item.bodyMother.motherOccupation, + motherLive: true, + }; + Object.assign(profileMother, { ...data, ...meta }); + const motherHistory = new ProfileFamilyMotherHistory(); + Object.assign(motherHistory, { ...profileMother, id: undefined }); + profileMother.profileId = profile.id; + await this.profileFamilyMotherRepo.save(profileMother, { data: req }); + setLogDataDiff(req, { before, after: profileMother }); + motherHistory.profileFamilyMotherId = profileMother.id; + await this.profileFamilyMotherHistoryRepo.save(motherHistory, { data: req }); + } + //Salary + //insert profileSalary อันเก่า กรณีพ้นราชการแล้วกลับมาบรรจุ + if (_oldSalaries.length > 0) { + console.log( + "[ExecuteOfficerProfileService] Restoring old salaries, count:", + _oldSalaries.length, + ); + await Promise.all( + _oldSalaries.map(async (oldSal) => { + const profileSal: any = new ProfileSalary(); + Object.assign(profileSal, { ...oldSal, ...meta }); + const salaryHistory = new ProfileSalaryHistory(); + Object.assign(salaryHistory, { ...profileSal, id: undefined }); + profileSal.profileId = profile.id; + await this.salaryRepo.save(profileSal, { data: req }); + setLogDataDiff(req, { before, after: profileSal }); + salaryHistory.profileSalaryId = profileSal.id; + await this.salaryHistoryRepo.save(salaryHistory, { data: req }); + }), + ); + } + //insert item.bodySalarys ต่อจากที่ insert เดิมไปแล้ว + if (item.bodySalarys && item.bodySalarys != null) { + console.log("[ExecuteOfficerProfileService] Processing new salary data"); + const dest_item = await this.salaryRepo.findOne({ + where: { profileId: profile.id }, + order: { order: "DESC" }, + }); + const profileSal: any = new ProfileSalary(); + profileSal.posNumCodeSit = _posNumCodeSit; + profileSal.posNumCodeSitAbb = _posNumCodeSitAbb; + Object.assign(profileSal, { ...item.bodySalarys, ...meta }); + const salaryHistory = new ProfileSalaryHistory(); + Object.assign(salaryHistory, { ...profileSal, id: undefined }); + profileSal.order = dest_item == null ? 1 : dest_item.order + 1; + profileSal.profileId = profile.id; + profileSal.dateGovernment = item.bodySalarys.commandDateAffect ?? meta.createdAt; + profileSal.amount = item.bodySalarys.amount ?? null; + profileSal.amountSpecial = item.bodySalarys.amountSpecial ?? null; + profileSal.positionSalaryAmount = item.bodySalarys.positionSalaryAmount ?? null; + profileSal.mouthSalaryAmount = item.bodySalarys.mouthSalaryAmount ?? null; + await this.salaryRepo.save(profileSal, { data: req }); + setLogDataDiff(req, { before, after: profileSal }); + salaryHistory.profileSalaryId = profileSal.id; + await this.salaryHistoryRepo.save(salaryHistory, { data: req }); + } + //Position + if (item.bodyPosition && item.bodyPosition != null) { + console.log("[ExecuteOfficerProfileService] Processing position assignment"); + // STEP 1: หา posMaster ที่จะใช้งานตาม id ที่ส่งมา (อาจเป็นตำแหน่งเก่าหรือใหม่ก็ได้) + console.log( + "[ExecuteOfficerProfileService] STEP 1: Finding posMaster, posmasterId:", + item.bodyPosition.posmasterId, + ); + let posMaster = await this.posMasterRepository.findOne({ + where: { + id: item.bodyPosition.posmasterId, + }, + relations: { + orgRevision: true, + orgRoot: true, + orgChild1: true, + orgChild2: true, + orgChild3: true, + orgChild4: true, + }, + }); + console.log("[ExecuteOfficerProfileService] posMaster found:", !!posMaster); + + // เช็คว่า posMaster ที่หามาอยู่ในโครงสร้างปัจจุบันหรือไม่ + const isCurrent = + posMaster?.orgRevision?.orgRevisionIsCurrent === true && + posMaster?.orgRevision?.orgRevisionIsDraft === false; + console.log("[ExecuteOfficerProfileService] posMaster isCurrent:", isCurrent); + + // ถ้าไม่อยู่ในโครงสร้างปัจจุบัน ให้หาตัวใหม่จาก ancestorDNA + if (!isCurrent && posMaster?.ancestorDNA) { + console.log( + "[ExecuteOfficerProfileService] Finding current posMaster from ancestorDNA", + ); + posMaster = await this.posMasterRepository.findOne({ + where: { + ancestorDNA: posMaster.ancestorDNA, + orgRevision: { + orgRevisionIsCurrent: true, + orgRevisionIsDraft: false, + }, + }, + relations: { + orgRevision: true, + orgRoot: true, + orgChild1: true, + orgChild2: true, + orgChild3: true, + orgChild4: true, + }, + }); + console.log( + "[ExecuteOfficerProfileService] Current posMaster from ancestorDNA found:", + !!posMaster, + ); + } + + if (posMaster == null) { + console.error( + `[ExecuteOfficerProfileService] not found posMasterId: ${item.bodyPosition.posmasterId}`, + ); + throw new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบข้อมูลตำแหน่งนี้"); + } + + // STEP 2: เคลียร์ข้อมูลตำแหน่งเก่าที่ครองอยู่ ในโครงสร้างปัจจุบัน + console.log("[ExecuteOfficerProfileService] STEP 2: Clearing old position data"); + const posMasterOld = await this.posMasterRepository.findOne({ + where: { + current_holderId: profile.id, + orgRevisionId: posMaster.orgRevisionId, + }, + }); + if (posMasterOld != null) { + // เคลียร์คนครองเก่าออกจากตำแหน่งเดิม + posMasterOld.current_holderId = null; + posMasterOld.lastUpdatedAt = new Date(); + } + + // หา position เก่าที่เลือกไว้ แล้วเคลียร์การเลือก + const positionOld = await this.positionRepository.findOne({ + where: { + posMasterId: posMasterOld?.id, + positionIsSelected: true, + }, + }); + if (positionOld != null) { + positionOld.positionIsSelected = false; + await this.positionRepository.save(positionOld); + } + + // STEP 3: เคลียร์ position ที่เลือกไว้อื่นๆ ใน posMaster ตัวใหม่ + console.log( + "[ExecuteOfficerProfileService] STEP 3: Clearing other selected positions in new posMaster", + ); + const checkPosition = await this.positionRepository.find({ + where: { + posMasterId: posMaster.id, + positionIsSelected: true, + }, + }); + if (checkPosition.length > 0) { + const clearPosition = checkPosition.map((positions) => ({ + ...positions, + positionIsSelected: false, + })); + await this.positionRepository.save(clearPosition); + } + + // STEP 4: กำหนดคนครองใหม่ให้กับ posMaster + console.log( + "[ExecuteOfficerProfileService] STEP 4: Assigning new holder to posMaster", + ); + posMaster.current_holderId = profile.id; + posMaster.lastUpdatedAt = new Date(); + // posMaster.conditionReason = _null; + // posMaster.isCondition = false; + if (posMasterOld != null) { + await this.posMasterRepository.save(posMasterOld); + await CreatePosMasterHistoryOfficer(posMasterOld.id, req); + } + await this.posMasterRepository.save(posMaster); + console.log("[ExecuteOfficerProfileService] posMaster saved with new holder"); + + // STEP 5: กำหนด position ใหม่ + console.log( + "[ExecuteOfficerProfileService] STEP 5: Determining position to assign", + ); + // Match position ตามลำดับ priority: + // Condition 1: match จาก positionId + // Condition 2: match 7 ฟิลด์ (positionName, posTypeId, posLevelId, positionField, positionArea, positionExecutiveField, posExecutiveId) + // Condition 3: match 3 ฟิลด์ (positionName, posTypeId, posLevelId) + // Fallback: เลือก position แรกใน posMaster + + let positionNew: Position | null = null; + + // ═══════════════════════════════════════════════════════════ + // CONDITION 1: เช็คจาก positionId ตรง + // ═══════════════════════════════════════════════════════════ + console.log( + "[ExecuteOfficerProfileService] CONDITION 1: Checking by positionId:", + item.bodyPosition?.positionId, + ); + if (item.bodyPosition?.positionId) { + const positionById = await this.positionRepository.findOne({ + where: { + id: item.bodyPosition.positionId, + posMasterId: posMaster.id, // ต้องอยู่ใน posMaster ที่ถูกต้อง + }, + relations: ["posExecutive"], + }); + + if (positionById) { + positionNew = positionById; + console.log( + "[ExecuteOfficerProfileService] CONDITION 1 matched, positionId:", + positionById.id, + ); + } + } + + // ═══════════════════════════════════════════════════════════ + // CONDITION 2: Match 7 ฟิลด์ (ถ้า Condition 1 ไม่ match) + // ═══════════════════════════════════════════════════════════ + if (!positionNew && item.bodyPosition) { + console.log( + "[ExecuteOfficerProfileService] CONDITION 1 not matched, trying CONDITION 2: Match 7 fields", + ); + // สร้าง where clause แบบ dynamic - ใส่เฉพาะฟิลด์ที่ไม่ใช่ null + const whereCondition: any = { + posMasterId: posMaster.id, + positionName: item.bodyPosition.positionName, + posTypeId: item.bodyPosition.posTypeId, + posLevelId: item.bodyPosition.posLevelId, + }; + + if (item.bodyPosition.positionField) { + whereCondition.positionField = item.bodyPosition.positionField; + } + if (item.bodyPosition.posExecutiveId) { + whereCondition.posExecutiveId = item.bodyPosition.posExecutiveId; + } + if (item.bodyPosition.positionExecutiveField) { + whereCondition.positionExecutiveField = item.bodyPosition.positionExecutiveField; + } + if (item.bodyPosition.positionArea) { + whereCondition.positionArea = item.bodyPosition.positionArea; + } + + const positionBy7Fields = await this.positionRepository.findOne({ + where: whereCondition, + relations: ["posExecutive"], + order: { orderNo: "ASC" }, + }); + + if (positionBy7Fields) { + positionNew = positionBy7Fields; + console.log( + "[ExecuteOfficerProfileService] CONDITION 2 matched with 7 fields, positionId:", + positionBy7Fields.id, + ); + } + } + + // ═══════════════════════════════════════════════════════════ + // CONDITION 3: Match 3 ฟิลด์ (ถ้า Condition 2 ไม่ match) + // ═══════════════════════════════════════════════════════════ + if (!positionNew && item.bodyPosition) { + console.log( + "[ExecuteOfficerProfileService] CONDITION 2 not matched, trying CONDITION 3: Match 3 fields", + ); + const positionBy3Fields = await this.positionRepository.findOne({ + where: { + posMasterId: posMaster.id, + positionName: item.bodyPosition.positionName, + posTypeId: item.bodyPosition.posTypeId, + posLevelId: item.bodyPosition.posLevelId, + }, + relations: ["posExecutive"], + order: { orderNo: "ASC" }, + }); + + if (positionBy3Fields) { + positionNew = positionBy3Fields; + console.log( + "[ExecuteOfficerProfileService] CONDITION 3 matched with 3 fields, positionId:", + positionBy3Fields.id, + ); + } else { + console.log( + "[ExecuteOfficerProfileService] No position matched for profileId:", + profile.id, + ); + } + } + + // // ═══════════════════════════════════════════════════════════ + // // FALLBACK: ถ้าทั้ง 3 ไม่ match ให้เลือก position แรกใน posMaster + // // ═══════════════════════════════════════════════════════════ + // if (!positionNew) { + // const fallbackPositions = await this.positionRepository.find({ + // where: { + // posMasterId: posMaster.id, + // }, + // relations: ["posExecutive"], + // order: { + // orderNo: "ASC", + // }, + // take: 1, + // }); + + // if (fallbackPositions.length > 0) { + // positionNew = fallbackPositions[0]; + // } + // } + + // อัพเดท org และ posMasterNo ตลอดไม่ต้องดัก isSit + profile.posMasterNo = getPosMasterNo(posMaster); + profile.org = getOrgFullName(posMaster); + // ถ้าไม่ใช่ตำแหน่งนั่งทับ (isSit = false) ถึงจะอัพเดทตำแหน่งในทะเบียนประวัติ + if (positionNew != null) { + console.log( + "[ExecuteOfficerProfileService] Final position assignment, isSit:", + posMaster.isSit, + "positionId:", + positionNew.id, + ); + positionNew.positionIsSelected = true; + if (!posMaster.isSit) { + profile.posLevelId = positionNew.posLevelId; + profile.posTypeId = positionNew.posTypeId; + 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(); + } + await this.positionRepository.save(positionNew, { data: req }); + } else if (!posMaster.isSit) { + // fallback: ตำแหน่งในโครงสร้างถูกแก้ไข ใช้ข้อมูลตำแหน่งที่สมัครสอบมา + console.log( + "[ExecuteOfficerProfileService] positionNew is null, using bodyPosition data as fallback", + ); + profile.position = item.bodyPosition.positionName ?? null; + profile.posTypeId = item.bodyPosition.posTypeId ?? null; + profile.posLevelId = item.bodyPosition.posLevelId ?? null; + profile.positionField = item.bodyPosition.positionField ?? null; + profile.positionArea = item.bodyPosition.positionArea ?? null; + profile.positionExecutiveField = item.bodyPosition.positionExecutiveField ?? null; + } + await this.profileRepository.save(profile, { data: req }); + setLogDataDiff(req, { before, after: profile }); + // await CreatePosMasterHistoryOfficer(posMaster.id, req); + await CreatePosMasterHistoryOfficer(posMaster.id, req, null, { + positionId: positionNew?.id, + }); + } + // Insignia + if (_oldInsigniaIds.length > 0) { + console.log( + "[ExecuteOfficerProfileService] Processing old insignias, count:", + _oldInsigniaIds.length, + ); + const _insignias = await this.insigniaRepo.find({ + where: { id: In(_oldInsigniaIds), isDeleted: false }, + order: { createdAt: "ASC" }, + }); + for (const oldInsignia of _insignias) { + const newInsigniaData: CreateProfileInsignia = { + profileId: profile.id, + year: oldInsignia.year, + no: oldInsignia.no, + volume: oldInsignia.volume, + section: oldInsignia.section, + page: oldInsignia.page, + receiveDate: oldInsignia.receiveDate, + insigniaId: oldInsignia.insigniaId, + dateAnnounce: oldInsignia.dateAnnounce, + issue: oldInsignia.issue, + volumeNo: oldInsignia.volumeNo, + refCommandDate: oldInsignia.refCommandDate, + refCommandNo: oldInsignia.refCommandNo, + note: oldInsignia.note, + isUpload: oldInsignia.isUpload, + }; + const insignia = new ProfileInsignia(); + Object.assign(insignia, { ...newInsigniaData, ...meta }); + const history = new ProfileInsigniaHistory(); + Object.assign(history, { ...insignia, id: undefined }); + await this.insigniaRepo.save(insignia, { data: req }); + setLogDataDiff(req, { before, after: insignia }); + history.profileInsigniaId = insignia.id; + await this.insigniaHistoryRepo.save(history, { data: req }); + } + } + // เพิ่มรูปภาพโปรไฟล์ + if (item.bodyProfile.objectRefId) { + console.log( + "[ExecuteOfficerProfileService] Processing profile avatar image, objectRefId:", + item.bodyProfile.objectRefId, + ); + const _profileAvatar = new ProfileAvatar(); + Object.assign(_profileAvatar, { + ...meta, + profileId: profile.id, + profileEmployeeId: undefined, + }); + if (profile.profileAvatars && profile.profileAvatars.length > 0) { + await Promise.all( + profile.profileAvatars.map(async (item: any) => { + item.isActive = false; + await this.avatarRepository.save(item); + }), + ); + } + await this.avatarRepository.save(_profileAvatar); + let avatar = `ทะเบียนประวัติ/โปรไฟล์/${profile.id}`; + let fileName = `profile-${_profileAvatar.id}`; + _profileAvatar.isActive = true; + _profileAvatar.avatar = avatar; + _profileAvatar.avatarName = fileName; + await this.avatarRepository.save(_profileAvatar, { data: req }); + profile.avatar = avatar; + profile.avatarName = fileName; + await this.profileRepository.save(profile, { data: req }); + const checkAvatar = await this.avatarRepository.findOne({ + where: { avatar: avatar, avatarName: fileName }, + }); + if (checkAvatar && checkAvatar.profileId == null) { + checkAvatar.profileId = profile.id; + await this.avatarRepository.save(checkAvatar); + } + //duplicate รูปภาพโปรไฟล์โดยอิงจากรูปภาพเดิม + await new CallAPI() + .PostData(req, `/salary/file/avatar/${item.bodyProfile.objectRefId}`, { + prefix: avatar, + fileName: fileName, + }) + .then(() => {}) + .catch(() => {}); + } + } + }), + ); + console.log("[ExecuteOfficerProfileService] executeCreateOfficerProfile completed successfully"); + } +} diff --git a/src/services/ExecuteSalaryCurrentService.ts b/src/services/ExecuteSalaryCurrentService.ts new file mode 100644 index 00000000..1fd5a54a --- /dev/null +++ b/src/services/ExecuteSalaryCurrentService.ts @@ -0,0 +1,431 @@ +import { Double } from "typeorm"; +import { AppDataSource } from "../database/data-source"; +import HttpError from "../interfaces/http-error"; +import HttpStatusCode from "../interfaces/http-status"; +import { Profile } from "../entities/Profile"; +import { ProfileSalary } from "../entities/ProfileSalary"; +import { ProfileSalaryHistory } from "../entities/ProfileSalaryHistory"; +import { OrgRoot } from "../entities/OrgRoot"; +import { PosMaster } from "../entities/PosMaster"; +import { Position } from "../entities/Position"; +import { Command } from "../entities/Command"; +import { getOrgFullName, getPosMasterNo } from "../utils/org-formatting"; +import { logPositionIsSelectedChange, setLogDataDiff } from "../interfaces/utils"; +import { CreatePosMasterHistoryOfficer } from "./PositionService"; + +/** + * Input: ข้อมูล 1 คนสำหรับ endpoint excexute/salary-current + * (C-PM-03, 04, 05, 06, 07, 39, 47 — เปลี่ยนตำแหน่งปัจจุบันของข้าราชการ + salary ใหม่) + */ +export interface SalaryCurrentItem { + profileId: string; + amount?: Double | null; + amountSpecial?: Double | null; + positionSalaryAmount?: Double | null; + mouthSalaryAmount?: Double | null; + positionExecutive: string | null; + positionExecutiveField?: string | null; + positionArea?: string | null; + positionType: string | null; + positionLevel: string | null; + positionTypeId?: string | null; + positionLevelId?: string | null; + posmasterId: string; + positionId: string; + posExecutiveId?: string | null; + positionField?: string | null; + commandId?: string | null; + orgRoot?: string | null; + orgChild1?: string | null; + orgChild2?: string | null; + orgChild3?: string | null; + orgChild4?: string | null; + commandNo: string | null; + commandYear: number | null; + posNo: string | null; + posNoAbb: string | null; + commandDateAffect?: Date | string | null; + commandDateSign?: Date | string | null; + positionName: string | null; + commandCode?: string | null; + commandName?: string | null; + remark: string | null; +} + +/** + * Context สำหรับ audit/log + */ +export interface SalaryCurrentExecutionContext { + user: { sub: string; name: string }; + req?: any; +} + +/** + * Service สำหรับสร้าง ProfileSalary ของข้าราชการ + อัปเดตตำแหน่งปัจจุบัน (เปลี่ยนตำแหน่ง) + * + * ใช้กับ commandType: C-PM-03, 04, 05, 06, 07, 39, 47 + * + * - endpoint /org/command/excexute/salary-current เรียกผ่าน service นี้ (thin wrapper) + * - consumer ใน rabbitmq handler เรียกผ่าน service นี้โดยตรง (Linear Flow) + * + * Behavior ทั้งหมด preserve จาก CommandController.newSalaryAndUpdateCurrent ต้นฉบับ + */ +export class ExecuteSalaryCurrentService { + private commandRepository = AppDataSource.getRepository(Command); + private profileRepository = AppDataSource.getRepository(Profile); + private salaryRepo = AppDataSource.getRepository(ProfileSalary); + private salaryHistoryRepo = AppDataSource.getRepository(ProfileSalaryHistory); + private posMasterRepository = AppDataSource.getRepository(PosMaster); + private positionRepository = AppDataSource.getRepository(Position); + private orgRootRepository = AppDataSource.getRepository(OrgRoot); + + /** + * ประมวลผลสร้าง ProfileSalary + อัปเดตตำแหน่งปัจจุบันของข้าราชการ + */ + async executeSalaryCurrent( + data: SalaryCurrentItem[], + ctx: SalaryCurrentExecutionContext, + ): Promise { + console.log("[ExecuteSalaryCurrentService] Starting executeSalaryCurrent"); + console.log("[ExecuteSalaryCurrentService] Request body count:", data?.length); + + const req = ctx.req; + + // ───────────────────────────────────────────────────────────── + // Normalize date fields (ผ่าน handler จะได้ string → ต้องแปลงเป็น Date) + // ───────────────────────────────────────────────────────────── + const toDate = (v: any): Date | null => { + if (v == null || v === "") return null; + if (v instanceof Date) return isNaN(v.getTime()) ? null : v; + const d = new Date(v); + return isNaN(d.getTime()) ? null : d; + }; + for (const item of data ?? []) { + const it = item as any; + it.commandDateAffect = toDate(it.commandDateAffect); + it.commandDateSign = toDate(it.commandDateSign); + } + + let _posNumCodeSit: string = ""; + let _posNumCodeSitAbb: string = ""; + const _command = await this.commandRepository.findOne({ + where: { id: data.find((x) => x.commandId)?.commandId ?? "" }, + }); + if (_command) { + if (_command?.isBangkok?.toLocaleUpperCase() == "OFFICE") { + const orgRootDeputy = await this.orgRootRepository.findOne({ + where: { + isDeputy: true, + orgRevision: { + orgRevisionIsCurrent: true, + orgRevisionIsDraft: false, + }, + }, + relations: ["orgRevision"], + }); + _posNumCodeSit = orgRootDeputy ? orgRootDeputy?.orgRootName : "สำนักปลัดกรุงเทพมหานคร"; + _posNumCodeSitAbb = orgRootDeputy ? orgRootDeputy?.orgRootShortName : "สนป."; + } else if (_command?.isBangkok?.toLocaleUpperCase() == "BANGKOK") { + _posNumCodeSit = "กรุงเทพมหานคร"; + _posNumCodeSitAbb = "กทม."; + } else { + let _profileAdmin = await this.profileRepository.findOne({ + where: { + keycloak: _command?.createdUserId.toString(), + current_holders: { + orgRevision: { + orgRevisionIsCurrent: true, + orgRevisionIsDraft: false, + }, + }, + }, + relations: ["current_holders", "current_holders.orgRevision", "current_holders.orgRoot"], + }); + _posNumCodeSit = + _profileAdmin?.current_holders.find((x) => x.orgRoot.orgRootName)?.orgRoot.orgRootName ?? + ""; + _posNumCodeSitAbb = + _profileAdmin?.current_holders.find((x) => x.orgRoot.orgRootShortName)?.orgRoot + .orgRootShortName ?? ""; + } + } + await Promise.all( + data.map(async (item) => { + const profile: any = await this.profileRepository.findOneBy({ id: item.profileId }); + if (!profile) { + throw new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบข้อมูลทะเบียนประวัตินี้"); + } + let _null: any = null; + const dest_item = await this.salaryRepo.findOne({ + where: { profileId: item.profileId }, + order: { order: "DESC" }, + }); + const before = null; + const dataSalary = new ProfileSalary(); + + const meta = { + order: dest_item == null ? 1 : dest_item.order + 1, + createdUserId: ctx.user.sub, + createdFullName: ctx.user.name, + lastUpdateUserId: ctx.user.sub, + lastUpdateFullName: ctx.user.name, + createdAt: new Date(), + lastUpdatedAt: new Date(), + }; + dataSalary.posNumCodeSit = _posNumCodeSit; + dataSalary.posNumCodeSitAbb = _posNumCodeSitAbb; + Object.assign(dataSalary, { ...item, ...meta }); + const history = new ProfileSalaryHistory(); + Object.assign(history, { ...dataSalary, id: undefined }); + await this.salaryRepo.save(dataSalary, { data: req }); + setLogDataDiff(req, { before, after: dataSalary }); + history.commandId = item.commandId ?? _null; + history.profileSalaryId = dataSalary.id; + await this.salaryHistoryRepo.save(history, { data: req }); + + // STEP 1: หา posMaster ที่จะใช้งานตาม id ที่ส่งมา + let posMaster = await this.posMasterRepository.findOne({ + where: { id: item.posmasterId }, + relations: { + orgRevision: true, + orgRoot: true, + orgChild1: true, + orgChild2: true, + orgChild3: true, + orgChild4: true, + }, + }); + + // เช็คว่า posMaster ที่หามาอยู่ในโครงสร้างปัจจุบันหรือไม่ + const isCurrent = + posMaster?.orgRevision?.orgRevisionIsCurrent === true && + posMaster?.orgRevision?.orgRevisionIsDraft === false; + + // ถ้าไม่อยู่ในโครงสร้างปัจจุบัน ให้หาตัวใหม่จาก ancestorDNA + if (!isCurrent && posMaster?.ancestorDNA) { + posMaster = await this.posMasterRepository.findOne({ + where: { + ancestorDNA: posMaster.ancestorDNA, + orgRevision: { + orgRevisionIsCurrent: true, + orgRevisionIsDraft: false, + }, + }, + relations: { + orgRevision: true, + orgRoot: true, + orgChild1: true, + orgChild2: true, + orgChild3: true, + orgChild4: true, + }, + }); + } + + if (posMaster == null) { + console.error( + `[ExecuteSalaryCurrentService] PosMaster not found - posMasterId: ${item.posmasterId}, `, + ); + throw new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบข้อมูลตำแหน่งนี้"); + } + + const posMasterOld = await this.posMasterRepository.findOne({ + where: { + current_holderId: item.profileId, + orgRevisionId: posMaster.orgRevisionId, + }, + }); + if (posMasterOld != null) { + posMasterOld.current_holderId = null; + posMasterOld.lastUpdatedAt = new Date(); + } + + const positionOld = await this.positionRepository.findOne({ + where: { + posMasterId: posMasterOld?.id, + positionIsSelected: true, + }, + }); + if (positionOld != null) { + logPositionIsSelectedChange(positionOld.id, positionOld.positionIsSelected, false, { + posMasterId: posMasterOld?.id, + userId: ctx.user.sub, + endpoint: "updateMaster", + action: "command_change_reset_old_position", + }); + + positionOld.positionIsSelected = false; + await this.positionRepository.save(positionOld); + } + + const checkPosition = await this.positionRepository.find({ + where: { + posMasterId: posMaster!.id, // ใช้ posMaster ตัวใหม่ (ที่อาจจะเปลี่ยนจาก ancestorDNA) + positionIsSelected: true, + }, + }); + if (checkPosition.length > 0) { + console.log( + `[positionIsSelected-DEBUG] Command change: clearing ${checkPosition.length} positions (posMasterId: ${posMaster!.id}, userId: ${ctx.user.sub}, endpoint: updateMaster)`, + ); + + const clearPosition = checkPosition.map((positions) => { + logPositionIsSelectedChange(positions.id, positions.positionIsSelected, false, { + posMasterId: posMaster!.id, + userId: ctx.user.sub, + endpoint: "updateMaster", + action: "command_change_clear_positions", + }); + + return { + ...positions, + positionIsSelected: false, + }; + }); + await this.positionRepository.save(clearPosition); + } + + posMaster.current_holderId = item.profileId; + posMaster.lastUpdatedAt = new Date(); + // posMaster.conditionReason = _null; + // posMaster.isCondition = false; + if (posMasterOld != null) { + await this.posMasterRepository.save(posMasterOld); + await CreatePosMasterHistoryOfficer(posMasterOld.id, req); + } + await this.posMasterRepository.save(posMaster); + + // STEP 2: กำหนด position ใหม่ + // Match position ตามลำดับ priority: + // Condition 1: match จาก positionId + // Condition 2: match 7 ฟิลด์ (positionName, posTypeId, posLevelId, positionField, positionArea, positionExecutiveField, posExecutiveId) + // Condition 3: match 3 ฟิลด์ (positionName, posTypeId, posLevelId) + // Fallback: เลือก position แรกใน posMaster + + let positionNew: Position | null = null; + + // Resolve ID: ใช้ positionTypeId/positionLevelId ก่อน ถ้าไม่มี fallback เป็น positionType/positionLevel + const posTypeId = item.positionTypeId || item.positionType; + const posLevelId = item.positionLevelId || item.positionLevel; + + // ═══════════════════════════════════════════════════════════ + // CONDITION 1: เช็คจาก positionId ตรง + // ═══════════════════════════════════════════════════════════ + if (item.positionId) { + const positionById = await this.positionRepository.findOne({ + where: { + id: item.positionId, + posMasterId: posMaster.id, // ต้องอยู่ใน posMaster ที่ถูกต้อง + }, + relations: ["posExecutive"], + }); + + if (positionById) { + positionNew = positionById; + } + } + + // ═══════════════════════════════════════════════════════════ + // CONDITION 2: Match 7 ฟิลด์ (ถ้า Condition 1 ไม่ match) + // ═══════════════════════════════════════════════════════════ + if (!positionNew && item.positionName && posTypeId && posLevelId) { + // สร้าง where clause แบบ dynamic - ใส่เฉพาะฟิลด์ที่มีค่า + const whereCondition: any = { + posMasterId: posMaster.id, + positionName: item.positionName, + posTypeId: posTypeId, + posLevelId: posLevelId, + }; + + // เพิ่มเฉพาะฟิลด์ที่มีค่า (ไม่ใช่ null, undefined, หรือ string ว่าง) + if (item.positionField) { + whereCondition.positionField = item.positionField; + } + if (item.posExecutiveId) { + whereCondition.posExecutiveId = item.posExecutiveId; + } + if (item.positionExecutiveField) { + whereCondition.positionExecutiveField = item.positionExecutiveField; + } + if (item.positionArea) { + whereCondition.positionArea = item.positionArea; + } + + const positionBy7Fields = await this.positionRepository.findOne({ + where: whereCondition, + relations: ["posExecutive"], + order: { orderNo: "ASC" }, + }); + + if (positionBy7Fields) { + positionNew = positionBy7Fields; + } + } + + // ═══════════════════════════════════════════════════════════ + // CONDITION 3: Match 3 ฟิลด์ (ถ้า Condition 2 ไม่ match) + // ═══════════════════════════════════════════════════════════ + if (!positionNew && item.positionName && posTypeId && posLevelId) { + const positionBy3Fields = await this.positionRepository.findOne({ + where: { + posMasterId: posMaster.id, + positionName: item.positionName, + posTypeId: posTypeId, + posLevelId: posLevelId, + }, + relations: ["posExecutive"], + order: { orderNo: "ASC" }, + }); + + if (positionBy3Fields) { + positionNew = positionBy3Fields; + } + } + + // // ═══════════════════════════════════════════════════════════ + // // FALLBACK: ถ้าทั้ง 3 ไม่ match ให้เลือก position แรกใน posMaster + // // ═══════════════════════════════════════════════════════════ + // if (!positionNew) { + // const fallbackPositions = await this.positionRepository.find({ + // where: { + // posMasterId: posMaster.id, + // }, + // relations: ["posExecutive"], + // order: { + // orderNo: "ASC", + // }, + // take: 1, + // }); + + // if (fallbackPositions.length > 0) { + // positionNew = fallbackPositions[0]; + // } + // } + + // ถ้าไม่ใช่ตำแหน่งนั่งทับ (isSit = false) ถึงจะอัพเดทตำแหน่งในทะเบียนประวัติ + if (positionNew != null) { + positionNew.positionIsSelected = true; + // อัพเดท org และ posMasterNo ตลอดไม่ต้องดัก isSit + profile.posMasterNo = getPosMasterNo(posMaster); + profile.org = getOrgFullName(posMaster); + if (!posMaster.isSit) { + profile.posLevelId = positionNew.posLevelId; + profile.posTypeId = positionNew.posTypeId; + 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.amountSpecial = item.amountSpecial ?? null; + await this.profileRepository.save(profile); + await this.positionRepository.save(positionNew); + } + await CreatePosMasterHistoryOfficer(posMaster.id, req); + }), + ); + + console.log("[ExecuteSalaryCurrentService] executeSalaryCurrent completed successfully"); + } +} diff --git a/src/services/ExecuteSalaryEmployeeCurrentService.ts b/src/services/ExecuteSalaryEmployeeCurrentService.ts new file mode 100644 index 00000000..919144c1 --- /dev/null +++ b/src/services/ExecuteSalaryEmployeeCurrentService.ts @@ -0,0 +1,262 @@ +import { Double } from "typeorm"; +import { AppDataSource } from "../database/data-source"; +import HttpError from "../interfaces/http-error"; +import HttpStatusCode from "../interfaces/http-status"; +import HttpStatus from "../interfaces/http-status"; +import { Profile } from "../entities/Profile"; +import { ProfileEmployee } from "../entities/ProfileEmployee"; +import { ProfileSalary } from "../entities/ProfileSalary"; +import { ProfileSalaryHistory } from "../entities/ProfileSalaryHistory"; +import { OrgRoot } from "../entities/OrgRoot"; +import { EmployeePosMaster } from "../entities/EmployeePosMaster"; +import { EmployeePosition } from "../entities/EmployeePosition"; +import { Command } from "../entities/Command"; +import { setLogDataDiff } from "../interfaces/utils"; +import { CreatePosMasterHistoryEmployee } from "./PositionService"; + +/** + * Input: ข้อมูล 1 คนสำหรับ endpoint excexute/salary-employee-current + * (C-PM-22, 24 — เปลี่ยนตำแหน่งปัจจุบันของลูกจ้าง + salary ใหม่) + */ +export interface SalaryEmployeeCurrentItem { + profileId: string; + amount?: Double | null; + amountSpecial?: Double | null; + positionSalaryAmount?: Double | null; + mouthSalaryAmount?: Double | null; + positionType: string | null; + positionLevel: string | null; + posmasterId: string; + positionId: string; + commandId?: string | null; + orgRoot?: string | null; + orgChild1?: string | null; + orgChild2?: string | null; + orgChild3?: string | null; + orgChild4?: string | null; + commandNo: string | null; + commandYear: number | null; + posNo: string | null; + posNoAbb: string | null; + commandDateAffect?: Date | string | null; + commandDateSign?: Date | string | null; + positionName: string | null; + commandCode?: string | null; + commandName?: string | null; + remark: string | null; +} + +/** + * Context สำหรับ audit/log + */ +export interface SalaryEmployeeCurrentExecutionContext { + user: { sub: string; name: string }; + req?: any; +} + +/** + * Service สำหรับสร้าง ProfileSalary ของลูกจ้าง + อัปเดตตำแหน่งปัจจุบัน (เปลี่ยนตำแหน่ง) + * + * ใช้กับ commandType: C-PM-22, 24 + * + * - endpoint /org/command/excexute/salary-employee-current เรียกผ่าน service นี้ (thin wrapper) + * - consumer ใน rabbitmq handler เรียกผ่าน service นี้โดยตรง (Linear Flow) + * + * Behavior ทั้งหมด preserve จาก CommandController.newSalaryEmployeeAndUpdateCurrent ต้นฉบับ + */ +export class ExecuteSalaryEmployeeCurrentService { + private commandRepository = AppDataSource.getRepository(Command); + private profileRepository = AppDataSource.getRepository(Profile); + private profileEmployeeRepository = AppDataSource.getRepository(ProfileEmployee); + private salaryRepo = AppDataSource.getRepository(ProfileSalary); + private salaryHistoryRepo = AppDataSource.getRepository(ProfileSalaryHistory); + private employeePosMasterRepository = AppDataSource.getRepository(EmployeePosMaster); + private employeePositionRepository = AppDataSource.getRepository(EmployeePosition); + private orgRootRepository = AppDataSource.getRepository(OrgRoot); + + /** + * ประมวลผลสร้าง ProfileSalary + อัปเดตตำแหน่งปัจจุบันของลูกจ้าง + */ + async executeSalaryEmployeeCurrent( + data: SalaryEmployeeCurrentItem[], + ctx: SalaryEmployeeCurrentExecutionContext, + ): Promise { + console.log("[ExecuteSalaryEmployeeCurrentService] Starting executeSalaryEmployeeCurrent"); + console.log("[ExecuteSalaryEmployeeCurrentService] Request body count:", data?.length); + + const req = ctx.req; + + // ───────────────────────────────────────────────────────────── + // Normalize date fields (ผ่าน handler จะได้ string → ต้องแปลงเป็น Date) + // ───────────────────────────────────────────────────────────── + const toDate = (v: any): Date | null => { + if (v == null || v === "") return null; + if (v instanceof Date) return isNaN(v.getTime()) ? null : v; + const d = new Date(v); + return isNaN(d.getTime()) ? null : d; + }; + for (const item of data ?? []) { + const it = item as any; + it.commandDateAffect = toDate(it.commandDateAffect); + it.commandDateSign = toDate(it.commandDateSign); + } + + let _posNumCodeSit: string = ""; + let _posNumCodeSitAbb: string = ""; + const _command = await this.commandRepository.findOne({ + where: { id: data.find((x) => x.commandId)?.commandId ?? "" }, + }); + if (_command) { + if (_command?.isBangkok?.toLocaleUpperCase() == "OFFICE") { + const orgRootDeputy = await this.orgRootRepository.findOne({ + where: { + isDeputy: true, + orgRevision: { + orgRevisionIsCurrent: true, + orgRevisionIsDraft: false, + }, + }, + relations: ["orgRevision"], + }); + _posNumCodeSit = orgRootDeputy ? orgRootDeputy?.orgRootName : "สำนักปลัดกรุงเทพมหานคร"; + _posNumCodeSitAbb = orgRootDeputy ? orgRootDeputy?.orgRootShortName : "สนป."; + } else if (_command?.isBangkok?.toLocaleUpperCase() == "BANGKOK") { + _posNumCodeSit = "กรุงเทพมหานคร"; + _posNumCodeSitAbb = "กทม."; + } else { + let _profileAdmin = await this.profileRepository.findOne({ + where: { + keycloak: _command?.createdUserId.toString(), + current_holders: { + orgRevision: { + orgRevisionIsCurrent: true, + orgRevisionIsDraft: false, + }, + }, + }, + relations: ["current_holders", "current_holders.orgRevision", "current_holders.orgRoot"], + }); + _posNumCodeSit = + _profileAdmin?.current_holders.find((x) => x.orgRoot.orgRootName)?.orgRoot.orgRootName ?? + ""; + _posNumCodeSitAbb = + _profileAdmin?.current_holders.find((x) => x.orgRoot.orgRootShortName)?.orgRoot + .orgRootShortName ?? ""; + } + } + await Promise.all( + data.map(async (item) => { + const profile: any = await this.profileEmployeeRepository.findOneBy({ id: item.profileId }); + if (!profile) { + throw new HttpError(HttpStatus.BAD_REQUEST, "ไม่พบ profile ดังกล่าว"); + } + + const dest_item = await this.salaryRepo.findOne({ + where: { profileEmployeeId: item.profileId }, + order: { order: "DESC" }, + }); + const before = null; + const dataSalary = new ProfileSalary(); + dataSalary.posNumCodeSit = _posNumCodeSit; + dataSalary.posNumCodeSitAbb = _posNumCodeSitAbb; + const meta = { + order: dest_item == null ? 1 : dest_item.order + 1, + createdUserId: ctx.user.sub, + createdFullName: ctx.user.name, + lastUpdateUserId: ctx.user.sub, + lastUpdateFullName: ctx.user.name, + createdAt: new Date(), + lastUpdatedAt: new Date(), + }; + + Object.assign(dataSalary, { + ...item, + ...meta, + profileEmployeeId: item.profileId, + profileId: undefined, + }); + const history = new ProfileSalaryHistory(); + Object.assign(history, { ...dataSalary, id: undefined }); + + await this.salaryRepo.save(dataSalary, { data: req }); + setLogDataDiff(req, { before, after: dataSalary }); + history.profileSalaryId = dataSalary.id; + await this.salaryHistoryRepo.save(history, { data: req }); + + const posMaster = await this.employeePosMasterRepository.findOne({ + where: { id: item.posmasterId }, + relations: ["orgRoot"], + }); + if (posMaster == null) + throw new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบข้อมูลตำแหน่งนี้"); + + const posMasterOld = await this.employeePosMasterRepository.findOne({ + where: { + current_holderId: item.profileId, + orgRevisionId: posMaster.orgRevisionId, + }, + }); + if (posMasterOld != null) { + posMasterOld.current_holderId = null; + posMasterOld.lastUpdatedAt = new Date(); + } + // if (posMasterOld != null) posMasterOld.next_holderId = null; + + const positionOld = await this.employeePositionRepository.findOne({ + where: { + posMasterId: posMasterOld?.id, + positionIsSelected: true, + }, + }); + if (positionOld != null) { + positionOld.positionIsSelected = false; + await this.employeePositionRepository.save(positionOld); + } + + const checkPosition = await this.employeePositionRepository.find({ + where: { + posMasterId: item.posmasterId, + positionIsSelected: true, + }, + }); + if (checkPosition.length > 0) { + const clearPosition = checkPosition.map((positions) => ({ + ...positions, + positionIsSelected: false, + })); + await this.employeePositionRepository.save(clearPosition); + } + + posMaster.current_holderId = item.profileId; + posMaster.lastUpdatedAt = new Date(); + posMaster.next_holderId = null; + if (posMasterOld != null) { + await this.employeePosMasterRepository.save(posMasterOld); + await CreatePosMasterHistoryEmployee(posMasterOld.id, req); + } + await this.employeePosMasterRepository.save(posMaster); + const positionNew = await this.employeePositionRepository.findOne({ + where: { + id: item.positionId, + posMasterId: item.posmasterId, + }, + }); + if (positionNew != null) { + positionNew.positionIsSelected = true; + profile.posLevelId = positionNew.posLevelId; + profile.posTypeId = positionNew.posTypeId; + profile.position = positionNew.positionName; + profile.employeeOc = posMaster?.orgRoot?.orgRootName ?? null; + profile.positionEmployeePositionId = positionNew.positionName; + profile.amount = item.amount ?? null; + profile.amountSpecial = item.amountSpecial ?? null; + await this.profileEmployeeRepository.save(profile); + await this.employeePositionRepository.save(positionNew); + } + await CreatePosMasterHistoryEmployee(posMaster.id, req); + }), + ); + + console.log("[ExecuteSalaryEmployeeCurrentService] executeSalaryEmployeeCurrent completed successfully"); + } +} diff --git a/src/services/ExecuteSalaryEmployeeLeaveService.ts b/src/services/ExecuteSalaryEmployeeLeaveService.ts new file mode 100644 index 00000000..bfb1af4c --- /dev/null +++ b/src/services/ExecuteSalaryEmployeeLeaveService.ts @@ -0,0 +1,321 @@ +import { Double } from "typeorm"; +import { AppDataSource } from "../database/data-source"; +import HttpError from "../interfaces/http-error"; +import HttpStatus from "../interfaces/http-status"; +import { Profile } from "../entities/Profile"; +import { ProfileEmployee } from "../entities/ProfileEmployee"; +import { ProfileSalary } from "../entities/ProfileSalary"; +import { ProfileSalaryHistory } from "../entities/ProfileSalaryHistory"; +import { OrgRoot } from "../entities/OrgRoot"; +import { OrgRevision } from "../entities/OrgRevision"; +import { EmployeePosMaster } from "../entities/EmployeePosMaster"; +import { CommandRecive } from "../entities/CommandRecive"; +import { Command } from "../entities/Command"; +import { checkCommandType, removeProfileInOrganize, setLogDataDiff } from "../interfaces/utils"; +import { reOrderCommandRecivesAndDelete } from "./CommandService"; +import { CreatePosMasterHistoryEmployee } from "./PositionService"; +import { deleteUser } from "../keycloak"; + +/** + * Input: ข้อมูล 1 คนสำหรับ endpoint excexute/salary-employee-leave + * (C-PM-23, 42, 43 — ลาออก/ยกเลิกลาออก/กลับเข้าราชการ ของลูกจ้าง) + */ +export interface SalaryEmployeeLeaveItem { + profileId: string; + amount?: Double | null; + amountSpecial?: Double | null; + positionSalaryAmount?: Double | null; + mouthSalaryAmount?: Double | null; + positionType: string | null; + positionLevel: string | null; + isLeave: boolean; + leaveReason?: string | null; + dateLeave?: Date | string | null; + isGovernment?: boolean | null; + commandId?: string | null; + orgRoot?: string | null; + orgChild1?: string | null; + orgChild2?: string | null; + orgChild3?: string | null; + orgChild4?: string | null; + positionExecutive?: string | null; + positionExecutiveField?: string | null; + positionArea?: string | null; + commandNo: string | null; + commandYear: number | null; + posNo: string | null; + posNoAbb: string | null; + commandDateAffect?: Date | string | null; + commandDateSign?: Date | string | null; + positionName: string | null; + commandCode?: string | null; + commandName?: string | null; + remark: string | null; + resignId: string | null; +} + +/** + * Context สำหรับ audit/log + */ +export interface SalaryEmployeeLeaveExecutionContext { + user: { sub: string; name: string }; + req?: any; +} + +/** + * Service สำหรับสร้าง ProfileSalary ลูกจ้าง + handle leave/กลับเข้าราชการ + * + * ใช้กับ commandType: C-PM-23, 42, 43 + * + * - endpoint /org/command/excexute/salary-employee-leave เรียกผ่าน service นี้ (thin wrapper) + * - consumer ใน rabbitmq handler เรียกผ่าน service นี้โดยตรง (Linear Flow) + * + * Behavior ทั้งหมด preserve จาก CommandController.newSalaryEmployeeAndUpdateLeave ต้นฉบับ + */ +export class ExecuteSalaryEmployeeLeaveService { + private commandRepository = AppDataSource.getRepository(Command); + private commandReciveRepository = AppDataSource.getRepository(CommandRecive); + private profileRepository = AppDataSource.getRepository(Profile); + private profileEmployeeRepository = AppDataSource.getRepository(ProfileEmployee); + private salaryRepo = AppDataSource.getRepository(ProfileSalary); + private salaryHistoryRepo = AppDataSource.getRepository(ProfileSalaryHistory); + private employeePosMasterRepository = AppDataSource.getRepository(EmployeePosMaster); + private orgRootRepository = AppDataSource.getRepository(OrgRoot); + private orgRevisionRepo = AppDataSource.getRepository(OrgRevision); + + /** + * ประมวลผลสร้าง ProfileSalary + handle leave ของลูกจ้าง + */ + async executeSalaryEmployeeLeave( + data: SalaryEmployeeLeaveItem[], + ctx: SalaryEmployeeLeaveExecutionContext, + ): Promise { + console.log("[ExecuteSalaryEmployeeLeaveService] Starting executeSalaryEmployeeLeave"); + console.log("[ExecuteSalaryEmployeeLeaveService] Request body count:", data?.length); + + const req = ctx.req; + + // ───────────────────────────────────────────────────────────── + // Normalize date fields (ผ่าน handler จะได้ string → ต้องแปลงเป็น Date) + // ───────────────────────────────────────────────────────────── + const toDate = (v: any): Date | null => { + if (v == null || v === "") return null; + if (v instanceof Date) return isNaN(v.getTime()) ? null : v; + const d = new Date(v); + return isNaN(d.getTime()) ? null : d; + }; + for (const item of data ?? []) { + const it = item as any; + it.dateLeave = toDate(it.dateLeave); + it.commandDateAffect = toDate(it.commandDateAffect); + it.commandDateSign = toDate(it.commandDateSign); + } + + let _posNumCodeSit: string = ""; + let _posNumCodeSitAbb: string = ""; + const _command = await this.commandRepository.findOne({ + where: { id: data.find((x) => x.commandId)?.commandId ?? "" }, + relations: { commandType: true }, + }); + if (_command) { + if (_command?.isBangkok?.toLocaleUpperCase() == "OFFICE") { + const orgRootDeputy = await this.orgRootRepository.findOne({ + where: { + isDeputy: true, + orgRevision: { + orgRevisionIsCurrent: true, + orgRevisionIsDraft: false, + }, + }, + relations: ["orgRevision"], + }); + _posNumCodeSit = orgRootDeputy ? orgRootDeputy?.orgRootName : "สำนักปลัดกรุงเทพมหานคร"; + _posNumCodeSitAbb = orgRootDeputy ? orgRootDeputy?.orgRootShortName : "สนป."; + } else if (_command?.isBangkok?.toLocaleUpperCase() == "BANGKOK") { + _posNumCodeSit = "กรุงเทพมหานคร"; + _posNumCodeSitAbb = "กทม."; + } else { + let _profileAdmin = await this.profileRepository.findOne({ + where: { + keycloak: _command?.createdUserId.toString(), + current_holders: { + orgRevision: { + orgRevisionIsCurrent: true, + orgRevisionIsDraft: false, + }, + }, + }, + relations: ["current_holders", "current_holders.orgRevision", "current_holders.orgRoot"], + }); + _posNumCodeSit = + _profileAdmin?.current_holders.find((x) => x.orgRoot.orgRootName)?.orgRoot.orgRootName ?? + ""; + _posNumCodeSitAbb = + _profileAdmin?.current_holders.find((x) => x.orgRoot.orgRootShortName)?.orgRoot + .orgRootShortName ?? ""; + } + } + const today = new Date().setHours(0, 0, 0, 0); + await Promise.all( + data.map(async (item) => { + const profile = await this.profileEmployeeRepository.findOne({ + where: { id: item.profileId }, + relations: { + roleKeycloaks: true, + posType: true, + posLevel: true, + }, + }); + if (!profile) { + throw new HttpError(HttpStatus.BAD_REQUEST, "ไม่พบ profile ดังกล่าว"); + } + const code = _command?.commandType?.code; + //ออกคำสั่งยกเลิกลาออก ลบเฉพาะคนที่ขอยกเลิกลาออก + if (item.resignId && code && ["C-PM-42"].includes(code)) { + const commandResign = await this.commandReciveRepository.findOne({ + where: { refId: item.resignId }, + relations: { command: true }, + }); + const executeDate = commandResign + ? new Date(commandResign.command.commandExcecuteDate).setHours(0, 0, 0, 0) + : today; + if ( + commandResign && + _command.status !== "REPORTED" && + (_command.status !== "WAITING" || today < executeDate) + ) { + await reOrderCommandRecivesAndDelete(commandResign!.id); + } + } + let _commandYear = item.commandYear; + if (item.commandYear) { + _commandYear = item.commandYear > 2500 ? item.commandYear : item.commandYear + 543; + } + const dest_item = await this.salaryRepo.findOne({ + where: { profileEmployeeId: item.profileId }, + order: { order: "DESC" }, + }); + const before = null; + const dataSalary = new ProfileSalary(); + dataSalary.posNumCodeSit = _posNumCodeSit; + dataSalary.posNumCodeSitAbb = _posNumCodeSitAbb; + const meta = { + order: dest_item == null ? 1 : dest_item.order + 1, + createdUserId: ctx.user.sub, + createdFullName: ctx.user.name, + lastUpdateUserId: ctx.user.sub, + lastUpdateFullName: ctx.user.name, + createdAt: new Date(), + lastUpdatedAt: new Date(), + }; + + Object.assign(dataSalary, { + ...item, + ...meta, + profileEmployeeId: item.profileId, + profileId: undefined, + }); + const history = new ProfileSalaryHistory(); + Object.assign(history, { ...dataSalary, id: undefined }); + dataSalary.dateGovernment = (item.commandDateAffect as Date) ?? meta.createdAt; + await this.salaryRepo.save(dataSalary, { data: req }); + setLogDataDiff(req, { before, after: dataSalary }); + history.profileSalaryId = dataSalary.id; + await this.salaryHistoryRepo.save(history, { data: req }); + + const _null: any = null; + profile.isLeave = item.isLeave; + profile.leaveReason = item.leaveReason ?? _null; + profile.dateLeave = item.dateLeave ?? _null; + profile.lastUpdateUserId = ctx.user.sub; + profile.lastUpdateFullName = ctx.user.name; + profile.lastUpdatedAt = new Date(); + // บันทึกประวัติก่อนลบตำแหน่ง + const clearProfile = await checkCommandType(String(item.commandId)); + const curRevision = await this.orgRevisionRepo.findOne({ + where: { orgRevisionIsCurrent: true, orgRevisionIsDraft: false }, + }); + let orgRootRef = null; + let orgChild1Ref = null; + let orgChild2Ref = null; + let orgChild3Ref = null; + let orgChild4Ref = null; + if (curRevision) { + const curPosMaster = await this.employeePosMasterRepository.findOne({ + where: { + current_holderId: profile.id, + orgRevisionId: curRevision.id, + }, + relations: { + orgRoot: true, + orgChild1: true, + orgChild2: true, + orgChild3: true, + orgChild4: true, + }, + }); + orgRootRef = curPosMaster?.orgRoot ?? null; + orgChild1Ref = curPosMaster?.orgChild1 ?? null; + orgChild2Ref = curPosMaster?.orgChild2 ?? null; + orgChild3Ref = curPosMaster?.orgChild3 ?? null; + orgChild4Ref = curPosMaster?.orgChild4 ?? null; + if (curPosMaster) { + await CreatePosMasterHistoryEmployee(curPosMaster.id, req, "DELETE"); + } + } + + // ลบตำแหน่ง + if (item.isLeave == true) { + await removeProfileInOrganize(profile.id, "EMPLOYEE"); + } + + if (clearProfile.status) { + if (profile.keycloak != null && profile.keycloak != "" && profile.isDelete === false) { + const delUserKeycloak = await deleteUser(profile.keycloak); + if (delUserKeycloak) { + // Task #228 + // profile.keycloak = _null; + profile.roleKeycloaks = []; + profile.isActive = false; + profile.isDelete = true; + } + } + profile.leaveCommandId = item.commandId ?? _null; + profile.leaveCommandNo = `${item.commandNo}/${_commandYear}`; + profile.leaveRemark = clearProfile.leaveRemark ?? _null; + profile.leaveDate = item.commandDateAffect ?? _null; + profile.leaveType = clearProfile.LeaveType ?? _null; + //ออกจากราชการ ไม่ต้องลบตำแหน่งในทะเบียน (issue #1516) + // profile.position = _null; + // profile.posTypeId = _null; + // profile.posLevelId = _null; + } + await this.profileEmployeeRepository.save(profile); + + // if (profile.id) { + // await this.keycloakAttributeService.clearOrgDnaAttributes( + // [profile.id], + // "PROFILE_EMPLOYEE", + // ); + // } + + // Task #2190 + if (code && ["C-PM-23", "C-PM-43"].includes(code)) { + let organizeName = ""; + if (orgRootRef) { + const names = [ + orgChild4Ref?.orgChild4Name, + orgChild3Ref?.orgChild3Name, + orgChild2Ref?.orgChild2Name, + orgChild1Ref?.orgChild1Name, + orgRootRef?.orgRootName, + ].filter(Boolean); + organizeName = names.join(" "); + } + } + }), + ); + + console.log("[ExecuteSalaryEmployeeLeaveService] executeSalaryEmployeeLeave completed successfully"); + } +} diff --git a/src/services/ExecuteSalaryLeaveService.ts b/src/services/ExecuteSalaryLeaveService.ts new file mode 100644 index 00000000..04d6c32c --- /dev/null +++ b/src/services/ExecuteSalaryLeaveService.ts @@ -0,0 +1,628 @@ +import { Double, In, Like } from "typeorm"; +import { AppDataSource } from "../database/data-source"; +import HttpError from "../interfaces/http-error"; +import HttpStatusCode from "../interfaces/http-status"; +import { Profile } from "../entities/Profile"; +import { ProfileSalary } from "../entities/ProfileSalary"; +import { ProfileSalaryHistory } from "../entities/ProfileSalaryHistory"; +import { OrgRoot } from "../entities/OrgRoot"; +import { OrgRevision } from "../entities/OrgRevision"; +import { PosMaster } from "../entities/PosMaster"; +import { Position } from "../entities/Position"; +import { RoleKeycloak } from "../entities/RoleKeycloak"; +import { CommandRecive } from "../entities/CommandRecive"; +import { Command } from "../entities/Command"; +import { + checkCommandType, + checkReturnCommandType, + removePostMasterAct, + removeProfileInOrganize, + setLogDataDiff, +} from "../interfaces/utils"; +import { reOrderCommandRecivesAndDelete } from "./CommandService"; +import { CreatePosMasterHistoryOfficer } from "./PositionService"; +import { getOrgFullName, getPosMasterNo } from "../utils/org-formatting"; +import { + addUserRoles, + createUser, + deleteUser, + getRoleMappings, + getRoles, + getUserByUsername, + updateUserAttributes, +} from "../keycloak"; + +/** + * Input: ข้อมูล 1 คนสำหรับ endpoint excexute/salary-leave + * (C-PM-08, 09, 17, 18, 41, 48 — ลาออก/พักราชการ/กลับเข้าราชการ ของข้าราชการ) + */ +export interface SalaryLeaveItem { + profileId: string; + amount?: Double | null; + amountSpecial?: Double | null; + positionSalaryAmount?: Double | null; + mouthSalaryAmount?: Double | null; + positionExecutive: string | null; + positionExecutiveField?: string | null; + positionArea?: string | null; + positionType: string | null; + positionLevel: string | null; + isLeave: boolean; + leaveReason?: string | null; + dateLeave?: Date | string | null; + posExecutiveId?: string | null; + positionField?: string | null; + commandId?: string | null; + isGovernment?: boolean | null; + orgRoot?: string | null; + orgChild1?: string | null; + orgChild2?: string | null; + orgChild3?: string | null; + orgChild4?: string | null; + commandNo: string | null; + commandYear: number | null; + posNo: string | null; + posNoAbb: string | null; + commandDateAffect?: Date | string | null; + commandDateSign?: Date | string | null; + positionName: string | null; + commandCode?: string | null; + commandName?: string | null; + remark: string | null; + positionId?: string | null; + positionTypeNew?: string | null; + positionLevelNew?: string | null; + positionNameNew?: string | null; + posmasterId?: string | null; + posTypeNameNew?: string | null; + posLevelNameNew?: string | null; + posNoNew?: string | null; + posNoAbbNew?: string | null; + orgRootNew?: string | null; + orgChild1New?: string | null; + orgChild2New?: string | null; + orgChild3New?: string | null; + orgChild4New?: string | null; + resignId?: string | null; +} + +/** + * Context สำหรับ audit/log + */ +export interface SalaryLeaveExecutionContext { + user: { sub: string; name: string }; + req?: any; +} + +/** + * Service สำหรับสร้าง ProfileSalary ข้าราชการ + handle leave/กลับเข้าราชการ + * + * ใช้กับ commandType: C-PM-08, 09, 17, 18, 41, 48 + * + * - endpoint /org/command/excexute/salary-leave เรียกผ่าน service นี้ (thin wrapper) + * - consumer ใน rabbitmq handler เรียกผ่าน service นี้โดยตรง (Linear Flow) + * + * Behavior ทั้งหมด preserve จาก CommandController.newSalaryAndUpdateLeave ต้นฉบับ + */ +export class ExecuteSalaryLeaveService { + private commandRepository = AppDataSource.getRepository(Command); + private commandReciveRepository = AppDataSource.getRepository(CommandRecive); + private profileRepository = AppDataSource.getRepository(Profile); + private salaryRepo = AppDataSource.getRepository(ProfileSalary); + private salaryHistoryRepo = AppDataSource.getRepository(ProfileSalaryHistory); + private posMasterRepository = AppDataSource.getRepository(PosMaster); + private positionRepository = AppDataSource.getRepository(Position); + private orgRootRepository = AppDataSource.getRepository(OrgRoot); + private orgRevisionRepo = AppDataSource.getRepository(OrgRevision); + private roleKeycloakRepo = AppDataSource.getRepository(RoleKeycloak); + + /** + * ประมวลผลสร้าง ProfileSalary + handle leave/กลับเข้าราชการ ของข้าราชการ + */ + async executeSalaryLeave(data: SalaryLeaveItem[], ctx: SalaryLeaveExecutionContext): Promise { + console.log("[ExecuteSalaryLeaveService] Starting executeSalaryLeave"); + console.log("[ExecuteSalaryLeaveService] Request body count:", data?.length); + + const req = ctx.req; + + // ───────────────────────────────────────────────────────────── + // Normalize date fields (ผ่าน handler จะได้ string → ต้องแปลงเป็น Date) + // ───────────────────────────────────────────────────────────── + const toDate = (v: any): Date | null => { + if (v == null || v === "") return null; + if (v instanceof Date) return isNaN(v.getTime()) ? null : v; + const d = new Date(v); + return isNaN(d.getTime()) ? null : d; + }; + for (const item of data ?? []) { + const it = item as any; + it.dateLeave = toDate(it.dateLeave); + it.commandDateAffect = toDate(it.commandDateAffect); + it.commandDateSign = toDate(it.commandDateSign); + } + + const roleKeycloak = await this.roleKeycloakRepo.findOne({ + where: { name: Like("USER") }, + }); + let _posNumCodeSit: string = ""; + let _posNumCodeSitAbb: string = ""; + const _command = await this.commandRepository.findOne({ + relations: ["commandType"], + where: { id: data.find((x) => x.commandId)?.commandId ?? "" }, + }); + if (_command) { + if (_command?.isBangkok?.toLocaleUpperCase() == "OFFICE") { + const orgRootDeputy = await this.orgRootRepository.findOne({ + where: { + isDeputy: true, + orgRevision: { + orgRevisionIsCurrent: true, + orgRevisionIsDraft: false, + }, + }, + relations: ["orgRevision"], + }); + _posNumCodeSit = orgRootDeputy ? orgRootDeputy?.orgRootName : "สำนักปลัดกรุงเทพมหานคร"; + _posNumCodeSitAbb = orgRootDeputy ? orgRootDeputy?.orgRootShortName : "สนป."; + } else if (_command?.isBangkok?.toLocaleUpperCase() == "BANGKOK") { + _posNumCodeSit = "กรุงเทพมหานคร"; + _posNumCodeSitAbb = "กทม."; + } else { + let _profileAdmin = await this.profileRepository.findOne({ + where: { + keycloak: _command?.createdUserId.toString(), + current_holders: { + orgRevision: { + orgRevisionIsCurrent: true, + orgRevisionIsDraft: false, + }, + }, + }, + relations: ["current_holders", "current_holders.orgRevision", "current_holders.orgRoot"], + }); + _posNumCodeSit = + _profileAdmin?.current_holders.find((x) => x.orgRoot.orgRootName)?.orgRoot.orgRootName ?? + ""; + _posNumCodeSitAbb = + _profileAdmin?.current_holders.find((x) => x.orgRoot.orgRootShortName)?.orgRoot + .orgRootShortName ?? ""; + } + } + const today = new Date().setHours(0, 0, 0, 0); + await Promise.all( + data.map(async (item) => { + const profile = await this.profileRepository.findOne({ + where: { id: item.profileId }, + relations: { + roleKeycloaks: true, + }, + }); + if (!profile) { + throw new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบข้อมูลทะเบียนประวัตินี้"); + } + //ลบตำแหน่งที่รักษาการแทน + const code = _command?.commandType?.code; + if (code && ["C-PM-08", "C-PM-17", "C-PM-18", "C-PM-48"].includes(code)) { + removePostMasterAct(profile.id); + } + //ออกคำสั่งยกเลิกลาออก ลบเฉพาะคนที่ขอยกเลิกลาออก + else if (item.resignId && code && ["C-PM-41"].includes(code)) { + const commandResign = await this.commandReciveRepository.findOne({ + where: { refId: item.resignId }, + relations: { command: true }, + }); + const executeDate = commandResign + ? new Date(commandResign.command.commandExcecuteDate).setHours(0, 0, 0, 0) + : today; + if ( + commandResign && + _command.status !== "REPORTED" && + (_command.status !== "WAITING" || today < executeDate) + ) { + await reOrderCommandRecivesAndDelete(commandResign!.id); + } + } + let _commandYear = item.commandYear; + if (item.commandYear) { + _commandYear = item.commandYear > 2500 ? item.commandYear : item.commandYear + 543; + } + const returnWork = await checkReturnCommandType(String(item.commandId)); + const dest_item = await this.salaryRepo.findOne({ + where: { profileId: item.profileId }, + order: { order: "DESC" }, + }); + const before = null; + const dataSalary = new ProfileSalary(); + dataSalary.posNumCodeSit = _posNumCodeSit; + dataSalary.posNumCodeSitAbb = _posNumCodeSitAbb; + dataSalary.dateGovernment = (item.commandDateAffect as Date) ?? new Date(); + dataSalary.order = dest_item == null ? 1 : dest_item.order + 1; + const meta = { + createdUserId: ctx.user.sub, + createdFullName: ctx.user.name, + lastUpdateUserId: ctx.user.sub, + lastUpdateFullName: ctx.user.name, + createdAt: new Date(), + lastUpdatedAt: new Date(), + }; + if (!returnWork) { + Object.assign(dataSalary, { ...item, ...meta }); + const history = new ProfileSalaryHistory(); + Object.assign(history, { ...dataSalary, id: undefined }); + await this.salaryRepo.save(dataSalary, { data: req }); + setLogDataDiff(req, { before, after: dataSalary }); + history.profileSalaryId = dataSalary.id; + await this.salaryHistoryRepo.save(history, { data: req }); + } + const _null: any = null; + profile.isLeave = item.isLeave; + profile.leaveReason = item.leaveReason ?? _null; + profile.dateLeave = item.dateLeave ?? _null; + profile.lastUpdateUserId = ctx.user.sub; + profile.lastUpdateFullName = ctx.user.name; + profile.lastUpdatedAt = new Date(); + const clearProfile = await checkCommandType(String(item.commandId)); + + //ปั๊มประวัติก่อนลบตำแหน่ง + const curRevision = await this.orgRevisionRepo.findOne({ + where: { orgRevisionIsCurrent: true, orgRevisionIsDraft: false }, + }); + let orgRootRef = null; + let orgChild1Ref = null; + let orgChild2Ref = null; + let orgChild3Ref = null; + let orgChild4Ref = null; + if (curRevision) { + const curPosMaster = await this.posMasterRepository.findOne({ + where: { + current_holderId: profile.id, + orgRevisionId: curRevision.id, + }, + relations: { + orgRoot: true, + orgChild1: true, + orgChild2: true, + orgChild3: true, + orgChild4: true, + }, + }); + orgRootRef = curPosMaster?.orgRoot ?? null; + orgChild1Ref = curPosMaster?.orgChild1 ?? null; + orgChild2Ref = curPosMaster?.orgChild2 ?? null; + orgChild3Ref = curPosMaster?.orgChild3 ?? null; + orgChild4Ref = curPosMaster?.orgChild4 ?? null; + if (curPosMaster && clearProfile.LeaveType != "RETIRE_OUT_EMP") { + await CreatePosMasterHistoryOfficer(curPosMaster.id, req, "DELETE"); + } + } + + //ลบตำแหน่ง + if (item.isLeave == true) { + await removeProfileInOrganize(profile.id, "OFFICER"); + } + if (clearProfile.status) { + if (profile.keycloak != null && profile.keycloak != "" && profile.isDelete === false) { + const delUserKeycloak = await deleteUser(profile.keycloak); + if (delUserKeycloak) { + // Task #228 + // profile.keycloak = _null; + profile.roleKeycloaks = []; + profile.isActive = false; + profile.isDelete = true; + } + } + profile.leaveCommandId = item.commandId ?? _null; + profile.leaveCommandNo = `${item.commandNo}/${_commandYear}`; + profile.leaveRemark = clearProfile.leaveRemark ?? _null; + profile.leaveDate = item.commandDateAffect ?? _null; + profile.leaveType = clearProfile.LeaveType ?? _null; + //ออกจากราชการ ไม่ต้องลบตำแหน่งในทะเบียน (issue #1516) + // profile.position = _null; + // profile.posTypeId = _null; + // profile.posLevelId = _null; + } + + if (item.isGovernment == true) { + if (returnWork) { + //ปลดตำแหน่งเดิมที่ไม่ถูกปลดออกจากกิ่งครั้งเมื่อออกคำสั่งพักราชการหรือออกราชการไว้ + await removeProfileInOrganize(profile.id, "OFFICER"); + //ปั๊มตำแหน่งใหม่ + // หา posMaster และเช็ค orgRevisionIsCurrent + let posMaster = await this.posMasterRepository.findOne({ + where: { id: item.posmasterId?.toString() }, + relations: { + orgRevision: true, + orgRoot: true, + orgChild1: true, + orgChild2: true, + orgChild3: true, + orgChild4: true, + }, + }); + + // เช็คว่า posMaster ที่หามาอยู่ในโครงสร้างปัจจุบันหรือไม่ + const isCurrent = + posMaster?.orgRevision?.orgRevisionIsCurrent === true && + posMaster?.orgRevision?.orgRevisionIsDraft === false; + + // ถ้าไม่อยู่ในโครงสร้างปัจจุบัน ให้หาตัวใหม่จาก ancestorDNA + if (!isCurrent && posMaster?.ancestorDNA) { + posMaster = await this.posMasterRepository.findOne({ + where: { + ancestorDNA: posMaster.ancestorDNA, + orgRevision: { + orgRevisionIsCurrent: true, + orgRevisionIsDraft: false, + }, + }, + relations: { + orgRevision: true, + orgRoot: true, + orgChild1: true, + orgChild2: true, + orgChild3: true, + orgChild4: true, + }, + }); + } + + if (posMaster) { + const checkPosition = await this.positionRepository.find({ + where: { + posMasterId: posMaster.id, + positionIsSelected: true, + }, + }); + if (checkPosition.length > 0) { + const clearPosition = checkPosition.map((positions) => ({ + ...positions, + positionIsSelected: false, + })); + await this.positionRepository.save(clearPosition); + } + posMaster.current_holderId = profile.id; + posMaster.lastUpdatedAt = new Date(); + // posMaster.conditionReason = _null; + // posMaster.isCondition = false; + await this.posMasterRepository.save(posMaster); + + // Match position ตามลำดับ priority: + // Condition 1: match จาก positionId + // Condition 2: match 7 ฟิลด์ (positionName, posTypeId, posLevelId, positionField, positionArea, positionExecutiveField, posExecutiveId) + // Condition 3: match 3 ฟิลด์ (positionName, posTypeId, posLevelId) + // Fallback: เลือก position แรกใน posMaster + + let positionNew: Position | null = null; + + // ═══════════════════════════════════════════════════════════ + // CONDITION 1: เช็คจาก positionId ตรง + // ═══════════════════════════════════════════════════════════ + if (item.positionId) { + const positionById = await this.positionRepository.findOne({ + where: { + id: item.positionId, + posMasterId: posMaster.id, // ต้องอยู่ใน posMaster ที่ถูกต้อง + }, + relations: ["posExecutive"], + }); + + if (positionById) { + positionNew = positionById; + } + } + + // ═══════════════════════════════════════════════════════════ + // CONDITION 2: Match 7 ฟิลด์ (ถ้า Condition 1 ไม่ match) + // ═══════════════════════════════════════════════════════════ + if (!positionNew && item.positionNameNew && item.positionTypeNew && item.positionLevelNew) { + // สร้าง where clause แบบ dynamic - ใส่เฉพาะฟิลด์ที่มีค่า + const whereCondition: any = { + posMasterId: posMaster.id, + positionName: item.positionNameNew, + posTypeId: item.positionTypeNew, + posLevelId: item.positionLevelNew, + }; + + if (item.positionField) { + whereCondition.positionField = item.positionField; + } + if (item.posExecutiveId) { + whereCondition.posExecutiveId = item.posExecutiveId; + } + if (item.positionExecutiveField) { + whereCondition.positionExecutiveField = item.positionExecutiveField; + } + if (item.positionArea) { + whereCondition.positionArea = item.positionArea; + } + + const positionBy7Fields = await this.positionRepository.findOne({ + where: whereCondition, + relations: ["posExecutive"], + order: { orderNo: "ASC" }, + }); + + if (positionBy7Fields) { + positionNew = positionBy7Fields; + } + } + + // ═══════════════════════════════════════════════════════════ + // CONDITION 3: Match 3 ฟิลด์ (ถ้า Condition 2 ไม่ match) + // ═══════════════════════════════════════════════════════════ + if (!positionNew && item.positionNameNew && item.positionTypeNew && item.positionLevelNew) { + const positionBy3Fields = await this.positionRepository.findOne({ + where: { + posMasterId: posMaster.id, + positionName: item.positionNameNew, + posTypeId: item.positionTypeNew, + posLevelId: item.positionLevelNew, + }, + relations: ["posExecutive"], + order: { orderNo: "ASC" }, + }); + + if (positionBy3Fields) { + positionNew = positionBy3Fields; + } + } + + // // FALLBACK: เลือก position แรก (ถ้าไม่เจอทั้ง 2 condition) + // if (!positionNew) { + // const fallbackPositions = await this.positionRepository.find({ + // where: { + // posMasterId: posMaster.id, + // }, + // relations: ["posExecutive"], + // order: { + // orderNo: "ASC", + // }, + // take: 1, + // }); + + // if (fallbackPositions.length > 0) { + // positionNew = fallbackPositions[0]; + // } + // } + + if (positionNew) { + positionNew.positionIsSelected = true; + await this.positionRepository.save(positionNew, { data: req }); + } + await CreatePosMasterHistoryOfficer(posMaster.id, req); + profile.posMasterNo = getPosMasterNo(posMaster); + profile.org = getOrgFullName(posMaster); + } + const newMapProfileSalary = { + profileId: profile.id, + commandId: item.commandId, + positionName: item.positionNameNew ?? null, + positionType: item.posTypeNameNew ?? null, + positionLevel: item.posLevelNameNew ?? null, + amount: item.amount ? item.amount : null, + positionSalaryAmount: item.positionSalaryAmount ? item.positionSalaryAmount : null, + amountSpecial: item.amountSpecial ? item.amountSpecial : null, + mouthSalaryAmount: item.mouthSalaryAmount ? item.mouthSalaryAmount : null, + posNo: item.posNoNew, + posNoAbb: item.posNoAbbNew, + orgRoot: item.orgRootNew, + orgChild1: item.orgChild1New, + orgChild2: item.orgChild2New, + orgChild3: item.orgChild3New, + orgChild4: item.orgChild4New, + isGovernment: item.isGovernment, + commandNo: item.commandNo, + commandYear: item.commandYear, + commandDateAffect: item.commandDateAffect, + commandDateSign: item.commandDateSign, + commandCode: item.commandCode, + commandName: item.commandName, + remark: item.remark, + }; + Object.assign(dataSalary, { ...newMapProfileSalary, ...meta }); + const history = new ProfileSalaryHistory(); + Object.assign(history, { ...dataSalary, id: undefined }); + await this.salaryRepo.save(dataSalary); + history.profileSalaryId = dataSalary.id; + await this.salaryHistoryRepo.save(history); + profile.leaveReason = _null; + profile.leaveCommandId = _null; + profile.leaveCommandNo = _null; + profile.leaveRemark = _null; + profile.leaveDate = _null; + profile.leaveType = _null; + profile.position = item.positionNameNew ?? _null; + profile.posTypeId = item.positionTypeNew ?? _null; + profile.posLevelId = item.positionLevelNew ?? _null; + } + let userKeycloakId; + const checkUser = await getUserByUsername(profile.citizenId); + //ถ้ายังไม่มี user keycloak ให้สร้างใหม่ + if (checkUser.length == 0) { + let password = profile.citizenId; + if (profile.birthDate != null) { + const _date = new Date(profile.birthDate.toDateString()) + .getDate() + .toString() + .padStart(2, "0"); + const _month = (new Date(profile.birthDate.toDateString()).getMonth() + 1) + .toString() + .padStart(2, "0"); + const _year = new Date(profile.birthDate.toDateString()).getFullYear() + 543; + password = `${_date}${_month}${_year}`; + } + // กรอง "." ออกจาก firstName ก่อนส่งไป keycloak + const sanitizedFirstName = profile.firstName?.replace(/\./g, "") ?? ""; + userKeycloakId = await createUser(profile.citizenId, password, { + firstName: sanitizedFirstName, + lastName: profile.lastName, + }); + const list = await getRoles(); + let result = false; + if (Array.isArray(list) && userKeycloakId) { + result = await addUserRoles( + userKeycloakId, + list + .filter((v) => v.name === "USER") + .map((x) => ({ + id: x.id, + name: x.name, + })), + ); + } + profile.roleKeycloaks = result && roleKeycloak ? [roleKeycloak] : []; + profile.keycloak = + userKeycloakId && typeof userKeycloakId === "string" ? userKeycloakId : ""; + } + //ถ้ามีอยู่แล้วให้ใช้อันเดิม + else { + const rolesData = await getRoleMappings(checkUser[0].id); + if (rolesData) { + const _roleKeycloak = await this.roleKeycloakRepo.find({ + where: { name: In(rolesData.map((x: any) => x.name)) }, + }); + profile.roleKeycloaks = + _roleKeycloak && _roleKeycloak.length > 0 ? _roleKeycloak : []; + } + profile.keycloak = checkUser[0].id; + } + profile.amount = item.amount ?? _null; + profile.amountSpecial = item.amountSpecial ?? _null; + profile.isActive = true; + profile.isDelete = false; + } + await this.profileRepository.save(profile); + + // if (profile.id) { + // await this.keycloakAttributeService.clearOrgDnaAttributes( + // [profile.id], + // "PROFILE", + // ); + // } + + // update user attribute in keycloak + await updateUserAttributes(profile.keycloak ?? "", { + profileId: [profile.id], + prefix: [profile.prefix || ""], + }); + + // Task #2190 + if (code && ["C-PM-17", "C-PM-18", "C-PM-48"].includes(code)) { + let organizeName = ""; + if (orgRootRef) { + const names = [ + orgChild4Ref?.orgChild4Name, + orgChild3Ref?.orgChild3Name, + orgChild2Ref?.orgChild2Name, + orgChild1Ref?.orgChild1Name, + orgRootRef?.orgRootName, + ].filter(Boolean); + organizeName = names.join(" "); + } + } + }), + ); + + console.log("[ExecuteSalaryLeaveService] executeSalaryLeave completed successfully"); + } +} diff --git a/src/services/ExecuteSalaryService.ts b/src/services/ExecuteSalaryService.ts new file mode 100644 index 00000000..bbdaa3a4 --- /dev/null +++ b/src/services/ExecuteSalaryService.ts @@ -0,0 +1,327 @@ +import { Double } from "typeorm"; +import { AppDataSource } from "../database/data-source"; +import HttpError from "../interfaces/http-error"; +import HttpStatusCode from "../interfaces/http-status"; +import { Profile } from "../entities/Profile"; +import { ProfileSalary } from "../entities/ProfileSalary"; +import { ProfileSalaryHistory } from "../entities/ProfileSalaryHistory"; +import { ProfileAssistance } from "../entities/ProfileAssistance"; +import { ProfileAssistanceHistory } from "../entities/ProfileAssistanceHistory"; +import { OrgRoot } from "../entities/OrgRoot"; +import { PosMaster } from "../entities/PosMaster"; +import { Command } from "../entities/Command"; +import { + checkCommandType, + removePostMasterAct, + removeProfileInOrganize, + setLogDataDiff, +} from "../interfaces/utils"; +import { CreatePosMasterHistoryOfficer } from "./PositionService"; +import { deleteUser } from "../keycloak"; + +/** + * Input: ข้อมูล 1 คนสำหรับ endpoint excexute/salary + * (C-PM-13 โอน, C-PM-15 ช่วยราชการ, C-PM-16 เกษียณ/ปลดเกษียณ) + */ +export interface SalaryItem { + profileId: string; + amount?: Double | null; + amountSpecial?: Double | null; + positionSalaryAmount?: Double | null; + mouthSalaryAmount?: Double | null; + positionExecutive: string | null; + positionExecutiveField?: string | null; + positionArea?: string | null; + positionType: string | null; + positionLevel: string | null; + commandId?: string | null; + leaveReason?: string | null; + dateLeave?: Date | string | null; + isLeave?: boolean; + orgRoot?: string | null; + orgChild1?: string | null; + orgChild2?: string | null; + orgChild3?: string | null; + orgChild4?: string | null; + officerOrg?: string | null; + dateStart?: Date | string | null; + dateEnd?: Date | string | null; + commandNo: string | null; + commandYear: number | null; + posNo: string | null; + posNoAbb: string | null; + commandDateAffect?: Date | string | null; + commandDateSign?: Date | string | null; + positionName: string | null; + commandCode?: string | null; + commandName?: string | null; + remark: string | null; + refId?: string | null; +} + +/** + * Context สำหรับ audit/log (เหมือน ExecuteOfficerProfileService) + */ +export interface SalaryExecutionContext { + user: { sub: string; name: string }; + req?: any; +} + +/** + * Service สำหรับสร้าง ProfileSalary ของข้าราชการ + handle leave/ออกจากราชการ/ช่วยราชการ + * + * ใช้กับ commandType: C-PM-13 (โอน), C-PM-15 (ช่วยราชการ), C-PM-16 (เกษียณ) + * + * - endpoint /org/command/excexute/salary เรียกผ่าน service นี้ (thin wrapper) + * - consumer ใน rabbitmq handler เรียกผ่าน service นี้โดยตรง (Linear Flow) + * + * Behavior ทั้งหมด preserve จาก CommandController.newSalaryAndUpdate ต้นฉบับ + */ +export class ExecuteSalaryService { + private commandRepository = AppDataSource.getRepository(Command); + private profileRepository = AppDataSource.getRepository(Profile); + private salaryRepo = AppDataSource.getRepository(ProfileSalary); + private salaryHistoryRepo = AppDataSource.getRepository(ProfileSalaryHistory); + private posMasterRepository = AppDataSource.getRepository(PosMaster); + private orgRootRepository = AppDataSource.getRepository(OrgRoot); + private assistanceRepository = AppDataSource.getRepository(ProfileAssistance); + private assistanceHistoryRepository = AppDataSource.getRepository(ProfileAssistanceHistory); + + /** + * ประมวลผลสร้าง ProfileSalary + handle leave/assistance + */ + async executeSalary(data: SalaryItem[], ctx: SalaryExecutionContext): Promise { + console.log("[ExecuteSalaryService] Starting executeSalary"); + console.log("[ExecuteSalaryService] Request body count:", data?.length); + + const req = ctx.req; + + // ───────────────────────────────────────────────────────────── + // Normalize date fields (ผ่าน handler จะได้ string → ต้องแปลงเป็น Date) + // ───────────────────────────────────────────────────────────── + const toDate = (v: any): Date | null => { + if (v == null || v === "") return null; + if (v instanceof Date) return isNaN(v.getTime()) ? null : v; + const d = new Date(v); + return isNaN(d.getTime()) ? null : d; + }; + for (const item of data ?? []) { + const it = item as any; + it.dateLeave = toDate(it.dateLeave); + it.dateStart = toDate(it.dateStart); + it.dateEnd = toDate(it.dateEnd); + it.commandDateAffect = toDate(it.commandDateAffect); + it.commandDateSign = toDate(it.commandDateSign); + } + + let _posNumCodeSit: string = ""; + let _posNumCodeSitAbb: string = ""; + const _command = await this.commandRepository.findOne({ + relations: ["commandType"], + where: { id: data.find((x) => x.commandId)?.commandId ?? "" }, + }); + if (_command) { + if (_command?.isBangkok?.toLocaleUpperCase() == "OFFICE") { + const orgRootDeputy = await this.orgRootRepository.findOne({ + where: { + isDeputy: true, + orgRevision: { + orgRevisionIsCurrent: true, + orgRevisionIsDraft: false, + }, + }, + relations: ["orgRevision"], + }); + _posNumCodeSit = orgRootDeputy ? orgRootDeputy?.orgRootName : "สำนักปลัดกรุงเทพมหานคร"; + _posNumCodeSitAbb = orgRootDeputy ? orgRootDeputy?.orgRootShortName : "สนป."; + } else if (_command?.isBangkok?.toLocaleUpperCase() == "BANGKOK") { + _posNumCodeSit = "กรุงเทพมหานคร"; + _posNumCodeSitAbb = "กทม."; + } else { + let _profileAdmin = await this.profileRepository.findOne({ + where: { + keycloak: _command?.createdUserId.toString(), + current_holders: { + orgRevision: { + orgRevisionIsCurrent: true, + orgRevisionIsDraft: false, + }, + }, + }, + relations: ["current_holders", "current_holders.orgRevision", "current_holders.orgRoot"], + }); + _posNumCodeSit = + _profileAdmin?.current_holders.find((x) => x.orgRoot.orgRootName)?.orgRoot.orgRootName ?? + ""; + _posNumCodeSitAbb = + _profileAdmin?.current_holders.find((x) => x.orgRoot.orgRootShortName)?.orgRoot + .orgRootShortName ?? ""; + } + } + await Promise.all( + data.map(async (item) => { + const profile: any = await this.profileRepository.findOne({ + where: { id: item.profileId }, + relations: { + roleKeycloaks: true, + posType: true, + posLevel: true, + }, + }); + if (!profile) { + throw new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบข้อมูลทะเบียนประวัตินี้"); + } + const posMaster: any = await this.posMasterRepository.findOne({ + where: { + current_holderId: item.profileId, + orgRevision: { + orgRevisionIsCurrent: true, + orgRevisionIsDraft: false, + }, + }, + relations: { + orgRevision: true, + orgRoot: true, + orgChild1: true, + orgChild2: true, + orgChild3: true, + orgChild4: true, + }, + }); + + const orgRevisionRef = posMaster ? posMaster.id : null; + const orgRootRef = orgRevisionRef?.orgRoot ?? null; + const orgChild1Ref = orgRevisionRef?.orgChild1 ?? null; + const orgChild2Ref = orgRevisionRef?.orgChild2 ?? null; + const orgChild3Ref = orgRevisionRef?.orgChild3 ?? null; + const orgChild4Ref = orgRevisionRef?.orgChild4 ?? null; + + //ลบตำแหน่งที่รักษาการแทน + const code = _command?.commandType?.code; + if (code && ["C-PM-13"].includes(code)) { + removePostMasterAct(profile.id); + } + + let _commandYear = item.commandYear; + if (item.commandYear) { + _commandYear = item.commandYear > 2500 ? item.commandYear : item.commandYear + 543; + } + const dest_item = await this.salaryRepo.findOne({ + where: { profileId: item.profileId }, + order: { order: "DESC" }, + }); + const before = null; + const dataSalary = new ProfileSalary(); + dataSalary.posNumCodeSit = _posNumCodeSit; + dataSalary.posNumCodeSitAbb = _posNumCodeSitAbb; + const meta = { + order: dest_item == null ? 1 : dest_item.order + 1, + createdUserId: ctx.user.sub, + createdFullName: ctx.user.name, + lastUpdateUserId: ctx.user.sub, + lastUpdateFullName: ctx.user.name, + createdAt: new Date(), + lastUpdatedAt: new Date(), + }; + if (item.isLeave != undefined && item.isLeave == true) { + await CreatePosMasterHistoryOfficer(orgRevisionRef, req, "DELETE"); + await removeProfileInOrganize(profile.id, "OFFICER"); + } + const clearProfile = await checkCommandType(String(item.commandId)); + const _null: any = null; + if (clearProfile.status) { + if (profile.keycloak != null && profile.keycloak != "" && profile.isDelete === false) { + const delUserKeycloak = await deleteUser(profile.keycloak); + if (delUserKeycloak) { + // Task #228 + // profile.keycloak = _null; + profile.roleKeycloaks = []; + profile.isActive = false; + profile.isDelete = true; + } + } + profile.isLeave = item.isLeave; + profile.leaveCommandId = item.commandId ?? _null; + profile.leaveCommandNo = `${item.commandNo}/${_commandYear}`; + profile.leaveRemark = clearProfile.leaveRemark ?? _null; + profile.leaveDate = item.commandDateAffect ?? _null; + profile.leaveType = clearProfile.LeaveType ?? _null; + //ออกจากราชการ ไม่ต้องลบตำแหน่งในทะเบียน (issue #1516) + // profile.position = _null; + // profile.posTypeId = _null; + // profile.posLevelId = _null; + profile.leaveReason = item.leaveReason ?? _null; + profile.dateLeave = item.dateLeave ?? _null; + profile.amount = item.amount ?? _null; + profile.amountSpecial = item.amountSpecial ?? _null; + await this.profileRepository.save(profile, { data: req }); + + // if (profile.id) { + // await this.keycloakAttributeService.clearOrgDnaAttributes( + // [profile.id], + // "PROFILE", + // ); + // } + } + Object.assign(dataSalary, { ...item, ...meta }); + const history = new ProfileSalaryHistory(); + Object.assign(history, { ...dataSalary, id: undefined }); + + await this.salaryRepo.save(dataSalary, { data: req }); + setLogDataDiff(req, { before, after: dataSalary }); + history.profileSalaryId = dataSalary.id; + await this.salaryHistoryRepo.save(history, { data: req }); + + if (_command) { + if (["C-PM-15", "C-PM-16"].includes(_command.commandType.code)) { + // ประวัติคำสั่งให้ช่วยราชการ + const dataAssis = new ProfileAssistance(); + + const metaAssis = { + profileId: item.profileId, + agency: item.officerOrg, + dateStart: item.dateStart, + dateEnd: item.dateEnd, + commandNo: `${item.commandNo}/${_commandYear}`, + commandName: item.commandName, + refId: item.refId, + refCommandDate: new Date(), + commandId: item.commandId, + createdUserId: ctx.user.sub, + createdFullName: ctx.user.name, + lastUpdateUserId: ctx.user.sub, + lastUpdateFullName: ctx.user.name, + createdAt: new Date(), + lastUpdatedAt: new Date(), + status: _command.commandType.code == "C-PM-15" ? "PENDING" : "DONE", + }; + + Object.assign(dataAssis, metaAssis); + const historyAssis = new ProfileAssistanceHistory(); + Object.assign(historyAssis, { ...dataAssis, id: undefined }); + + await this.assistanceRepository.save(dataAssis); + historyAssis.profileAssistanceId = dataAssis.id; + await this.assistanceHistoryRepository.save(historyAssis); + } + // Task #2190 + else if (_command.commandType.code == "C-PM-13") { + let organizeName = ""; + if (orgRootRef) { + const names = [ + orgChild4Ref?.orgChild4Name, + orgChild3Ref?.orgChild3Name, + orgChild2Ref?.orgChild2Name, + orgChild1Ref?.orgChild1Name, + orgRootRef?.orgRootName, + ].filter(Boolean); + organizeName = names.join(" "); + } + } + } + }), + ); + + console.log("[ExecuteSalaryService] executeSalary completed successfully"); + } +} diff --git a/src/services/KeycloakAttributeService.ts b/src/services/KeycloakAttributeService.ts index 1e0f3f07..7bfe88ed 100644 --- a/src/services/KeycloakAttributeService.ts +++ b/src/services/KeycloakAttributeService.ts @@ -530,18 +530,20 @@ export class KeycloakAttributeService { // Initialize rate limiter if rate limiting is enabled if (rateLimit && rateLimit > 0) { rateLimiter = new RateLimiter(rateLimit); - console.log(`[syncMissingEmpTypeByMonth] Rate limiting enabled: ${rateLimit} requests/second`); + console.log( + `[syncMissingEmpTypeByMonth] Rate limiting enabled: ${rateLimit} requests/second`, + ); } // Select repository based on profile type - const repo = - profileType === "PROFILE" ? this.profileRepo : this.profileEmployeeRepo; + const repo = profileType === "PROFILE" ? this.profileRepo : this.profileEmployeeRepo; // Query profiles updated within the month const profiles = await repo .createQueryBuilder("p") .where("p.keycloak IS NOT NULL") .andWhere("p.keycloak != :empty", { empty: "" }) + .andWhere("p.isDelete = :isDelete", { isDelete: false }) .andWhere("p.lastUpdatedAt BETWEEN :start AND :end", { start: startDate, end: endDate, @@ -579,8 +581,7 @@ export class KeycloakAttributeService { try { // Check if empType is empty in Keycloak - const { isEmpty, currentEmpType } = - await this.checkEmpTypeEmpty(keycloakUserId); + const { isEmpty, currentEmpType } = await this.checkEmpTypeEmpty(keycloakUserId); result.profilesChecked++; @@ -607,8 +608,7 @@ export class KeycloakAttributeService { // Sync the profile const success = await withRetry( - async () => - this.syncOnOrganizationChange(profile.id, profileType), + async () => this.syncOnOrganizationChange(profile.id, profileType), 3, // maxRetries 1000, // baseDelay ); @@ -768,7 +768,13 @@ export class KeycloakAttributeService { maxRetries?: number; // Retry attempts for failed operations rateLimit?: number; // Requests per second clearProgress?: boolean; // Start fresh, ignore existing progress - }): Promise<{ total: number; success: number; failed: number; details: any[]; resumed?: boolean }> { + }): Promise<{ + total: number; + success: number; + failed: number; + details: any[]; + resumed?: boolean; + }> { const limit = options?.limit; const concurrency = options?.concurrency ?? 5; const resume = options?.resume ?? false; @@ -922,7 +928,10 @@ export class KeycloakAttributeService { // Save progress after each batch SyncProgressManager.save(updatedState); // Log progress every 50 items - if (updatedState.lastSyncedIndex % 50 === 0 || updatedState.lastSyncedIndex === updatedState.totalProfiles) { + if ( + updatedState.lastSyncedIndex % 50 === 0 || + updatedState.lastSyncedIndex === updatedState.totalProfiles + ) { SyncProgressManager.logProgress(updatedState); } }, diff --git a/src/services/PositionService.ts b/src/services/PositionService.ts index b1837b99..144a5621 100644 --- a/src/services/PositionService.ts +++ b/src/services/PositionService.ts @@ -11,6 +11,8 @@ import { PosMasterHistory } from "../entities/PosMasterHistory"; import { Position } from "../entities/Position"; import { ProfileEducation } from "../entities/ProfileEducation"; import { RequestWithUser } from "../middlewares/user"; +import { Profile } from "../entities/Profile"; +import { ProfileEmployee } from "../entities/ProfileEmployee"; /** * function สำหรับดึงตำแหน่งที่รักษาการแทน @@ -18,9 +20,7 @@ import { RequestWithUser } from "../middlewares/user"; * - ถ้า posType = "อำนวยการ" หรือ "บริหาร" ใช้ posExecutiveName * - ถ้า posType อื่นๆ ใช้ positionName + posLevel */ -export async function getPosMasterPositions( - posMasterIds: string[] -): Promise> { +export async function getPosMasterPositions(posMasterIds: string[]): Promise> { if (posMasterIds.length === 0) { return new Map(); } @@ -61,7 +61,9 @@ export async function getPosMasterPositions( let positionText = ""; if (posTypeName === "อำนวยการ" || posTypeName === "บริหาร") { - positionText = pos.posExecutive?.posExecutiveName || `${pos.positionName || ""}ระดับ${pos.posLevel?.posLevelName || ""}`.trim(); + positionText = + pos.posExecutive?.posExecutiveName || + `${pos.positionName || ""}ระดับ${pos.posLevel?.posLevelName || ""}`.trim(); } else { positionText = `${pos.positionName || ""}${pos.posLevel?.posLevelName || ""}`.trim(); } @@ -72,7 +74,6 @@ export async function getPosMasterPositions( return positionMap; } - export async function CreatePosMasterHistoryOfficer( posMasterId: string, request: RequestWithUser | null, @@ -85,6 +86,7 @@ export async function CreatePosMasterHistoryOfficer( const repoHistory = transactionManager.getRepository(PosMasterHistory); const repoOrgRevision = transactionManager.getRepository(OrgRevision); const repoPosition = transactionManager.getRepository(Position); + const repoProfile = transactionManager.getRepository(Profile); const pm = await repoPosmaster.findOne({ where: { id: posMasterId }, @@ -132,6 +134,21 @@ export async function CreatePosMasterHistoryOfficer( : null; } + let position = selectedPosition?.positionName ?? _null; + let posTypeName = selectedPosition?.posType?.posTypeName ?? _null; + let posLevelName = selectedPosition?.posLevel?.posLevelName ?? _null; + let posExecutiveName = selectedPosition?.posExecutive?.posExecutiveName ?? _null; + if (pm.isSit && pm.current_holderId) { + const profile = await repoProfile.findOne({ + where: { id: pm.current_holderId }, + relations: ["posType", "posLevel"], + }); + position = profile?.position ?? _null; + posTypeName = profile?.posType?.posTypeName ?? _null; + posLevelName = profile?.posLevel?.posLevelName ?? _null; + posExecutiveName = profile?.posExecutive ?? _null; + } + h.ancestorDNA = pm.ancestorDNA ? pm.ancestorDNA : _null; if (!type || type != "DELETE") { if (checkCurrentRevision) { @@ -144,9 +161,9 @@ export async function CreatePosMasterHistoryOfficer( h.firstName = pm.next_holder?.firstName || _null; h.lastName = pm.next_holder?.lastName || _null; } - h.position = selectedPosition?.positionName ?? _null; - h.posType = selectedPosition?.posType?.posTypeName ?? _null; - h.posLevel = selectedPosition?.posLevel?.posLevelName ?? _null; + h.position = position; + h.posType = posTypeName; + h.posLevel = posLevelName; } h.rootDnaId = pm.orgRoot?.ancestorDNA || _null; h.child1DnaId = pm.orgChild1?.ancestorDNA || _null; @@ -156,7 +173,7 @@ export async function CreatePosMasterHistoryOfficer( h.posMasterNoPrefix = pm.posMasterNoPrefix ?? _null; h.posMasterNo = pm.posMasterNo ?? _null; h.posMasterNoSuffix = pm.posMasterNoSuffix ?? _null; - h.posExecutive = selectedPosition?.posExecutive?.posExecutiveName ?? _null; + h.posExecutive = posExecutiveName; h.shortName = [ pm.orgChild4?.orgChild4ShortName, @@ -188,6 +205,7 @@ export async function CreatePosMasterHistoryOfficer( return true; } catch (err) { if (manager) { + console.error("CreatePosMasterHistoryOfficer error (external transaction):", err); throw err; } console.error("CreatePosMasterHistoryOfficer transaction error:", err); @@ -204,6 +222,7 @@ export async function CreatePosMasterHistoryEmployee( await AppDataSource.transaction(async (manager) => { const repoPosmaster = manager.getRepository(EmployeePosMaster); const repoHistory = manager.getRepository(PosMasterEmployeeHistory); + const repoProfileEmployee = manager.getRepository(ProfileEmployee); const pm = await repoPosmaster.findOne({ where: { id: posMasterId }, @@ -228,15 +247,38 @@ export async function CreatePosMasterHistoryEmployee( pm.positions.length > 0 ? pm.positions.find((p) => p.positionIsSelected === true) ?? null : null; + + let position = selectedPosition?.positionName ?? _null; + let posTypeName = selectedPosition?.posType?.posTypeName ?? _null; + let posLevelName = selectedPosition?.posType && selectedPosition?.posLevel + ? `${selectedPosition?.posType?.posTypeShortName ?? ""} ${selectedPosition?.posLevel?.posLevelName ?? ""}`.trim() + : _null; + if (pm.isSit && pm.current_holderId) { + const profile = await repoProfileEmployee.findOne({ + where: { id: pm.current_holderId }, + relations: ["posType", "posLevel"] + }); + position = profile?.position ?? _null; + posTypeName = profile?.posType?.posTypeName ?? _null; + posLevelName = profile?.posType && profile?.posLevel + ? `${profile?.posType?.posTypeShortName ?? ""} ${profile?.posLevel?.posLevelName ?? ""}`.trim() + : _null; + } h.ancestorDNA = pm.ancestorDNA; if (!type || type != "DELETE") { + h.profileEmployeeId = pm.current_holder?.id || _null; h.prefix = pm.current_holder?.prefix || _null; h.firstName = pm.current_holder?.firstName || _null; h.lastName = pm.current_holder?.lastName || _null; - h.position = selectedPosition?.positionName ?? _null; - h.posType = selectedPosition?.posType?.posTypeName ?? _null; - h.posLevel = selectedPosition?.posLevel?.posLevelName ?? _null; + h.position = position; + h.posType = posTypeName; + h.posLevel = posLevelName; } + h.rootDnaId = pm.orgRoot?.ancestorDNA || _null; + h.child1DnaId = pm.orgChild1?.ancestorDNA || _null; + h.child2DnaId = pm.orgChild2?.ancestorDNA || _null; + h.child3DnaId = pm.orgChild3?.ancestorDNA || _null; + h.child4DnaId = pm.orgChild4?.ancestorDNA || _null; h.posMasterNoPrefix = pm.posMasterNoPrefix ?? _null; h.posMasterNo = pm.posMasterNo ?? _null; h.posMasterNoSuffix = pm.posMasterNoSuffix ?? _null; @@ -452,8 +494,15 @@ export async function BatchSavePosMasterHistoryOfficer( const existing = historyByDna.get(op.posMasterDnaId)?.[0]; const shouldInsert = !existing && op.profileId && op.pm; const profileChanged = existing && existing.profileId !== op.profileId; + const positionChanged = + existing && + (existing.position !== op.pm?.position || + existing.posType !== op.pm?.posType || + existing.posLevel !== op.pm?.posLevel || + existing.posExecutive !== op.pm?.posExecutive); - if (shouldInsert || profileChanged) { + // ถ้าไม่มี record เดิม หรือ profile เปลี่ยน หรือ position เปลี่ยน ให้สร้าง record ใหม่ + if (shouldInsert || profileChanged || positionChanged) { const newPmh = new PosMasterHistory(); newPmh.ancestorDNA = op.posMasterDnaId; newPmh.prefix = op.pm?.prefix ?? _null; @@ -494,3 +543,61 @@ export async function BatchSavePosMasterHistoryOfficer( return false; } } + +/** + * อัพเดทประวัติคนครองตำแหน่งเมื่อมีการเปลี่ยนแปลงข้อมูล profile + * เช่น เปลี่ยนชื่อ - นามสกุล + * ใช้สำหรับบันทึกประวัติเมื่อ profile ที่ครองตำแหน่งมีการเปลี่ยนแปลง + * + * @param profileId ID ของ profile ที่ต้องการตรวจสอบ + * @param request RequestWithUser สำหรับบันทึกข้อมูลผู้ดำเนินการ + * @param type "OFFICER" สำหรับข้าราชการ | "EMPLOYEE" สำหรับลูกจ้างประจำ (default: "OFFICER") + */ +export async function updateHolderProfileHistory( + profileId: string, + request: RequestWithUser, + type: "OFFICER" | "EMPLOYEE" = "OFFICER", +): Promise { + try { + if (type === "OFFICER") { + const posMasterRepo = AppDataSource.getRepository(PosMaster); + const posMaster = await posMasterRepo.findOne({ + where: { + current_holderId: profileId, + orgRevision: { + orgRevisionIsCurrent: true, + orgRevisionIsDraft: false, + }, + }, + relations: { + orgRevision: true, + }, + }); + + if (posMaster) { + await CreatePosMasterHistoryOfficer(posMaster.id, request); + } + } else if (type === "EMPLOYEE") { + const empPosMasterRepo = AppDataSource.getRepository(EmployeePosMaster); + const employeePosMaster = await empPosMasterRepo.findOne({ + where: { + current_holderId: profileId, + orgRevision: { + orgRevisionIsCurrent: true, + orgRevisionIsDraft: false, + }, + }, + relations: { + orgRevision: true, + }, + }); + + if (employeePosMaster) { + await CreatePosMasterHistoryEmployee(employeePosMaster.id, request); + } + } + } catch (error) { + console.error("updateHolderProfileHistory error:", error); + throw error; + } +} diff --git a/src/services/ProfileLeaveService.ts b/src/services/ProfileLeaveService.ts index 327a1fe2..16284bb7 100644 --- a/src/services/ProfileLeaveService.ts +++ b/src/services/ProfileLeaveService.ts @@ -1,12 +1,13 @@ import { AppDataSource } from "../database/data-source"; import { Profile } from "./../entities/Profile"; import { ProfileEmployee } from "../entities/ProfileEmployee"; +import { ProfileSalary } from "./../entities/ProfileSalary"; import { OrgRoot } from "../entities/OrgRoot"; import { OrgChild1 } from "../entities/OrgChild1"; import { OrgChild2 } from "../entities/OrgChild2"; import { OrgChild3 } from "../entities/OrgChild3"; import { OrgChild4 } from "../entities/OrgChild4"; -import { Brackets, Repository } from "typeorm"; +import { Brackets, In, Repository } from "typeorm"; import Extension from "../interfaces/extension"; import { RequestWithUser } from "../middlewares/user"; @@ -62,6 +63,7 @@ interface OrgParentName { export class ProfileLeaveService { private profileEmployeeRepo: Repository; private profileRepo: Repository; + private profileSalaryRepo: Repository; private orgRootRepository: Repository; private child1Repository: Repository; private child2Repository: Repository; @@ -72,6 +74,7 @@ export class ProfileLeaveService { constructor() { this.profileEmployeeRepo = AppDataSource.getRepository(ProfileEmployee); this.profileRepo = AppDataSource.getRepository(Profile); + this.profileSalaryRepo = AppDataSource.getRepository(ProfileSalary); this.orgRootRepository = AppDataSource.getRepository(OrgRoot); this.child1Repository = AppDataSource.getRepository(OrgChild1); this.child2Repository = AppDataSource.getRepository(OrgChild2); @@ -207,19 +210,16 @@ export class ProfileLeaveService { let params: NodeParams = {}; const orgLists = await this.findOrgNodeParentAll(node, nodeId); - console.log("Org Hierarchy for Node Condition:", orgLists); - await Promise.all( - this.nodeConfigs.map(async (config, index) => { - if (index <= node) { - const orgName = orgLists[config.nameField as keyof OrgParentName] || null; - if (orgName) { - nodeCondition += index > 0 ? ` AND ${config.condition}` : config.condition; - nodeCondition += isAll === false && config.isAllTrue ? ` AND ${config.isAllTrue}` : ""; - params[config.paramKey] = orgName; - } - } - }), - ); + + for (let index = 0; index <= node; index++) { + const config = this.nodeConfigs[index]; + const orgName = orgLists[config.nameField as keyof OrgParentName] || null; + if (orgName) { + nodeCondition += index > 0 ? ` AND ${config.condition}` : config.condition; + nodeCondition += isAll === false && config.isAllTrue ? ` AND ${config.isAllTrue}` : ""; + params[config.paramKey] = orgName; + } + } return { condition: nodeCondition, @@ -234,53 +234,31 @@ export class ProfileLeaveService { child3: string | null; child4: string | null; }): Promise { - const orgNames: OrgParentName = { - orgRootName: null, - orgChild1Name: null, - orgChild2Name: null, - orgChild3Name: null, - orgChild4Name: null, + const [rootName, child1, child2, child3, child4] = await Promise.all([ + orgIds.root + ? this.orgRootRepository.findOne({ where: { id: orgIds.root }, select: ["orgRootName"] }) + : Promise.resolve(null), + orgIds.child1 + ? this.child1Repository.findOne({ where: { id: orgIds.child1 }, select: ["orgChild1Name"] }) + : Promise.resolve(null), + orgIds.child2 + ? this.child2Repository.findOne({ where: { id: orgIds.child2 }, select: ["orgChild2Name"] }) + : Promise.resolve(null), + orgIds.child3 + ? this.child3Repository.findOne({ where: { id: orgIds.child3 }, select: ["orgChild3Name"] }) + : Promise.resolve(null), + orgIds.child4 + ? this.child4Repository.findOne({ where: { id: orgIds.child4 }, select: ["orgChild4Name"] }) + : Promise.resolve(null), + ]); + + return { + orgRootName: rootName?.orgRootName ?? null, + orgChild1Name: child1?.orgChild1Name ?? null, + orgChild2Name: child2?.orgChild2Name ?? null, + orgChild3Name: child3?.orgChild3Name ?? null, + orgChild4Name: child4?.orgChild4Name ?? null, }; - if (orgIds.root) { - const rootName = await this.orgRootRepository.findOne({ - where: { id: orgIds.root }, - select: ["orgRootName"], - }); - orgNames.orgRootName = rootName ? rootName.orgRootName : null; - } - if (orgIds.child1) { - const child1 = await this.child1Repository.findOne({ - where: { id: orgIds.child1 }, - select: ["orgChild1Name"], - }); - orgNames.orgChild1Name = child1 ? child1.orgChild1Name : null; - } - - if (orgIds.child2) { - const child2 = await this.child2Repository.findOne({ - where: { id: orgIds.child2 }, - select: ["orgChild2Name"], - }); - orgNames.orgChild2Name = child2 ? child2.orgChild2Name : null; - } - - if (orgIds.child3) { - const child3 = await this.child3Repository.findOne({ - where: { id: orgIds.child3 }, - select: ["orgChild3Name"], - }); - orgNames.orgChild3Name = child3 ? child3.orgChild3Name : null; - } - - if (orgIds.child4) { - const child4 = await this.child4Repository.findOne({ - where: { id: orgIds.child4 }, - select: ["orgChild4Name"], - }); - orgNames.orgChild4Name = child4 ? child4.orgChild4Name : null; - } - - return orgNames; } /** สร้างเงื่อนไขการค้นหาตาม node และ nodeId และเช็คกับ permission */ @@ -317,16 +295,15 @@ export class ProfileLeaveService { return { condition: "1=0", params: {} }; // no access } - await Promise.all( - this.nodeConfigs.map(async (config, index) => { - const orgName = orgLists[config.nameField as keyof OrgParentName] || null; - if (orgName) { - nodeCondition += index > 0 ? ` AND ${config.condition}` : config.condition; - nodeCondition += isAll === false && config.isAllTrue ? ` AND ${config.isAllTrue}` : ""; - params[config.paramKey] = orgName; - } - }), - ); + for (let index = 0; index < this.nodeConfigs.length; index++) { + const config = this.nodeConfigs[index]; + const orgName = orgLists[config.nameField as keyof OrgParentName] || null; + if (orgName) { + nodeCondition += index > 0 ? ` AND ${config.condition}` : config.condition; + nodeCondition += isAll === false && config.isAllTrue ? ` AND ${config.isAllTrue}` : ""; + params[config.paramKey] = orgName; + } + } return { condition: nodeCondition, @@ -478,97 +455,146 @@ export class ProfileLeaveService { _data, } = filter; + const t0 = Date.now(); const searchQuery = this.buildSearchQuery(searchField, "profileEmployee"); - // สร้าง main query - เปลี่ยนจาก leftJoinAndSelect เป็น leftJoin สำหรับ profileSalary - const queryBuilder = this.profileEmployeeRepo - .createQueryBuilder("profileEmployee") - .leftJoinAndSelect("profileEmployee.posLevel", "posLevel") - .leftJoinAndSelect("profileEmployee.posType", "posType") - .leftJoinAndSelect("profileEmployee.profileEmployeeEmployment", "profileEmployeeEmployment") - .leftJoin( - "profileEmployee.profileSalary", - "profileSalary", - "profileSalary.order = (SELECT MAX(ps.order) FROM profileSalary ps WHERE ps.profileEmployeeId = profileEmployee.id and ps.positionName != 'เกษียณอายุราชการ')", - ) - .addSelect([ - "profileSalary.id", - "profileSalary.order", - "profileSalary.posNo", - "profileSalary.posNoAbb", - "profileSalary.orgRoot", - "profileSalary.orgChild1", - "profileSalary.orgChild2", - "profileSalary.orgChild3", - "profileSalary.orgChild4", - ]) - .where( - new Brackets((qb) => { - qb.where("profileEmployee.isLeave = :isLeave", { isLeave: true }).orWhere( + // สร้าง base WHERE conditions แชร์ระหว่าง count/id/data query + const baseWhere = (qb: any) => { + qb.where( + new Brackets((qb2) => { + qb2.where("profileEmployee.isLeave = :isLeave", { isLeave: true }).orWhere( "profileEmployee.isRetirement = :isRetirement", { isRetirement: true }, ); }), ) - .andWhere("profileEmployee.employeeClass LIKE :type", { type: "PERM" }) - .andWhere( - new Brackets((qb) => { - qb.orWhere(searchKeyword && searchKeyword != "" ? searchQuery : "1=1", { - keyword: `%${searchKeyword}%`, - }); - }), - ); + .andWhere("profileEmployee.employeeClass LIKE :type", { type: "PERM" }) + .andWhere( + new Brackets((qb2) => { + qb2.orWhere(searchKeyword && searchKeyword != "" ? searchQuery : "1=1", { + keyword: `%${searchKeyword}%`, + }); + }), + ); - // เพิ่มเงื่อนไขการค้นหา - if (posType) { - queryBuilder.andWhere("posType.posTypeName LIKE :keyword1", { keyword1: `${posType}` }); - } - - if (posLevel) { - queryBuilder.andWhere( - "CONCAT(posType.posTypeShortName, ' ', posLevel.posLevelName) LIKE :keyword2", - { keyword2: `${posLevel}` }, - ); - } - - if (isProbation) { - queryBuilder.andWhere(`profileEmployee.isProbation = ${isProbation}`); - } - - if (retireType) { - queryBuilder.andWhere("profileEmployee.leaveType = :retireType", { retireType }); - } - - if (node !== null && node !== undefined && nodeId) { - const [nodeCondition, permissionCondition] = await Promise.all([ - this.buildNodeCondition(node, nodeId, isAll), - this.buildPermissionCondition(_data, isAll), - ]); - // console.log("Permission Condition:", permissionCondition); - // console.log("Node Condition:", nodeCondition); - - queryBuilder.andWhere(nodeCondition.condition, nodeCondition.params); - - if (_data.privilege !== "OWNER" && _data.privilege !== "PARENT") { - queryBuilder.andWhere(permissionCondition.condition, permissionCondition.params); + if (posType) { + qb.andWhere("posType.posTypeName LIKE :keyword1", { keyword1: `${posType}` }); } + if (posLevel) { + qb.andWhere( + "CONCAT(posType.posTypeShortName, ' ', posLevel.posLevelName) LIKE :keyword2", + { keyword2: `${posLevel}` }, + ); + } + if (isProbation !== undefined && isProbation !== null) { + qb.andWhere("profileEmployee.isProbation = :isProbation", { isProbation }); + } + if (retireType) { + qb.andWhere("profileEmployee.leaveType = :retireType", { retireType }); + } + }; + + // Compute permission/node conditions เพียงครั้งเดียว + const conditions: { condition: string; params: Record }[] = []; + if (_data.privilege !== "OWNER" && _data.privilege !== "PARENT") { + conditions.push(await this.buildPermissionCondition(_data, isAll)); + } + if (node !== null && node !== undefined && nodeId) { + conditions.push(await this.buildNodeCondition(node, nodeId, isAll)); + } + const applyConditions = (qb: any) => { + for (const cond of conditions) { + qb.andWhere(cond.condition, cond.params); + } + }; + + // console.log(`[ProfileLeaveService] getLeaveEmployees conditions took ${Date.now() - t0}ms`); + + // สร้าง salary EXISTS filter (ใช้ซ้ำทั้ง step1, step2) + const applySalaryFilter = (qb: any) => { + if (conditions.length > 0) { + let existsCond = "profileSalary.positionName != :notRetire"; + const existsParams: Record = { notRetire: "เกษียณอายุราชการ" }; + for (const cond of conditions) { + existsCond += ` AND ${cond.condition}`; + Object.assign(existsParams, cond.params); + } + qb.andWhere( + `EXISTS (SELECT 1 FROM profileSalary WHERE profileEmployeeId = profileEmployee.id AND ${existsCond} AND profileSalary.\`order\` = (SELECT MAX(ps.\`order\`) FROM profileSalary ps WHERE ps.profileEmployeeId = profileEmployee.id AND ps.positionName != :notRetire2))`, + { ...existsParams, notRetire2: "เกษียณอายุราชการ" } + ); + } + }; + + // Step 1: Count query + const countQb = this.profileEmployeeRepo + .createQueryBuilder("profileEmployee") + .leftJoinAndSelect("profileEmployee.posLevel", "posLevel") + .leftJoinAndSelect("profileEmployee.posType", "posType"); + baseWhere(countQb); + applySalaryFilter(countQb); + const total = await countQb.getCount(); + + // console.log(`[ProfileLeaveService] getLeaveEmployees count took ${Date.now() - t0}ms, total=${total}`); + + // Step 2: ดึงเฉพาะ profileEmployee IDs ที่ผ่านเงื่อนไข + const idQb = this.profileEmployeeRepo + .createQueryBuilder("profileEmployee") + .select(["profileEmployee.id"]) + .leftJoin("profileEmployee.posLevel", "posLevel") + .leftJoin("profileEmployee.posType", "posType"); + baseWhere(idQb); + applySalaryFilter(idQb); + idQb.orderBy(sortBy, sort).skip((page - 1) * pageSize).take(pageSize); + const rawIds = await idQb.getRawMany(); + const employeeIds = rawIds.map((r) => r.profileEmployee_id); + + // console.log(`[ProfileLeaveService] getLeaveEmployees ids took ${Date.now() - t0}ms, ids=${employeeIds.length}`); + + if (employeeIds.length === 0) { + return { data: [], total }; } - // เพิ่ม sorting และ pagination - queryBuilder - .orderBy(sortBy, sort) - .skip((page - 1) * pageSize) - .take(pageSize); + // Step 3: Load full data โดยไม่ JOIN salary + const records = await this.profileEmployeeRepo.find({ + where: { id: In(employeeIds) }, + relations: ["posLevel", "posType", "profileEmployeeEmployment"], + order: { [sortBy.split(".")[1]]: sort } as any, + }); - const [records, total] = await queryBuilder.getManyAndCount(); + // Step 4: Load salary เฉพาะ row ที่มี order สูงสุดต่อ profileEmployeeId (INNER JOIN + GROUP BY) + const salaries = await this.profileSalaryRepo + .createQueryBuilder("ps") + .innerJoin( + (subQuery) => + subQuery + .select("ps2.profileEmployeeId", "pid") + .addSelect("MAX(ps2.order)", "maxOrd") + .from(ProfileSalary, "ps2") + .where("ps2.profileEmployeeId IN (:...employeeIds)", { employeeIds }) + .andWhere("ps2.positionName != :notRetire", { notRetire: "เกษียณอายุราชการ" }) + .groupBy("ps2.profileEmployeeId"), + "latest", + "latest.pid = ps.profileEmployeeId AND ps.order = latest.maxOrd" + ) + .getMany(); - // print query for debug - // console.log("SQL Query:", queryBuilder.getSql()); + // สร้าง map: profileEmployeeId → salary ที่มี order สูงสุด + const salaryMap = new Map(); + for (const s of salaries) { + salaryMap.set(s.profileEmployeeId, s); + } - const data = await Promise.all( - records.map((record) => Promise.resolve(this.transformEmployeeData(record))), - ); + // แปลงข้อมูลพร้อม salary + const data = records.map((record) => { + const salary = salaryMap.get(record.id); + if (salary) { + (record as any).profileSalary = [salary]; + } + return this.transformEmployeeData(record); + }); + // console.log(`[ProfileLeaveService] getLeaveEmployees total took ${Date.now() - t0}ms, total=${total}`); return { data, total }; } @@ -649,94 +675,143 @@ export class ProfileLeaveService { _data, } = filter; + const t0 = Date.now(); const searchQuery = this.buildSearchQuery(searchField); - // สร้าง main query - เปลี่ยนจาก leftJoinAndSelect เป็น leftJoin สำหรับ profileSalary - const queryBuilder = this.profileRepo - .createQueryBuilder("profile") - .leftJoinAndSelect("profile.posLevel", "posLevel") - .leftJoinAndSelect("profile.posType", "posType") - .leftJoin( - "profile.profileSalary", - "profileSalary", - "profileSalary.order = (SELECT MAX(ps.order) FROM profileSalary ps WHERE ps.profileId = profile.id and ps.positionName != 'เกษียณอายุราชการ')", - ) - .addSelect([ - "profileSalary.id", - "profileSalary.order", - "profileSalary.posNo", - "profileSalary.posNoAbb", - "profileSalary.orgRoot", - "profileSalary.orgChild1", - "profileSalary.orgChild2", - "profileSalary.orgChild3", - "profileSalary.orgChild4", - "profileSalary.positionExecutive", - ]) - .where( - new Brackets((qb) => { - qb.where("profile.isLeave = :isLeave", { isLeave: true }).orWhere( + // สร้าง base WHERE conditions แชร์ระหว่าง count/id/data query + const baseWhere = (qb: any) => { + qb.where( + new Brackets((qb2) => { + qb2.where("profile.isLeave = :isLeave", { isLeave: true }).orWhere( "profile.isRetirement = :isRetirement", { isRetirement: true }, ); }), - ) - .andWhere( - new Brackets((qb) => { - qb.orWhere(searchKeyword && searchKeyword != "" ? searchQuery : "1=1", { + ).andWhere( + new Brackets((qb2) => { + qb2.orWhere(searchKeyword && searchKeyword != "" ? searchQuery : "1=1", { keyword: `%${searchKeyword}%`, }); }), ); - if (posType) { - queryBuilder.andWhere("posType.posTypeName LIKE :keyword1", { keyword1: `${posType}` }); - } - - if (posLevel) { - queryBuilder.andWhere("posLevel.posLevelName LIKE :keyword2", { keyword2: `${posLevel}` }); - } - - if (isProbation) { - queryBuilder.andWhere(`profile.isProbation = ${isProbation}`); - } - - if (retireType) { - queryBuilder.andWhere("profile.leaveType = :retireType", { retireType }); - } - - // เพิ่ม permission และ node conditions - if (node !== null && node !== undefined && nodeId) { - // สร้าง query conditions แบบ parallel - const [nodeCondition, permissionCondition] = await Promise.all([ - this.buildNodeCondition(node, nodeId, isAll), - this.buildPermissionCondition(_data, isAll), - ]); - console.log("Permission Condition:", permissionCondition); - console.log("Node Condition:", nodeCondition); - - queryBuilder.andWhere(nodeCondition.condition, nodeCondition.params); - - if (_data.privilege !== "OWNER" && _data.privilege !== "PARENT") { - queryBuilder.andWhere(permissionCondition.condition, permissionCondition.params); + if (posType) { + qb.andWhere("posType.posTypeName LIKE :keyword1", { keyword1: `${posType}` }); } + if (posLevel) { + qb.andWhere("posLevel.posLevelName LIKE :keyword2", { keyword2: `${posLevel}` }); + } + if (isProbation !== undefined && isProbation !== null) { + qb.andWhere("profile.isProbation = :isProbation", { isProbation }); + } + if (retireType) { + qb.andWhere("profile.leaveType = :retireType", { retireType }); + } + }; + + // Compute permission/node conditions เพียงครั้งเดียว + const conditions: { condition: string; params: Record }[] = []; + if (_data.privilege !== "OWNER" && _data.privilege !== "PARENT") { + conditions.push(await this.buildPermissionCondition(_data, isAll)); + } + if (node !== null && node !== undefined && nodeId) { + conditions.push(await this.buildNodeCondition(node, nodeId, isAll)); + } + const applyConditions = (qb: any) => { + for (const cond of conditions) { + qb.andWhere(cond.condition, cond.params); + } + }; + + // console.log(`[ProfileLeaveService] getLeaveOfficer conditions took ${Date.now() - t0}ms`); + + // สร้าง salary EXISTS filter (ใช้ซ้ำทั้ง step1, step2) + const applySalaryFilter = (qb: any) => { + if (conditions.length > 0) { + let existsCond = "profileSalary.positionName != :notRetire"; + const existsParams: Record = { notRetire: "เกษียณอายุราชการ" }; + for (const cond of conditions) { + existsCond += ` AND ${cond.condition}`; + Object.assign(existsParams, cond.params); + } + qb.andWhere( + `EXISTS (SELECT 1 FROM profileSalary WHERE profileId = profile.id AND ${existsCond} AND profileSalary.\`order\` = (SELECT MAX(ps.\`order\`) FROM profileSalary ps WHERE ps.profileId = profile.id AND ps.positionName != :notRetire2))`, + { ...existsParams, notRetire2: "เกษียณอายุราชการ" } + ); + } + }; + + // Step 1: Count query + const countQb = this.profileRepo + .createQueryBuilder("profile") + .leftJoinAndSelect("profile.posLevel", "posLevel") + .leftJoinAndSelect("profile.posType", "posType"); + baseWhere(countQb); + applySalaryFilter(countQb); + const total = await countQb.getCount(); + + // console.log(`[ProfileLeaveService] getLeaveOfficer count took ${Date.now() - t0}ms, total=${total}`); + + // Step 2: ดึงเฉพาะ profile IDs ที่ผ่านเงื่อนไข + const idQb = this.profileRepo + .createQueryBuilder("profile") + .select(["profile.id"]) + .leftJoin("profile.posLevel", "posLevel") + .leftJoin("profile.posType", "posType"); + baseWhere(idQb); + applySalaryFilter(idQb); + idQb.orderBy(sortBy, sort).skip((page - 1) * pageSize).take(pageSize); + const rawIds = await idQb.getRawMany(); + const profileIds = rawIds.map((r) => r.profile_id); + + // console.log(`[ProfileLeaveService] getLeaveOfficer ids took ${Date.now() - t0}ms, ids=${profileIds.length}`); + + if (profileIds.length === 0) { + return { data: [], total }; } - // เพิ่ม sorting และ pagination - queryBuilder - .orderBy(sortBy, sort) - .skip((page - 1) * pageSize) - .take(pageSize); + // Step 3: Load full data โดยไม่ JOIN salary + const records = await this.profileRepo.find({ + where: { id: In(profileIds) }, + relations: ["posLevel", "posType"], + order: { [sortBy.split(".")[1]]: sort } as any, + }); + // console.log(`[ProfileLeaveService] getLeaveOfficer step3 (load profiles) took ${Date.now() - t0}ms`); - const [records, total] = await queryBuilder.getManyAndCount(); + // Step 4: Load salary เฉพาะ row ที่มี order สูงสุดต่อ profileId (INNER JOIN + GROUP BY) + const salaries = await this.profileSalaryRepo + .createQueryBuilder("ps") + .innerJoin( + (subQuery) => + subQuery + .select("ps2.profileId", "pid") + .addSelect("MAX(ps2.order)", "maxOrd") + .from(ProfileSalary, "ps2") + .where("ps2.profileId IN (:...profileIds)", { profileIds }) + .andWhere("ps2.positionName != :notRetire", { notRetire: "เกษียณอายุราชการ" }) + .groupBy("ps2.profileId"), + "latest", + "latest.pid = ps.profileId AND ps.order = latest.maxOrd" + ) + .getMany(); + // console.log(`[ProfileLeaveService] getLeaveOfficer step4 (load salaries) took ${Date.now() - t0}ms, salary rows=${salaries.length}`); - // print query for debug - // console.log("SQL Query:", queryBuilder.getSql()); + // สร้าง map: profileId → salary ที่มี order สูงสุด + const salaryMap = new Map(); + for (const s of salaries) { + salaryMap.set(s.profileId, s); + } - const data = await Promise.all( - records.map((record) => Promise.resolve(this.transformOfficerData(record))), - ); + // แปลงข้อมูลพร้อม salary + const data = records.map((record) => { + const salary = salaryMap.get(record.id); + if (salary) { + (record as any).profileSalary = [salary]; + } + return this.transformOfficerData(record); + }); + // console.log(`[ProfileLeaveService] getLeaveOfficer total took ${Date.now() - t0}ms, total=${total}`); return { data, total }; } } diff --git a/src/services/rabbitmq.ts b/src/services/rabbitmq.ts index c00b8150..d84a3eed 100644 --- a/src/services/rabbitmq.ts +++ b/src/services/rabbitmq.ts @@ -29,6 +29,12 @@ import { sendWebSocket } from "./webSocket"; import { PayloadSendNoti } from "../interfaces/utils"; import { PermissionProfile } from "../entities/PermissionProfile"; import { PosMasterHistory } from "../entities/PosMasterHistory"; +import { ExecuteOfficerProfileService } from "./ExecuteOfficerProfileService"; +import { ExecuteSalaryService } from "./ExecuteSalaryService"; +import { ExecuteSalaryCurrentService } from "./ExecuteSalaryCurrentService"; +import { ExecuteSalaryEmployeeCurrentService } from "./ExecuteSalaryEmployeeCurrentService"; +import { ExecuteSalaryLeaveService } from "./ExecuteSalaryLeaveService"; +import { ExecuteSalaryEmployeeLeaveService } from "./ExecuteSalaryEmployeeLeaveService"; const redis = require("redis"); const REDIS_HOST = process.env.REDIS_HOST; @@ -320,13 +326,94 @@ async function handler(msg: amqp.ConsumeMessage): Promise { 20, ); - for (const chunk of chunks) { - await new CallAPI().PostData( - { headers: { authorization: token } }, - path + "/excecute", - { refIds: chunk }, - false, - ); + // ───────────────────────────────────────────────────────────── + // Linear Flow + // รับ resultData จาก .NET แล้วเรียก Service ตรงๆ ตาม commandType (ไม่ผ่าน HTTP loopback) + // - ExecuteOfficerProfileService : C-PM-01, 02, 14 (บรรจุ/รับโอน) + // - ExecuteSalaryCurrentService : C-PM-03, 04, 05, 06, 07, 39, 47 (แต่งตั้ง-เลื่อน-ย้าย) + // - ExecuteSalaryEmployeeCurrentService : C-PM-22, 24 (ลูกจ้าง ปรับระดับชั้นงาน-ย้าย) + // - ExecuteSalaryService : C-PM-13, 15, 16 (ให้โอน/ให้ช่วยราชการ/ให้กลับเข้าราชการ) + // - ExecuteSalaryLeaveService : C-PM-08, 09, 17, 18, 41, 48 (ข้าราชการ leave/กลับเข้าราชการ) + // - ExecuteSalaryEmployeeLeaveService : C-PM-23, 42, 43 (ลูกจ้าง leave) + // - คำสั่งอื่น ยังใช้ Circular Flow เดิม + // ───────────────────────────────────────────────────────────── + const code = command.commandType?.code; + const isOfficerProfile = ["C-PM-01", "C-PM-02", "C-PM-14"].includes(code); + const isSalaryCurrent = ["C-PM-03", "C-PM-04", "C-PM-05", "C-PM-06", "C-PM-07", "C-PM-39", "C-PM-47"].includes(code); + const isSalaryEmployeeCurrent = ["C-PM-22", "C-PM-24"].includes(code); + const isSalary = ["C-PM-13", "C-PM-15", "C-PM-16"].includes(code); + const isSalaryLeave = ["C-PM-08", "C-PM-09", "C-PM-17", "C-PM-18", "C-PM-41", "C-PM-48"].includes(code); + const isSalaryEmployeeLeave = ["C-PM-23", "C-PM-42", "C-PM-43"].includes(code); + const isLinearFlow = + isOfficerProfile || + isSalaryCurrent || + isSalaryEmployeeCurrent || + isSalary || + isSalaryLeave || + isSalaryEmployeeLeave; + + if (isLinearFlow) { + console.log(`[AMQ] Linear Flow (${code})`); + let resultData: any[] = []; + + for (const chunk of chunks) { + const res = await new CallAPI().PostData( + { headers: { authorization: token } }, + path + "/excecute", + { refIds: chunk }, + false, + ); + // response (resultData) จาก .NET + if (Array.isArray(res)) { + console.log(`[AMQ] Push result data`); + resultData.push(...res); + } + } + + console.log(`[AMQ] Received ${resultData.length} profiles from .NET (${code})`); + + // Route ไป service ที่ถูกต้องตาม commandType + if (resultData.length > 0) { + // สร้าง pseudo-req สำหรับ setLogDataDiff/save({data: req}) + const pseudoReq = { + headers: { authorization: token }, + user, + }; + const ctx = { + user: { sub: user?.sub ?? "system", name: user?.name ?? "System" }, + req: pseudoReq, + }; + + if (isOfficerProfile) { + await new ExecuteOfficerProfileService().executeCreateOfficerProfile(resultData, ctx); + console.log(`[AMQ] Processed ${resultData.length} profiles via ExecuteOfficerProfileService`); + } else if (isSalaryCurrent) { + await new ExecuteSalaryCurrentService().executeSalaryCurrent(resultData, ctx); + console.log(`[AMQ] Processed ${resultData.length} profiles via ExecuteSalaryCurrentService`); + } else if (isSalaryEmployeeCurrent) { + await new ExecuteSalaryEmployeeCurrentService().executeSalaryEmployeeCurrent(resultData, ctx); + console.log(`[AMQ] Processed ${resultData.length} profiles via ExecuteSalaryEmployeeCurrentService`); + } else if (isSalary) { + await new ExecuteSalaryService().executeSalary(resultData, ctx); + console.log(`[AMQ] Processed ${resultData.length} profiles via ExecuteSalaryService`); + } else if (isSalaryLeave) { + await new ExecuteSalaryLeaveService().executeSalaryLeave(resultData, ctx); + console.log(`[AMQ] Processed ${resultData.length} profiles via ExecuteSalaryLeaveService`); + } else if (isSalaryEmployeeLeave) { + await new ExecuteSalaryEmployeeLeaveService().executeSalaryEmployeeLeave(resultData, ctx); + console.log(`[AMQ] Processed ${resultData.length} profiles via ExecuteSalaryEmployeeLeaveService`); + } + } + } else { + console.log(`[AMQ] Circular Flow (${code})`); + for (const chunk of chunks) { + await new CallAPI().PostData( + { headers: { authorization: token } }, + path + "/excecute", + { refIds: chunk }, + false, + ); + } } Object.assign(command, { status, lastUpdateUserId, lastUpdateFullName, lastUpdatedAt }); @@ -492,14 +579,14 @@ async function handler_command_noti(msg: amqp.ConsumeMessage): Promise let profilesSend = command && command.commandSends.length > 0 ? command.commandSends - .filter((x: any) => x.profileId != null) - .map((x: any) => ({ - receiverUserId: x.profileId, - notiLink: "", - isSendMail: x.commandSendCCs.map((x: any) => x.name == "EMAIL").length > 0, - isSendInbox: x.commandSendCCs.map((x: any) => x.name == "INBOX").length > 0, - isSendNotification: true, - })) + .filter((x: any) => x.profileId != null) + .map((x: any) => ({ + receiverUserId: x.profileId, + notiLink: "", + isSendMail: x.commandSendCCs.map((x: any) => x.name == "EMAIL").length > 0, + isSendInbox: x.commandSendCCs.map((x: any) => x.name == "INBOX").length > 0, + isSendNotification: true, + })) : []; const payloadStr = await PayloadSendNoti(command.id); const profilesSendRequest = new CallAPI() @@ -695,7 +782,19 @@ async function handler_org(msg: amqp.ConsumeMessage): Promise { where: { orgRevisionId: orgRevisionPublish.id, }, - select: ["id", "current_holderId", "ancestorDNA"], + select: [ + "id", + "current_holderId", + "ancestorDNA", + "posMasterNo", + "posMasterNoPrefix", + "posMasterNoSuffix", + "orgRootId", + "orgChild1Id", + "orgChild2Id", + "orgChild3Id", + "orgChild4Id", + ], }); // Task #2160 ดึง posMasterAssign ของ revision เดิม @@ -782,8 +881,9 @@ async function handler_org(msg: amqp.ConsumeMessage): Promise { // 2. Batch load profiles ทั้งหมดในครั้งเดียว (แก้ปัญหา N+1 Query) const profilesMap = new Map(); if (profileIds.length > 0) { - const profiles = await repoProfile.findBy({ - id: In(profileIds), + const profiles = await repoProfile.find({ + where: { id: In(profileIds) }, + relations: ["posType", "posLevel"], }); profiles.forEach((p) => profilesMap.set(p.id, p)); } @@ -860,7 +960,21 @@ async function handler_org(msg: amqp.ConsumeMessage): Promise { const newHolderId = item?.next_holderId; const isHolderChanged = oldHolderId !== newHolderId; - if (isHolderChanged) { + // เช็คว่า holder เดิม แต่ตำแหน่งเปลี่ยน + const isSameHolder = oldHolderId === newHolderId && oldHolderId != null && newHolderId != null; + const isPositionChanged = + isSameHolder && + oldPm && + (oldPm.posMasterNo !== item.posMasterNo || + oldPm.posMasterNoPrefix !== item.posMasterNoPrefix || + oldPm.posMasterNoSuffix !== item.posMasterNoSuffix || + oldPm.orgRootId !== item.orgRoot?.id || + oldPm.orgChild1Id !== item.orgChild1?.id || + oldPm.orgChild2Id !== item.orgChild2?.id || + oldPm.orgChild3Id !== item.orgChild3?.id || + oldPm.orgChild4Id !== item.orgChild4?.id); + + if (isHolderChanged || isPositionChanged) { const nextHolderProfile = item.next_holderId != null && item.next_holderId !== "" ? profilesMap.get(item.next_holderId) @@ -878,6 +992,18 @@ async function handler_org(msg: amqp.ConsumeMessage): Promise { item.orgRoot?.orgRootShortName, ].find((name) => typeof name === "string" && name.trim().length > 0) ?? _null; + // ถ้าเป็นตำแหน่งนั่งทับ (isSit = true) และมีคนครอง ใช้ตำแหน่งจาก profile แทน + let positionName = selectedPosition?.positionName ?? _null; + let posTypeName = selectedPosition?.posType?.posTypeName ?? _null; + let posLevelName = selectedPosition?.posLevel?.posLevelName ?? _null; + let posExecutiveName = selectedPosition?.posExecutive?.posExecutiveName ?? _null; + if (item.isSit && nextHolderProfile) { + positionName = nextHolderProfile.position ?? _null; + posTypeName = nextHolderProfile.posType?.posTypeName ?? _null; + posLevelName = nextHolderProfile.posLevel?.posLevelName ?? _null; + posExecutiveName = nextHolderProfile.posExecutive ?? _null; + } + historyRowsToSave.push({ ancestorDNA: item.ancestorDNA ? item.ancestorDNA : _null, prefix: nextHolderProfile?.prefix || _null, @@ -887,11 +1013,11 @@ async function handler_org(msg: amqp.ConsumeMessage): Promise { posMasterNoPrefix: item.posMasterNoPrefix ?? _null, posMasterNo: item.posMasterNo ?? _null, posMasterNoSuffix: item.posMasterNoSuffix ?? _null, - position: selectedPosition?.positionName ?? _null, - posType: selectedPosition?.posType?.posTypeName ?? _null, - posLevel: selectedPosition?.posLevel?.posLevelName ?? _null, - posExecutive: selectedPosition?.posExecutive?.posExecutiveName ?? _null, - profileId: _null, + position: positionName, + posType: posTypeName, + posLevel: posLevelName, + posExecutive: posExecutiveName, + profileId: nextHolderProfile?.id || _null, rootDnaId: item.orgRoot?.ancestorDNA || _null, child1DnaId: item.orgChild1?.ancestorDNA || _null, child2DnaId: item.orgChild2?.ancestorDNA || _null, @@ -1698,6 +1824,7 @@ async function handler_org(msg: amqp.ConsumeMessage): Promise { } async function clearMenuAndRoleCache(): Promise { + console.log("[AMQ] clearMenuAndRoleCache: Starting..."); const redisClient = redis.createClient({ host: REDIS_HOST, port: REDIS_PORT, @@ -1707,17 +1834,28 @@ async function clearMenuAndRoleCache(): Promise { const delAsync = promisify(redisClient.del).bind(redisClient); try { - const menuKeys = await keysAsync("menu_*"); - if (menuKeys.length > 0) { - await delAsync(...menuKeys); - console.log(`[AMQ] Cleared ${menuKeys.length} menu cache keys`); - } + // Clear menu and role cache (patterns that affect menu display) + const menuRolePatterns = ["menu_*", "role_*"]; - const roleKeys = await keysAsync("role_*"); - if (roleKeys.length > 0) { - await delAsync(...roleKeys); - console.log(`[AMQ] Cleared ${roleKeys.length} role cache keys`); + for (const pattern of menuRolePatterns) { + console.log(`[AMQ] Checking pattern: ${pattern}`); + const keys = await keysAsync(pattern); + console.log(`[AMQ] Found ${keys.length} keys for pattern: ${pattern}`); + if (keys.length > 0) { + // Delete in chunks of 1000 to avoid argument limit + const chunkSize = 1000; + for (let i = 0; i < keys.length; i += chunkSize) { + const chunk = keys.slice(i, i + chunkSize); + await delAsync(...chunk); + } + console.log(`[AMQ] Cleared ${keys.length} cache keys for pattern: ${pattern}`); + } else { + console.log(`[AMQ] No keys found for pattern: ${pattern}`); + } } + console.log("[AMQ] clearMenuAndRoleCache: Completed successfully"); + } catch (error) { + console.error("[AMQ] clearMenuAndRoleCache ERROR:", error); } finally { redisClient.quit(); } diff --git a/src/services/webSocket.ts b/src/services/webSocket.ts index 36359cd8..7d464655 100644 --- a/src/services/webSocket.ts +++ b/src/services/webSocket.ts @@ -22,7 +22,7 @@ export function initWebSocket() { }); io.on("connection", (ws) => { - console.log("✅ Client connected to WebSocket"); + // console.log("✅ Client connected to WebSocket"); ws.on("close", () => { console.log("❌ Client disconnected"); @@ -46,7 +46,7 @@ export async function sendWebSocket( ) { if (!io) initWebSocket(); // console.log( `🔔 :`,data.message); - + for (let [id, session] of io.of("/").sockets) { const user: { sub: string; diff --git a/src/utils/org-formatting.ts b/src/utils/org-formatting.ts index fd61f33b..eb4b7a9d 100644 --- a/src/utils/org-formatting.ts +++ b/src/utils/org-formatting.ts @@ -101,7 +101,7 @@ export function getOrgFullName(posMaster: PosMaster): string { } /** - * สร้างเลขที่ตำแหน่ง เช่น "กทม. กบ.1234ช" + * สร้างเลขที่ตำแหน่ง เช่น "กทม. กบ. 1234 ช" */ export function getPosMasterNo(posMaster: PosMaster): string { const orgShortName = getOrgShortName(posMaster); @@ -110,5 +110,5 @@ export function getPosMasterNo(posMaster: PosMaster): string { posMaster.posMasterNo, posMaster.posMasterNoSuffix, ].filter((part) => part !== null && part !== undefined); - return `${orgShortName} ${parts.join('')}`; + return `${orgShortName} ${parts.join(' ')}`; } diff --git a/src/utils/tenure.ts b/src/utils/tenure.ts index dbdedbb3..1c97dff1 100644 --- a/src/utils/tenure.ts +++ b/src/utils/tenure.ts @@ -1,18 +1,37 @@ /** - * คำนวณอายุงานจากจำนวนวันรวม - * ใช้สูตรเดียวกับ Stored Procedure GetProfileSalaryPosition - * @param totalDays จำนวนวันรวม - * @returns { year, month, day } ปี เดือน วัน + * Normalize a duration sum using calendar arithmetic + * Converts excess days to months using average month length (30.4375 days) + * and excess months to years. Matches the logic used in stored procedures. + * + * @param years Total years from sum + * @param months Total months from sum + * @param days Total days from sum + * @returns Normalized { years, months, days } */ -export function calculateTenure(totalDays: number) { - // Match stored procedure formula: - // days_diff / 365.2524 AS Years - // (days_diff / 30.4375) % 12 AS Months - // days_diff % 30.4375 AS Days +export function normalizeDurationSumSimple( + years: number, + months: number, + days: number, +): { years: number; months: number; days: number } { + const DAYS_PER_MONTH = 30.4375; // Average days per month in Gregorian calendar - const year = Math.floor(totalDays / 365.2524); - const month = Math.floor((totalDays / 30.4375) % 12); - const day = Math.floor(totalDays % 30.4375); + let totalMonths = months; + let totalDays = days; - return { year, month, day }; + // Convert excess days to months + if (totalDays >= DAYS_PER_MONTH) { + const additionalMonths = Math.floor(totalDays / DAYS_PER_MONTH); + totalMonths += additionalMonths; + totalDays = totalDays - additionalMonths * DAYS_PER_MONTH; + } + + // Convert excess months to years + let totalYears = years; + if (totalMonths >= 12) { + const additionalYears = Math.floor(totalMonths / 12); + totalYears += additionalYears; + totalMonths = totalMonths % 12; + } + + return { years: totalYears, months: Math.floor(totalMonths), days: Math.floor(totalDays) }; } diff --git a/tsoa.json b/tsoa.json index 492907b8..e346e3b1 100644 --- a/tsoa.json +++ b/tsoa.json @@ -29,6 +29,12 @@ "name": "X-API-Key", "description": "API KEY สำหรับ Web Service", "in": "header" + }, + "internalAuth": { + "type": "apiKey", + "name": "api-key", + "description": "API KEY สำหรับ Internal Service (.NET, HRMS)", + "in": "header" } }, "tags": [