From af2bd5054f416de7c15cacb711a8c658499af65f Mon Sep 17 00:00:00 2001 From: waruneeauy Date: Thu, 14 May 2026 11:37:57 +0700 Subject: [PATCH 01/83] feat: clear menu and role cache when organization structure is published Add Redis cache clearing to handler_org function to clear all menu_* and role_* keys after successfully publishing organization structure changes. This ensures users see updated permissions and menus immediately after publish. - Add promisify import and Redis client setup - Add clearMenuAndRoleCache helper function - Call cache clearing before successful return Co-Authored-By: Claude Opus 4.7 --- src/services/rabbitmq.ts | 88 +++++++++++++++++++++++++++++----------- 1 file changed, 64 insertions(+), 24 deletions(-) diff --git a/src/services/rabbitmq.ts b/src/services/rabbitmq.ts index 990cd5fb..c00b8150 100644 --- a/src/services/rabbitmq.ts +++ b/src/services/rabbitmq.ts @@ -1,5 +1,6 @@ import { randomUUID } from "crypto"; import amqp from "amqplib"; +import { promisify } from "util"; import { AppDataSource } from "../database/data-source"; import { Command } from "../entities/Command"; import { chunkArray, commandTypePath } from "../interfaces/utils"; @@ -29,6 +30,10 @@ import { PayloadSendNoti } from "../interfaces/utils"; import { PermissionProfile } from "../entities/PermissionProfile"; import { PosMasterHistory } from "../entities/PosMasterHistory"; +const redis = require("redis"); +const REDIS_HOST = process.env.REDIS_HOST; +const REDIS_PORT = process.env.REDIS_PORT; + let reconnectTimer: ReturnType | null = null; function scheduleReconnect() { @@ -143,7 +148,9 @@ function createConsumer( //----> consumer console.log("[AMQ] Process Consumer success"); return channel.ack(msg); } - console.error(`[AMQ] Process Consumer failed on queue ${queue}, acknowledging without retry`); + console.error( + `[AMQ] Process Consumer failed on queue ${queue}, acknowledging without retry`, + ); return channel.ack(msg); } catch (error) { console.error(`[AMQ] Consumer processing error on queue ${queue}:`, error); @@ -547,19 +554,19 @@ async function handler_org(msg: amqp.ConsumeMessage): Promise { const repoPosmaster = AppDataSource.getRepository(PosMaster); const posMasterAssignRepository = AppDataSource.getRepository(PosMasterAssign); const posMasterActRepository = AppDataSource.getRepository(PosMasterAct); - const permissionProfilesRepository = AppDataSource.getRepository(PermissionProfile); - const repoEmployeePosmaster = AppDataSource.getRepository(EmployeePosMaster); - const repoEmployeeTempPosmaster = AppDataSource.getRepository(EmployeeTempPosMaster); + // const permissionProfilesRepository = AppDataSource.getRepository(PermissionProfile); + // const repoEmployeePosmaster = AppDataSource.getRepository(EmployeePosMaster); + // const repoEmployeeTempPosmaster = AppDataSource.getRepository(EmployeeTempPosMaster); const repoProfile = AppDataSource.getRepository(Profile); - const repoProfileEmployee = AppDataSource.getRepository(ProfileEmployee); - const employeePositionRepository = AppDataSource.getRepository(EmployeePosition); + // const repoProfileEmployee = AppDataSource.getRepository(ProfileEmployee); + // const employeePositionRepository = AppDataSource.getRepository(EmployeePosition); const repoOrgRevision = AppDataSource.getRepository(OrgRevision); - const orgRootRepository = AppDataSource.getRepository(OrgRoot); - const child1Repository = AppDataSource.getRepository(OrgChild1); - const child2Repository = AppDataSource.getRepository(OrgChild2); - const child3Repository = AppDataSource.getRepository(OrgChild3); - const child4Repository = AppDataSource.getRepository(OrgChild4); - const { data, token, user } = JSON.parse(msg.content.toString()); + // const orgRootRepository = AppDataSource.getRepository(OrgRoot); + // const child1Repository = AppDataSource.getRepository(OrgChild1); + // const child2Repository = AppDataSource.getRepository(OrgChild2); + // const child3Repository = AppDataSource.getRepository(OrgChild3); + // const child4Repository = AppDataSource.getRepository(OrgChild4); + const { data, user } = JSON.parse(msg.content.toString()); const { id, status, lastUpdateUserId, lastUpdateFullName, lastUpdatedAt } = data; console.log(`[AMQ] Received message - revisionId: ${id}, status: ${status}`); @@ -994,7 +1001,7 @@ async function handler_org(msg: amqp.ConsumeMessage): Promise { if (posMasterUpdates.length > 0) { const chunks = chunkArray(posMasterUpdates, 500); const posMasterTableName = repoPosmaster.metadata.tableName; - for (const chunk of chunks as typeof posMasterUpdates[]) { + for (const chunk of chunks as (typeof posMasterUpdates)[]) { const caseClauses = chunk.map(() => "WHEN ? THEN ?").join(" "); const wherePlaceholders = chunk.map(() => "?").join(", "); const params = chunk.flatMap((update: (typeof posMasterUpdates)[number]) => [ @@ -1207,13 +1214,15 @@ async function handler_org(msg: amqp.ConsumeMessage): Promise { ? x.id : x.ancestorDNA, })); - const _orgemployeeTempPosMaster: EmployeeTempPosMaster[] = orgemployeeTempPosMaster.map((x) => ({ - ...x, - ancestorDNA: - x.ancestorDNA == null || x.ancestorDNA == "00000000-0000-0000-0000-000000000000" - ? x.id - : x.ancestorDNA, - })); + const _orgemployeeTempPosMaster: EmployeeTempPosMaster[] = orgemployeeTempPosMaster.map( + (x) => ({ + ...x, + ancestorDNA: + x.ancestorDNA == null || x.ancestorDNA == "00000000-0000-0000-0000-000000000000" + ? x.id + : x.ancestorDNA, + }), + ); console.time("[AMQ] insert_employeePosMaster"); await repoEmployeePosmaster @@ -1316,7 +1325,10 @@ async function handler_org(msg: amqp.ConsumeMessage): Promise { lastUpdateFullName: "System Administrator", lastUpdatedAt: timestamp, }); - const buildColumnData = (repository: Repository, source: T): Partial => { + const buildColumnData = ( + repository: Repository, + source: T, + ): Partial => { const row = {} as Partial; const target = row as Record; const sourceRecord = source as Record; @@ -1363,8 +1375,7 @@ async function handler_org(msg: amqp.ConsumeMessage): Promise { ...buildColumnData(employeePositionRepository, position), id: randomUUID(), posMasterId: positionParentKey === "posMasterId" ? parentId : undefined, - posMasterTempId: - positionParentKey === "posMasterTempId" ? parentId : undefined, + posMasterTempId: positionParentKey === "posMasterTempId" ? parentId : undefined, ...buildAuditFields(positionTimestamp), }); } @@ -1409,7 +1420,8 @@ async function handler_org(msg: amqp.ConsumeMessage): Promise { const dataId = x.id; const matchedOrgRoot = findMatchedNodeByAncestorDNA(orgRootCurrent, x); - const filteredEmployeePosMaster = employeePosMasterByNode.get(getNodeKey("root", dataId)) ?? []; + const filteredEmployeePosMaster = + employeePosMasterByNode.get(getNodeKey("root", dataId)) ?? []; await cloneEmployeeNodeBatch( filteredEmployeePosMaster, @@ -1664,6 +1676,8 @@ async function handler_org(msg: amqp.ConsumeMessage): Promise { console.log(`[AMQ] handler_org SUCCESS - Total time: ${Date.now() - startTime}ms`); console.timeEnd("[AMQ] handler_org_total"); + + await clearMenuAndRoleCache(); return true; } catch (error) { const totalTime = Date.now() - startTime; @@ -1683,6 +1697,32 @@ async function handler_org(msg: amqp.ConsumeMessage): Promise { } } +async function clearMenuAndRoleCache(): Promise { + const redisClient = redis.createClient({ + host: REDIS_HOST, + port: REDIS_PORT, + }); + + const keysAsync = promisify(redisClient.keys).bind(redisClient); + 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`); + } + + const roleKeys = await keysAsync("role_*"); + if (roleKeys.length > 0) { + await delAsync(...roleKeys); + console.log(`[AMQ] Cleared ${roleKeys.length} role cache keys`); + } + } finally { + redisClient.quit(); + } +} + async function handler_org_draft(msg: amqp.ConsumeMessage): Promise { const { data, token, user } = JSON.parse(msg.content.toString()); const { requestBody, request, revision } = data; From cab2f76bd6f593b56f00b26e32db52a8bbc57b2c Mon Sep 17 00:00:00 2001 From: waruneeauy Date: Thu, 14 May 2026 12:33:21 +0700 Subject: [PATCH 02/83] add api notify-from-token --- src/controllers/SocketController.ts | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/src/controllers/SocketController.ts b/src/controllers/SocketController.ts index eb0b72c2..c975336f 100644 --- a/src/controllers/SocketController.ts +++ b/src/controllers/SocketController.ts @@ -1,5 +1,6 @@ -import { Body, Controller, Post, Route } from "tsoa"; +import { Body, Controller, Post, Request, Route, Security } from "tsoa"; import { sendWebSocket } from "../services/webSocket"; +import { RequestWithUser } from "../middlewares/user"; @Route("/api/v1/org/through-socket") export class SocketController extends Controller { @@ -22,4 +23,26 @@ export class SocketController extends Controller { }, ); } + + @Post("notify-from-token") + @Security("bearerAuth") + async notifyFromToken( + @Body() + payload: { + message: string; + targetUserId?: string | string[]; + roles?: string | string[]; + error?: boolean; + }, + @Request() req: RequestWithUser, + ) { + sendWebSocket( + "socket-notification", + { success: !payload.error, message: payload.message }, + { + roles: payload.roles || req.user.role || [], + userId: payload.targetUserId || req.user.sub || [], + }, + ); + } } From 3c8b377764794ff37d93e44e3e64a392f00293db Mon Sep 17 00:00:00 2001 From: harid Date: Thu, 14 May 2026 17:15:39 +0700 Subject: [PATCH 03/83] =?UTF-8?q?fix=20=E0=B8=A3=E0=B8=B0=E0=B8=9A?= =?UTF-8?q?=E0=B8=9A=E0=B9=81=E0=B8=88=E0=B9=89=E0=B8=87=20Noti=20?= =?UTF-8?q?=E0=B9=84=E0=B8=A1=E0=B9=88=E0=B8=95=E0=B8=A3=E0=B8=87=E0=B8=95?= =?UTF-8?q?=E0=B8=B2=E0=B8=A1=E0=B8=AA=E0=B8=B4=E0=B8=97=E0=B8=98=E0=B8=B4?= =?UTF-8?q?=E0=B9=8C=E0=B8=97=E0=B8=B5=E0=B9=88=E0=B9=84=E0=B8=94=E0=B9=89?= =?UTF-8?q?=E0=B8=A3=E0=B8=B1=E0=B8=9A=20#2488?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../OrganizationDotnetController.ts | 145 +++++++++++++++++- 1 file changed, 143 insertions(+), 2 deletions(-) diff --git a/src/controllers/OrganizationDotnetController.ts b/src/controllers/OrganizationDotnetController.ts index 86405850..e61bc4d8 100644 --- a/src/controllers/OrganizationDotnetController.ts +++ b/src/controllers/OrganizationDotnetController.ts @@ -8720,7 +8720,16 @@ export class OrganizationDotnetController extends Controller { ) { const profile = await this.profileRepo.findOne({ where: { id: requestBody.profileId }, - relations: ["current_holders", "current_holders.orgRevision"], + relations: { + current_holders: { + orgRevision: true, + orgRoot: true, + orgChild1: true, + orgChild2: true, + orgChild3: true, + orgChild4: true + } + } }); if (!profile) throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูลโปรไฟล์"); @@ -8755,10 +8764,21 @@ export class OrganizationDotnetController extends Controller { "orgChild2.ancestorDNA AS child2DnaId", "orgChild3.ancestorDNA AS child3DnaId", "orgChild4.ancestorDNA AS child4DnaId", + "authRoleAttr.attrPrivilege AS attrPrivilege", ]) .distinct(true) // ต้องมี posMasterAssign .innerJoin("posMasterAssign", "assign", "assign.posMasterId = pm.id") + // INNER JOIN เพื่อเอาเฉพาะที่มี attrPrivilege + .innerJoin("pm.authRole", "authRole") + .innerJoin( + "authRole.authRoles", "authRoleAttr", + "authRoleAttr.authSysId = :authSysId AND authRoleAttr.attrIsList = :attrIsList", + { + attrIsList: true, + authSysId: assign.id + } + ) // join เพื่อเอา ancestorDNA .leftJoin("pm.orgRoot", "orgRoot") .leftJoin("pm.orgChild1", "orgChild1") @@ -8780,6 +8800,127 @@ export class OrganizationDotnetController extends Controller { }) .getRawMany(); - return new HttpSuccess(posMasters); + // ──────────────────────────────────────────────────────── + // กรองตามสิทธิ์ (NORMAL, CHILD, BROTHER) + // ROOT และ PARENT ให้ผ่านทุกคน เพราะ filter orgRootId อยู่แล้ว + // ──────────────────────────────────────────────────────── + + // 1. หา User Node + const userNode = currentHolder.orgChild4Id ? 4 + : currentHolder.orgChild3Id ? 3 + : currentHolder.orgChild2Id ? 2 + : currentHolder.orgChild1Id ? 1 + : 0; + + // 2. หา User DNA แต่ละระดับ + const userDna = { + root: currentHolder.orgRoot?.ancestorDNA ?? null, + child1: currentHolder.orgChild1?.ancestorDNA ?? null, + child2: currentHolder.orgChild2?.ancestorDNA ?? null, + child3: currentHolder.orgChild3?.ancestorDNA ?? null, + child4: currentHolder.orgChild4?.ancestorDNA ?? null, + }; + + // 3. กรอง posMasters ตามสิทธิ์ + const filteredPosMasters = posMasters.filter((staff) => { + const privilege = staff.attrPrivilege; + + // ROOT และ PARENT: ให้ผ่านทุกคน เพราะ filter orgRootId อยู่แล้ว + if (privilege === "ROOT" || privilege === "PARENT" || privilege === "OWNER") { + return true; + } + + // หา Staff Node + const staffNode = staff.orgChild4Id ? 4 + : staff.orgChild3Id ? 3 + : staff.orgChild2Id ? 2 + : staff.orgChild1Id ? 1 + : 0; + + // หา Staff DNA + const staffDna = { + root: staff.rootDnaId, + child1: staff.child1DnaId, + child2: staff.child2DnaId, + child3: staff.child3DnaId, + child4: staff.child4DnaId, + }; + + // NORMAL: Node เท่ากัน + DNA เหมือนกันทุกตัว + if (privilege === "NORMAL") { + return ( + staffNode === userNode && + staffDna.root === userDna.root && + (staffNode < 1 || staffDna.child1 === userDna.child1) && + (staffNode < 2 || staffDna.child2 === userDna.child2) && + (staffNode < 3 || staffDna.child3 === userDna.child3) && + (staffNode < 4 || staffDna.child4 === userDna.child4) + ); + } + + // CHILD: Staff เห็น User ที่อยู่ในกิ่งลูก + if (privilege === "CHILD") { + // Staff ต้องอยู่บนกว่าหรือเท่ากับ User + if (staffNode > userNode) return false; + + // เช็ค DNA ตรงกันที่ระดับ Staff + switch (staffNode) { + case 0: + if (staffDna.root !== userDna.root) return false; + break; + case 1: + if (staffDna.root !== userDna.root) return false; + if (staffDna.child1 !== userDna.child1) return false; + break; + case 2: + if (staffDna.root !== userDna.root) return false; + if (staffDna.child1 !== userDna.child1) return false; + if (staffDna.child2 !== userDna.child2) return false; + break; + case 3: + if (staffDna.root !== userDna.root) return false; + if (staffDna.child1 !== userDna.child1) return false; + if (staffDna.child2 !== userDna.child2) return false; + if (staffDna.child3 !== userDna.child3) return false; + break; + case 4: + if (staffDna.root !== userDna.root) return false; + if (staffDna.child1 !== userDna.child1) return false; + if (staffDna.child2 !== userDna.child2) return false; + if (staffDna.child3 !== userDna.child3) return false; + if (staffDna.child4 !== userDna.child4) return false; + break; + } + return true; + } + + // 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 (staffDna.root !== userDna.root) return false; + } else if (staffNode === 1) { + if (staffDna.root !== userDna.root) return false; + if (staffDna.child1 !== userDna.child1) return false; + } 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; + } else if (staffNode === 3) { + if (staffDna.child2 !== userDna.child2) return false; + if (staffDna.child3 !== userDna.child3) return false; + } else if (staffNode === 4) { + if (staffDna.child3 !== userDna.child3) return false; + if (staffDna.child4 !== userDna.child4) return false; + } + return true; + } + // กรณีอื่นๆ ให้ผ่าน + return true; + }); + return new HttpSuccess(filteredPosMasters); } } From 9f2fec3ee37e9a131044b2e9ea8abaf7db63e171 Mon Sep 17 00:00:00 2001 From: harid Date: Fri, 15 May 2026 11:12:17 +0700 Subject: [PATCH 04/83] =?UTF-8?q?fix=20=E0=B8=A3=E0=B8=B0=E0=B8=9A?= =?UTF-8?q?=E0=B8=9A=E0=B9=81=E0=B8=88=E0=B9=89=E0=B8=87=20Noti=20?= =?UTF-8?q?=E0=B8=AA=E0=B8=B4=E0=B8=97=E0=B8=98=E0=B8=B4=E0=B9=8C=20BROTHE?= =?UTF-8?q?R?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/controllers/OrganizationDotnetController.ts | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/src/controllers/OrganizationDotnetController.ts b/src/controllers/OrganizationDotnetController.ts index e61bc4d8..c5efd798 100644 --- a/src/controllers/OrganizationDotnetController.ts +++ b/src/controllers/OrganizationDotnetController.ts @@ -8863,7 +8863,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,10 +8895,8 @@ 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 (staffDna.root !== userDna.root) return false; } else if (staffNode === 1) { @@ -8907,14 +8904,13 @@ export class OrganizationDotnetController extends Controller { if (staffDna.child1 !== userDna.child1) return false; } 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; } From 74d03176cdaac4700aacf8bb78ebd9d72481f91f Mon Sep 17 00:00:00 2001 From: harid Date: Fri, 15 May 2026 11:53:14 +0700 Subject: [PATCH 05/83] =?UTF-8?q?fix=20=E0=B8=A3=E0=B8=B0=E0=B8=9A?= =?UTF-8?q?=E0=B8=9A=E0=B9=81=E0=B8=88=E0=B9=89=E0=B8=87=20Noti=20?= =?UTF-8?q?=E0=B9=84=E0=B8=A1=E0=B9=88=E0=B8=95=E0=B8=A3=E0=B8=87=E0=B8=95?= =?UTF-8?q?=E0=B8=B2=E0=B8=A1=E0=B8=AA=E0=B8=B4=E0=B8=97=E0=B8=98=E0=B8=B4?= =?UTF-8?q?=E0=B9=8C=E0=B8=97=E0=B8=B5=E0=B9=88=E0=B9=84=E0=B8=94=E0=B9=89?= =?UTF-8?q?=E0=B8=A3=E0=B8=B1=E0=B8=9A=20#2488?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/controllers/OrganizationDotnetController.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/controllers/OrganizationDotnetController.ts b/src/controllers/OrganizationDotnetController.ts index c5efd798..1af5c723 100644 --- a/src/controllers/OrganizationDotnetController.ts +++ b/src/controllers/OrganizationDotnetController.ts @@ -8897,20 +8897,20 @@ export class OrganizationDotnetController extends Controller { if (privilege === "BROTHER") { if (userNode < staffNode - 1 || userNode > 4) return false; - 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 && userDna.child2 !== null) 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 && userDna.child3 !== null) 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 && userDna.child4 !== null) return false; + // if (staffDna.child4 !== userDna.child4 && userDna.child4 !== null) return false; } return true; } From b103e1578816861a95dfdf16d85e6f99f9ea3a3b Mon Sep 17 00:00:00 2001 From: waruneeauy Date: Fri, 15 May 2026 14:33:00 +0700 Subject: [PATCH 06/83] fixed web socket noti by token --- src/controllers/SocketController.ts | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) 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, ); } } From 7985125882e33640e1593d2f31b9d415363a68fe Mon Sep 17 00:00:00 2001 From: harid Date: Fri, 15 May 2026 14:58:13 +0700 Subject: [PATCH 07/83] api check keycloak for process check in --- .../OrganizationDotnetController.ts | 117 ++++++++++++++++++ 1 file changed, 117 insertions(+) diff --git a/src/controllers/OrganizationDotnetController.ts b/src/controllers/OrganizationDotnetController.ts index 1af5c723..23255fe3 100644 --- a/src/controllers/OrganizationDotnetController.ts +++ b/src/controllers/OrganizationDotnetController.ts @@ -2350,6 +2350,123 @@ 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}") + async GetProfileForProcessCheckInAsync(@Path() keycloakId: string) { + /* ========================= + * 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) { + const profile = await this.profileEmpRepo.findOne({ + where: { keycloak: keycloakId }, + relations: { + current_holders: { + orgRevision: true, + orgRoot: true, + orgChild1: true, + orgChild2: true, + orgChild3: true, + orgChild4: true, + }, + }, + }); + if (!profile) throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูล"); + const currentHolder = profile.current_holders?.find( + (x) => + x.orgRevision?.orgRevisionIsDraft === false && + x.orgRevision?.orgRevisionIsCurrent === true, + ); + + const mapProfile = { + profileType: "EMPLOYEE", + 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, + }; + + return new HttpSuccess(mapProfile); + } + + /* ========================================= + * 2. current holder (Officer) + * ========================================= */ + const currentHolder = profile.current_holders?.find( + (x) => + x.orgRevision?.orgRevisionIsDraft === false && x.orgRevision?.orgRevisionIsCurrent === true, + ); + + /* ========================================= + * 6. 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, + }; + + return new HttpSuccess(mapProfile); + } + /** * API Get Profile For Logs * From 173378d87c3096ad90b855376540ce6e3d0e761c Mon Sep 17 00:00:00 2001 From: harid Date: Mon, 18 May 2026 09:18:07 +0700 Subject: [PATCH 08/83] =?UTF-8?q?fix=20=E0=B8=A2=E0=B8=A8=E0=B9=84?= =?UTF-8?q?=E0=B8=A1=E0=B9=88=E0=B9=81=E0=B8=AA=E0=B8=94=E0=B8=87=E0=B9=83?= =?UTF-8?q?=E0=B8=99=E0=B8=A3=E0=B8=B0=E0=B8=9A=E0=B8=9A=E0=B8=97=E0=B8=B0?= =?UTF-8?q?=E0=B9=80=E0=B8=9A=E0=B8=B5=E0=B8=A2=E0=B8=99=E0=B8=9B=E0=B8=A3?= =?UTF-8?q?=E0=B8=B0=E0=B8=A7=E0=B8=B1=E0=B8=95=E0=B8=B4=E0=B8=AB=E0=B8=A5?= =?UTF-8?q?=E0=B8=B1=E0=B8=87=E0=B8=AD=E0=B8=AD=E0=B8=81=E0=B8=84=E0=B8=B3?= =?UTF-8?q?=E0=B8=AA=E0=B8=B1=E0=B9=88=E0=B8=87=E0=B8=A3=E0=B8=B1=E0=B8=9A?= =?UTF-8?q?=E0=B9=82=E0=B8=AD=E0=B8=99=E0=B9=80=E0=B8=AA=E0=B8=A3=E0=B9=87?= =?UTF-8?q?=E0=B8=88=E0=B8=AA=E0=B8=B4=E0=B9=89=E0=B8=99=20#2469?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/controllers/CommandController.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/controllers/CommandController.ts b/src/controllers/CommandController.ts index 27d2671c..d50a3431 100644 --- a/src/controllers/CommandController.ts +++ b/src/controllers/CommandController.ts @@ -6594,6 +6594,7 @@ export class CommandController extends Controller { profile.amountSpecial = item.bodyProfile.amountSpecial ?? null; profile.isProbation = item.bodyProfile.isProbation; //เพิ่มใหม่จากรับโอน + profile.rank = item.bodyProfile.rank ?? null; profile.prefix = item.bodyProfile.prefix ?? null; profile.prefixMain = item.bodyProfile.prefix ?? null; profile.firstName = item.bodyProfile.firstName ?? null; @@ -6657,6 +6658,7 @@ export class CommandController extends Controller { 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.prefix ?? null; profile.prefixMain = item.bodyProfile.prefix ?? null; profile.firstName = item.bodyProfile.firstName ?? null; @@ -6715,6 +6717,7 @@ export class CommandController extends Controller { profile.lastUpdateFullName = req.user.name; profile.lastUpdatedAt = new Date(); //เพิ่มใหม่จากรับโอน + profile.rank = item.bodyProfile.rank ?? null; profile.prefix = item.bodyProfile.prefix && item.bodyProfile.prefix != "" ? item.bodyProfile.prefix From 15830ef2ba97364ced1c0142abb024fcc19e5ab8 Mon Sep 17 00:00:00 2001 From: harid Date: Mon, 18 May 2026 15:02:38 +0700 Subject: [PATCH 09/83] =?UTF-8?q?fix=20=E0=B8=A2=E0=B8=A8=E0=B9=84?= =?UTF-8?q?=E0=B8=A1=E0=B9=88=E0=B9=81=E0=B8=AA=E0=B8=94=E0=B8=87=E0=B9=83?= =?UTF-8?q?=E0=B8=99=E0=B8=A3=E0=B8=B0=E0=B8=9A=E0=B8=9A=E0=B8=97=E0=B8=B0?= =?UTF-8?q?=E0=B9=80=E0=B8=9A=E0=B8=B5=E0=B8=A2=E0=B8=99=20#2469=20+=20Err?= =?UTF-8?q?or=20Log=20api=20check-keycloak?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/controllers/CommandController.ts | 21 ++- .../OrganizationDotnetController.ts | 136 +++++++++--------- 2 files changed, 81 insertions(+), 76 deletions(-) diff --git a/src/controllers/CommandController.ts b/src/controllers/CommandController.ts index d50a3431..fcadf77c 100644 --- a/src/controllers/CommandController.ts +++ b/src/controllers/CommandController.ts @@ -6594,9 +6594,9 @@ export class CommandController extends Controller { profile.amountSpecial = item.bodyProfile.amountSpecial ?? null; profile.isProbation = item.bodyProfile.isProbation; //เพิ่มใหม่จากรับโอน - profile.rank = item.bodyProfile.rank ?? null; - profile.prefix = item.bodyProfile.prefix ?? null; - profile.prefixMain = item.bodyProfile.prefix ?? null; + 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; @@ -6658,9 +6658,9 @@ export class CommandController extends Controller { 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.prefix ?? null; - profile.prefixMain = item.bodyProfile.prefix ?? null; + 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; @@ -6717,12 +6717,9 @@ export class CommandController extends Controller { profile.lastUpdateFullName = req.user.name; profile.lastUpdatedAt = new Date(); //เพิ่มใหม่จากรับโอน - profile.rank = item.bodyProfile.rank ?? null; - profile.prefix = - item.bodyProfile.prefix && item.bodyProfile.prefix != "" - ? item.bodyProfile.prefix - : profile.prefix; - profile.prefixMain = item.bodyProfile.prefix ?? null; + 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 diff --git a/src/controllers/OrganizationDotnetController.ts b/src/controllers/OrganizationDotnetController.ts index 23255fe3..b26cf73c 100644 --- a/src/controllers/OrganizationDotnetController.ts +++ b/src/controllers/OrganizationDotnetController.ts @@ -2357,26 +2357,11 @@ export class OrganizationDotnetController extends Controller { */ @Get("check-keycloak/{keycloakId}") async GetProfileForProcessCheckInAsync(@Path() keycloakId: string) { - /* ========================= - * 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) { - const profile = await this.profileEmpRepo.findOne({ + try { + /* ========================= + * 1. Load profile (Officer) + * ========================= */ + const profile = await this.profileRepo.findOne({ where: { keycloak: keycloakId }, relations: { current_holders: { @@ -2389,15 +2374,72 @@ export class OrganizationDotnetController extends Controller { }, }, }); - if (!profile) throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูล"); + + // Employee + if (!profile) { + 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) 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, + }; + + return new HttpSuccess(mapProfile); + } + + /* ========================================= + * 2. current holder (Officer) + * ========================================= */ const currentHolder = profile.current_holders?.find( (x) => - x.orgRevision?.orgRevisionIsDraft === false && - x.orgRevision?.orgRevisionIsCurrent === true, + x.orgRevision?.orgRevisionIsDraft === false && x.orgRevision?.orgRevisionIsCurrent === true, ); + /* ========================================= + * 3. map response + * ========================================= */ const mapProfile = { - profileType: "EMPLOYEE", + profileType: "OFFICER", id: profile.id, keycloak: profile.keycloak, prefix: profile.prefix, @@ -2424,47 +2466,13 @@ export class OrganizationDotnetController extends Controller { }; 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; } - - /* ========================================= - * 2. current holder (Officer) - * ========================================= */ - const currentHolder = profile.current_holders?.find( - (x) => - x.orgRevision?.orgRevisionIsDraft === false && x.orgRevision?.orgRevisionIsCurrent === true, - ); - - /* ========================================= - * 6. 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, - }; - - return new HttpSuccess(mapProfile); } /** From f1c546ba8fc13951883dc5ddd19e12dc8d098355 Mon Sep 17 00:00:00 2001 From: waruneeauy Date: Mon, 18 May 2026 17:37:43 +0700 Subject: [PATCH 10/83] fix store procedure --- ...mployeeSalaryLevel_calendar_arithmetic.sql | 140 ++++++++++++++++++ ...oyeeSalaryPosition_calendar_arithmetic.sql | 137 +++++++++++++++++ ...ileSalaryExecutive_calendar_arithmetic.sql | 12 +- ...ProfileSalaryLevel_calendar_arithmetic.sql | 12 +- ...fileSalaryPosition_calendar_arithmetic.sql | 10 +- 5 files changed, 294 insertions(+), 17 deletions(-) create mode 100644 docs/migrations/fix_GetProfileEmployeeSalaryLevel_calendar_arithmetic.sql create mode 100644 docs/migrations/fix_GetProfileEmployeeSalaryPosition_calendar_arithmetic.sql 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..07d85ee8 100644 --- a/docs/migrations/fix_GetProfileSalaryExecutive_calendar_arithmetic.sql +++ b/docs/migrations/fix_GetProfileSalaryExecutive_calendar_arithmetic.sql @@ -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; From d093953fbe44c7c774f674d85d2526e44370da0b Mon Sep 17 00:00:00 2001 From: waruneeauy Date: Mon, 18 May 2026 20:56:20 +0700 Subject: [PATCH 11/83] =?UTF-8?q?=E0=B9=81=E0=B8=81=E0=B9=89=E0=B9=84?= =?UTF-8?q?=E0=B8=82=E0=B8=81=E0=B8=B2=E0=B8=A3=E0=B8=84=E0=B8=B3=E0=B8=99?= =?UTF-8?q?=E0=B8=A7=E0=B8=99=E0=B8=A3=E0=B8=B0=E0=B8=A2=E0=B8=B0=E0=B9=80?= =?UTF-8?q?=E0=B8=A7=E0=B8=A5=E0=B8=B2=E0=B8=84=E0=B8=A3=E0=B8=AD=E0=B8=87?= =?UTF-8?q?=E0=B8=95=E0=B8=B3=E0=B9=81=E0=B8=AB=E0=B8=99=E0=B9=88=E0=B8=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/controllers/ProfileSalaryController.ts | 211 +++++++++--------- .../ProfileSalaryEmployeeController.ts | 114 ++++++++-- src/utils/tenure.ts | 45 ++-- 3 files changed, 227 insertions(+), 143 deletions(-) diff --git a/src/controllers/ProfileSalaryController.ts b/src/controllers/ProfileSalaryController.ts index 277ac081..ce477490 100644 --- a/src/controllers/ProfileSalaryController.ts +++ b/src/controllers/ProfileSalaryController.ts @@ -23,7 +23,7 @@ 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 { normalizeDurationSumSimple } from "../utils/tenure"; import { TenurePositionOfficer } from "../entities/TenurePositionOfficer"; import { TenureLevelOfficer } from "../entities/TenureLevelOfficer"; import { TenurePositionEmployee } from "../entities/TenurePositionEmployee"; @@ -78,33 +78,28 @@ export class ProfileSalaryController extends Controller { _currentDate, ]); const _position = position.length > 0 ? position[0] : []; + // Filter for current position and use SP's calculated values (calendar arithmetic) const mapPosition = _position.length > 1 ? _position.slice(1).map((curr: any, index: number) => ({ - days_diff: curr.days_diff, positionName: _position[index]?.positionName, + // 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, })) : []; - 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); + const currentTenure = mapPosition.find((curr: any) => curr.positionName == x.position); + if (currentTenure) { + const mapData: any = { + profileId: x.id, + positionName: currentTenure.positionName, + year: currentTenure.year, + month: currentTenure.month, + day: currentTenure.day, + }; + data.push(mapData); + } } await this.positionOfficerRepo.save(data); @@ -128,33 +123,28 @@ export class ProfileSalaryController extends Controller { _currentDate, ]); const _position = position.length > 0 ? position[0] : []; + // Filter for current position and use SP's calculated values (calendar arithmetic) const mapPosition = _position.length > 1 ? _position.slice(1).map((curr: any, index: number) => ({ - days_diff: curr.days_diff, positionName: _position[index]?.positionName, + // 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, })) : []; - 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); + const currentTenure = mapPosition.find((curr: any) => curr.positionName == x.position); + if (currentTenure) { + const mapData: any = { + profileEmployeeId: x.id, + positionName: currentTenure.positionName, + year: currentTenure.year, + month: currentTenure.month, + day: currentTenure.day, + }; + data.push(mapData); + } } await this.positionEmployeeRepo.save(data); @@ -203,16 +193,17 @@ export class ProfileSalaryController extends Controller { }, { days_diff: 0, positionType: null, positionLevel: null, positionCee: null }, ); - const { year, month, day } = calculateTenure(calDayDiff.days_diff); + const years = calDayDiff.days_diff / 365; + const normalized = normalizeDurationSumSimple(years, 0, 0); const mapData: any = { profileId: x.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: x.posLevel == null ? 0 : normalized.years.toFixed(4), + Months: x.posLevel == null ? 0 : normalized.months.toFixed(4), + Days: x.posLevel == null ? 0 : normalized.days.toFixed(4), }; data.push(mapData); } @@ -263,16 +254,17 @@ export class ProfileSalaryController extends Controller { }, { days_diff: 0, positionType: null, positionLevel: null, positionCee: null }, ); - const { year, month, day } = calculateTenure(calDayDiff.days_diff); + const years = calDayDiff.days_diff / 365; + const normalized = normalizeDurationSumSimple(years, 0, 0); const mapData: any = { profileEmployeeId: x.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: x.posLevel == null ? 0 : normalized.years.toFixed(4), + Months: x.posLevel == null ? 0 : normalized.months.toFixed(4), + Days: x.posLevel == null ? 0 : normalized.days.toFixed(4), }; data.push(mapData); } @@ -337,14 +329,15 @@ export class ProfileSalaryController extends Controller { }, { days_diff: 0, positionExecutive: null }, ); - const { year, month, day } = calculateTenure(calDayDiff.days_diff); + const years = calDayDiff.days_diff / 365; + const normalized = normalizeDurationSumSimple(years, 0, 0); const mapData: any = { profileId: x.id, positionExecutiveName: calDayDiff.positionExecutive, days_diff: calDayDiff.days_diff, - Years: year.toFixed(4), - Months: month.toFixed(4), - Days: day.toFixed(4), + Years: normalized.years.toFixed(4), + Months: normalized.months.toFixed(4), + Days: normalized.days.toFixed(4), }; data.push(mapData); } @@ -617,15 +610,15 @@ export class ProfileSalaryController extends Controller { 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; }, @@ -668,15 +661,15 @@ export class ProfileSalaryController extends Controller { 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; }, @@ -714,15 +707,15 @@ export class ProfileSalaryController extends Controller { 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; }, @@ -787,15 +780,15 @@ export class ProfileSalaryController extends Controller { 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; }, @@ -839,15 +832,15 @@ export class ProfileSalaryController extends Controller { 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; }, @@ -885,15 +878,15 @@ export class ProfileSalaryController extends Controller { 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/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) }; } From 5ea111a3c5b1b0a5d5a0b1c1b02a57b20b5079ac Mon Sep 17 00:00:00 2001 From: waruneeauy Date: Mon, 18 May 2026 21:51:23 +0700 Subject: [PATCH 12/83] fixed cron job --- src/controllers/ProfileSalaryController.ts | 280 +++++++++++++++------ 1 file changed, 202 insertions(+), 78 deletions(-) diff --git a/src/controllers/ProfileSalaryController.ts b/src/controllers/ProfileSalaryController.ts index ce477490..cd8e2948 100644 --- a/src/controllers/ProfileSalaryController.ts +++ b/src/controllers/ProfileSalaryController.ts @@ -84,19 +84,31 @@ export class ProfileSalaryController extends Controller { ? _position.slice(1).map((curr: any, index: number) => ({ positionName: _position[index]?.positionName, // 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, + 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 currentTenure = mapPosition.find((curr: any) => curr.positionName == x.position); if (currentTenure) { + const normalized = normalizeDurationSumSimple( + currentTenure.year, + currentTenure.month, + currentTenure.day, + ); const mapData: any = { profileId: x.id, positionName: currentTenure.positionName, - year: currentTenure.year, - month: currentTenure.month, - day: currentTenure.day, + Years: normalized.years, + Months: normalized.months, + Days: normalized.days, }; data.push(mapData); } @@ -129,19 +141,31 @@ export class ProfileSalaryController extends Controller { ? _position.slice(1).map((curr: any, index: number) => ({ positionName: _position[index]?.positionName, // 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, + 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 currentTenure = mapPosition.find((curr: any) => curr.positionName == x.position); if (currentTenure) { + const normalized = normalizeDurationSumSimple( + currentTenure.year, + currentTenure.month, + currentTenure.day, + ); const mapData: any = { profileEmployeeId: x.id, positionName: currentTenure.positionName, - year: currentTenure.year, - month: currentTenure.month, - day: currentTenure.day, + Years: normalized.years, + Months: normalized.months, + Days: normalized.days, }; data.push(mapData); } @@ -175,6 +199,16 @@ 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 @@ -189,21 +223,35 @@ 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 years = calDayDiff.days_diff / 365; - const normalized = normalizeDurationSumSimple(years, 0, 0); + const normalized = normalizeDurationSumSimple( + calDayDiff.year, + calDayDiff.month, + calDayDiff.day, + ); const mapData: any = { profileId: x.id, positionType: calDayDiff.positionType, positionLevel: calDayDiff.positionLevel, positionCee: calDayDiff.positionCee, days_diff: calDayDiff.days_diff, - Years: x.posLevel == null ? 0 : normalized.years.toFixed(4), - Months: x.posLevel == null ? 0 : normalized.months.toFixed(4), - Days: x.posLevel == null ? 0 : normalized.days.toFixed(4), + Years: x.posLevel == null ? 0 : normalized.years, + Months: x.posLevel == null ? 0 : normalized.months, + Days: x.posLevel == null ? 0 : normalized.days, }; data.push(mapData); } @@ -236,6 +284,16 @@ 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 @@ -250,21 +308,35 @@ 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 years = calDayDiff.days_diff / 365; - const normalized = normalizeDurationSumSimple(years, 0, 0); + const normalized = normalizeDurationSumSimple( + calDayDiff.year, + calDayDiff.month, + calDayDiff.day, + ); const mapData: any = { profileEmployeeId: x.id, positionType: calDayDiff.positionType, positionLevel: calDayDiff.positionLevel, positionCee: calDayDiff.positionCee, days_diff: calDayDiff.days_diff, - Years: x.posLevel == null ? 0 : normalized.years.toFixed(4), - Months: x.posLevel == null ? 0 : normalized.months.toFixed(4), - Days: x.posLevel == null ? 0 : normalized.days.toFixed(4), + Years: x.posLevel == null ? 0 : normalized.years, + Months: x.posLevel == null ? 0 : normalized.months, + Days: x.posLevel == null ? 0 : normalized.days, }; data.push(mapData); } @@ -316,6 +388,16 @@ export class ProfileSalaryController extends Controller { ? _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; @@ -325,19 +407,25 @@ export class ProfileSalaryController extends Controller { (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 years = calDayDiff.days_diff / 365; - const normalized = normalizeDurationSumSimple(years, 0, 0); + const normalized = normalizeDurationSumSimple( + calDayDiff.year, + calDayDiff.month, + calDayDiff.day, + ); const mapData: any = { profileId: x.id, positionExecutiveName: calDayDiff.positionExecutive, days_diff: calDayDiff.days_diff, - Years: normalized.years.toFixed(4), - Months: normalized.months.toFixed(4), - Days: normalized.days.toFixed(4), + Years: normalized.years, + Months: normalized.months, + Days: normalized.days, }; data.push(mapData); } @@ -589,8 +677,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, })) @@ -606,16 +698,18 @@ 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 using calendar arithmetic - const normalized = normalizeDurationSumSimple( - existing.year, - existing.month, - existing.day - ); + const normalized = normalizeDurationSumSimple(existing.year, existing.month, existing.day); existing.year = normalized.years; existing.month = normalized.months; existing.day = normalized.days; @@ -634,8 +728,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 @@ -657,16 +755,18 @@ 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 using calendar arithmetic - const normalized = normalizeDurationSumSimple( - existing.year, - existing.month, - existing.day - ); + const normalized = normalizeDurationSumSimple(existing.year, existing.month, existing.day); existing.year = normalized.years; existing.month = normalized.months; existing.day = normalized.days; @@ -686,8 +786,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, })) @@ -703,16 +807,18 @@ 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 using calendar arithmetic - const normalized = normalizeDurationSumSimple( - existing.year, - existing.month, - existing.day - ); + const normalized = normalizeDurationSumSimple(existing.year, existing.month, existing.day); existing.year = normalized.years; existing.month = normalized.months; existing.day = normalized.days; @@ -759,8 +865,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, })) @@ -776,16 +886,18 @@ 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 using calendar arithmetic - const normalized = normalizeDurationSumSimple( - existing.year, - existing.month, - existing.day - ); + const normalized = normalizeDurationSumSimple(existing.year, existing.month, existing.day); existing.year = normalized.years; existing.month = normalized.months; existing.day = normalized.days; @@ -805,8 +917,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 @@ -828,16 +944,18 @@ 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 using calendar arithmetic - const normalized = normalizeDurationSumSimple( - existing.year, - existing.month, - existing.day - ); + const normalized = normalizeDurationSumSimple(existing.year, existing.month, existing.day); existing.year = normalized.years; existing.month = normalized.months; existing.day = normalized.days; @@ -857,8 +975,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, })) @@ -874,16 +996,18 @@ 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 using calendar arithmetic - const normalized = normalizeDurationSumSimple( - existing.year, - existing.month, - existing.day - ); + const normalized = normalizeDurationSumSimple(existing.year, existing.month, existing.day); existing.year = normalized.years; existing.month = normalized.months; existing.day = normalized.days; From 6c5356ca4688b899737fa3233ad4608b7e1a5845 Mon Sep 17 00:00:00 2001 From: waruneeauy Date: Mon, 18 May 2026 23:25:09 +0700 Subject: [PATCH 13/83] fixed tenure --- src/controllers/ProfileSalaryController.ts | 504 +++++++++++++++------ src/entities/TenureLevelEmployee.ts | 2 +- 2 files changed, 379 insertions(+), 127 deletions(-) diff --git a/src/controllers/ProfileSalaryController.ts b/src/controllers/ProfileSalaryController.ts index cd8e2948..19a9f5e4 100644 --- a/src/controllers/ProfileSalaryController.ts +++ b/src/controllers/ProfileSalaryController.ts @@ -24,11 +24,20 @@ import { In, IsNull, LessThan, MoreThan, Not } from "typeorm"; import permission from "../interfaces/permission"; import { setLogDataDiff } from "../interfaces/utils"; import { normalizeDurationSumSimple } 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 { + 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,44 +55,84 @@ 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 = 100; + 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] : []; - // Filter for current position and use SP's calculated values (calendar arithmetic) + const mapPosition = _position.length > 1 ? _position.slice(1).map((curr: any, index: number) => ({ positionName: _position[index]?.positionName, - // Use stored procedure's calculated values (calendar arithmetic) year: curr.Years !== null && curr.Years !== undefined ? Math.floor(Number(curr.Years)) @@ -96,51 +145,102 @@ export class ProfileSalaryController extends Controller { curr.Days !== null && curr.Days !== undefined ? Math.floor(Number(curr.Days)) : 0, })) : []; - const currentTenure = mapPosition.find((curr: any) => curr.positionName == x.position); + + const currentTenure = mapPosition.find((curr: any) => curr.positionName === profile.position); + if (currentTenure) { const normalized = normalizeDurationSumSimple( currentTenure.year, currentTenure.month, currentTenure.day, ); - const mapData: any = { - profileId: x.id, + return { + profileId: profile.id, positionName: currentTenure.positionName, + days_diff: null, Years: normalized.years, Months: normalized.months, Days: normalized.days, }; - data.push(mapData); } + return null; + } catch (error) { + return null; } - await this.positionOfficerRepo.save(data); - - return new HttpSuccess(); } @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 = 100; + 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] : []; - // Filter for current position and use SP's calculated values (calendar arithmetic) + const mapPosition = _position.length > 1 ? _position.slice(1).map((curr: any, index: number) => ({ positionName: _position[index]?.positionName, - // Use stored procedure's calculated values (calendar arithmetic) year: curr.Years !== null && curr.Years !== undefined ? Math.floor(Number(curr.Years)) @@ -153,45 +253,105 @@ export class ProfileSalaryController extends Controller { curr.Days !== null && curr.Days !== undefined ? Math.floor(Number(curr.Days)) : 0, })) : []; - const currentTenure = mapPosition.find((curr: any) => curr.positionName == x.position); + + const currentTenure = mapPosition.find((curr: any) => curr.positionName === profile.position); + if (currentTenure) { const normalized = normalizeDurationSumSimple( currentTenure.year, currentTenure.month, currentTenure.day, ); - const mapData: any = { - profileEmployeeId: x.id, + return { + profileEmployeeId: profile.id, positionName: currentTenure.positionName, + days_diff: null, Years: normalized.years, Months: normalized.months, Days: normalized.days, }; - data.push(mapData); } + return null; + } catch (error) { + return null; } - await this.positionEmployeeRepo.save(data); - - return new HttpSuccess(); } @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 = 100; + 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) => ({ @@ -211,11 +371,12 @@ export class ProfileSalaryController extends Controller { 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) => { @@ -238,45 +399,103 @@ export class ProfileSalaryController extends Controller { day: 0, }, ); + const normalized = normalizeDurationSumSimple( calDayDiff.year, calDayDiff.month, calDayDiff.day, ); - const mapData: any = { - profileId: x.id, + + return { + profileId: profile.id, positionType: calDayDiff.positionType, positionLevel: calDayDiff.positionLevel, positionCee: calDayDiff.positionCee, days_diff: calDayDiff.days_diff, - Years: x.posLevel == null ? 0 : normalized.years, - Months: x.posLevel == null ? 0 : normalized.months, - Days: x.posLevel == null ? 0 : normalized.days, + 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 = 100; + 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) => ({ @@ -296,11 +515,12 @@ export class ProfileSalaryController extends Controller { 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) => { @@ -323,66 +543,97 @@ export class ProfileSalaryController extends Controller { day: 0, }, ); + const normalized = normalizeDurationSumSimple( calDayDiff.year, calDayDiff.month, calDayDiff.day, ); - const mapData: any = { - profileEmployeeId: x.id, + + return { + profileEmployeeId: profile.id, positionType: calDayDiff.positionType, positionLevel: calDayDiff.positionLevel, positionCee: calDayDiff.positionCee, days_diff: calDayDiff.days_diff, - Years: x.posLevel == null ? 0 : normalized.years, - Months: x.posLevel == null ? 0 : normalized.months, - Days: x.posLevel == null ? 0 : normalized.days, + 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 = 100; + 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) => ({ @@ -400,9 +651,9 @@ export class ProfileSalaryController extends Controller { 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; @@ -414,23 +665,24 @@ export class ProfileSalaryController extends Controller { }, { days_diff: 0, positionExecutive: null, year: 0, month: 0, day: 0 }, ); + const normalized = normalizeDurationSumSimple( calDayDiff.year, calDayDiff.month, calDayDiff.day, ); - const mapData: any = { - profileId: x.id, + + return { + profileId: profile.id, positionExecutiveName: calDayDiff.positionExecutive, days_diff: calDayDiff.days_diff, 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") 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; From b7f7b907bf96145bab53eb088a106157c510cbe7 Mon Sep 17 00:00:00 2001 From: waruneeauy Date: Tue, 19 May 2026 06:58:21 +0700 Subject: [PATCH 14/83] fix executive store procedure --- .../fix_GetProfileSalaryExecutive_calendar_arithmetic.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/migrations/fix_GetProfileSalaryExecutive_calendar_arithmetic.sql b/docs/migrations/fix_GetProfileSalaryExecutive_calendar_arithmetic.sql index 07d85ee8..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 *, From a8f7554302bbf862d88e67d0b930cb3da0dda1b4 Mon Sep 17 00:00:00 2001 From: harid Date: Tue, 19 May 2026 10:33:50 +0700 Subject: [PATCH 15/83] test --- src/controllers/CommandController.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/controllers/CommandController.ts b/src/controllers/CommandController.ts index fcadf77c..7bac242a 100644 --- a/src/controllers/CommandController.ts +++ b/src/controllers/CommandController.ts @@ -3776,7 +3776,7 @@ export class CommandController extends Controller { public async newSalaryEmployeeAndUpdateCurrent( @Request() req: RequestWithUser, @Body() - body: { + body: { data: { profileId: string; amount?: Double | null; From 458c9b104284c89c583283277b0f962a9dc34512 Mon Sep 17 00:00:00 2001 From: harid Date: Tue, 19 May 2026 16:23:29 +0700 Subject: [PATCH 16/83] =?UTF-8?q?fix=20=E0=B9=80=E0=B8=A1=E0=B8=99?= =?UTF-8?q?=E0=B8=B9=E0=B8=88=E0=B8=B1=E0=B8=94=E0=B8=81=E0=B8=B2=E0=B8=A3?= =?UTF-8?q?=E0=B8=9C=E0=B8=B9=E0=B9=89=E0=B9=83=E0=B8=8A=E0=B9=89=E0=B8=87?= =?UTF-8?q?=E0=B8=B2=E0=B8=99=20#2471?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/controllers/UserController.ts | 30 ++++++++++++++++++++++++------ 1 file changed, 24 insertions(+), 6 deletions(-) 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}%`, + } ); }), ) From e04d1ad7d3c44aa9b59f1cf478d55403b4aeaafa Mon Sep 17 00:00:00 2001 From: harid Date: Wed, 20 May 2026 10:57:18 +0700 Subject: [PATCH 17/83] =?UTF-8?q?Migrate=20+=20API=20=E0=B8=94=E0=B8=B6?= =?UTF-8?q?=E0=B8=87=E0=B8=A3=E0=B8=B2=E0=B8=A2=E0=B8=8A=E0=B8=B7=E0=B9=88?= =?UTF-8?q?=E0=B8=AD=E0=B8=A5=E0=B8=B9=E0=B8=81=E0=B8=88=E0=B9=89=E0=B8=B2?= =?UTF-8?q?=E0=B8=87=E0=B8=9B=E0=B8=A3=E0=B8=B0=E0=B8=88=E0=B8=B3=E0=B8=95?= =?UTF-8?q?=E0=B8=B2=E0=B8=A1=E0=B8=9B=E0=B8=A3=E0=B8=B0=E0=B8=A7=E0=B8=B1?= =?UTF-8?q?=E0=B8=95=E0=B8=B4=E0=B8=95=E0=B8=B3=E0=B9=81=E0=B8=AB=E0=B8=99?= =?UTF-8?q?=E0=B9=88=E0=B8=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../OrganizationDotnetController.ts | 427 +++++++++--------- src/entities/PosMasterEmployeeHistory.ts | 84 ++-- ...44154610-update_posMasterEmpHis_add_dna.ts | 23 + src/services/PositionService.ts | 6 + 4 files changed, 280 insertions(+), 260 deletions(-) create mode 100644 src/migration/1779244154610-update_posMasterEmpHis_add_dna.ts diff --git a/src/controllers/OrganizationDotnetController.ts b/src/controllers/OrganizationDotnetController.ts index b26cf73c..91e5c9d2 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"; @@ -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); @@ -6922,229 +6924,218 @@ 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") + 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.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), + }, + order: { + firstName: "ASC", + lastName: "ASC", + createdAt: "DESC", // ให้ createdAt ล่าสุดอยู่ข้างบน + }, + }); - // // 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); - // } - // } - // } + // group by ancestorDNA แล้วเลือก create_at ล่าสุด + const grouped = new Map(); + for (const item of posEmpHis) { + 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); + } + } + } - // 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 profile_ = await Promise.all( + Array.from(grouped.values()) + .filter((x) => x.profileEmployeeId != null) + .map(async (item: PosMasterEmployeeHistory) => { + let profileEmp = await this.profileEmpRepo.findOne({ + where: { id: item.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, - // }; - // }), - // ); + return { + id: profileEmp?.id, + prefix: profileEmp?.prefix, + firstName: profileEmp?.firstName, + lastName: profileEmp?.lastName, + citizenId: profileEmp?.citizenId ?? null, + dateStart: profileEmp?.dateStart ?? null, + dateAppoint: profileEmp?.dateAppoint ?? null, + keycloak: profileEmp?.keycloak ?? null, + posNo: item.shortName, + 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_); - // } + return new HttpSuccess(profile_); + } /** * 4. API Update รอบการลงเวลา ในตาราง profile 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/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/services/PositionService.ts b/src/services/PositionService.ts index b1837b99..29bb168a 100644 --- a/src/services/PositionService.ts +++ b/src/services/PositionService.ts @@ -230,6 +230,7 @@ export async function CreatePosMasterHistoryEmployee( : 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; @@ -237,6 +238,11 @@ export async function CreatePosMasterHistoryEmployee( h.posType = selectedPosition?.posType?.posTypeName ?? _null; h.posLevel = selectedPosition?.posLevel?.posLevelName ?? _null; } + 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; From 0e80acfb1b4ecee71e80856f6b17619b2b2b8f66 Mon Sep 17 00:00:00 2001 From: waruneeauy Date: Wed, 20 May 2026 11:26:27 +0700 Subject: [PATCH 18/83] =?UTF-8?q?=E0=B8=A5=E0=B8=9A=20log=20=E0=B8=AD?= =?UTF-8?q?=E0=B8=AD=E0=B8=81=E0=B9=80=E0=B8=9E=E0=B8=B7=E0=B9=88=E0=B8=AD?= =?UTF-8?q?=E0=B9=83=E0=B8=AB=E0=B9=89=E0=B8=AA=E0=B8=B2=E0=B8=A1=E0=B8=B2?= =?UTF-8?q?=E0=B8=A3=E0=B8=96=E0=B8=94=E0=B8=B9=20log=20=E0=B8=AD=E0=B8=B7?= =?UTF-8?q?=E0=B9=88=E0=B8=99=E0=B9=86=20=E0=B9=84=E0=B8=94=E0=B9=89?= =?UTF-8?q?=E0=B8=87=E0=B9=88=E0=B8=B2=E0=B8=A2=E0=B8=82=E0=B8=B6=E0=B9=89?= =?UTF-8?q?=E0=B8=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/services/webSocket.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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; From 5dafd131245659db952343ad819fa65aa01b26b8 Mon Sep 17 00:00:00 2001 From: harid Date: Wed, 20 May 2026 14:35:37 +0700 Subject: [PATCH 19/83] =?UTF-8?q?API=20=E0=B8=94=E0=B8=B6=E0=B8=87?= =?UTF-8?q?=E0=B8=A3=E0=B8=B2=E0=B8=A2=E0=B8=8A=E0=B8=B7=E0=B9=88=E0=B8=AD?= =?UTF-8?q?=E0=B8=A5=E0=B8=B9=E0=B8=81=E0=B8=88=E0=B9=89=E0=B8=B2=E0=B8=87?= =?UTF-8?q?=E0=B8=9B=E0=B8=A3=E0=B8=B0=E0=B8=88=E0=B8=B3=E0=B8=95=E0=B8=B2?= =?UTF-8?q?=E0=B8=A1=E0=B8=9B=E0=B8=A3=E0=B8=B0=E0=B8=A7=E0=B8=B1=E0=B8=95?= =?UTF-8?q?=E0=B8=B4=E0=B8=95=E0=B8=B3=E0=B9=81=E0=B8=AB=E0=B8=99=E0=B9=88?= =?UTF-8?q?=E0=B8=87=20#2385?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../OrganizationDotnetController.ts | 131 +++++++++++++----- 1 file changed, 93 insertions(+), 38 deletions(-) diff --git a/src/controllers/OrganizationDotnetController.ts b/src/controllers/OrganizationDotnetController.ts index 91e5c9d2..fce9bb98 100644 --- a/src/controllers/OrganizationDotnetController.ts +++ b/src/controllers/OrganizationDotnetController.ts @@ -7075,66 +7075,121 @@ export class OrganizationDotnetController extends Controller { } } // set เวลาเป็น 23:59:59 ของวันนั้น - const date = body.date.setHours(23, 59, 59, 999); + const date = body.date ? new Date(body.date.toISOString().slice(0, 10)) : new Date(); + date.setHours(23, 59, 59, 999); 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", // ให้ createdAt ล่าสุดอยู่ข้างบน + createdAt: "DESC", }, }); - // group by ancestorDNA แล้วเลือก create_at ล่าสุด - const grouped = new Map(); + // group1: group by ancestorDNA แล้วเลือก create_at ล่าสุด + const grouped1 = new Map(); for (const item of posEmpHis) { - const key = `${item.shortName}-${item.posMasterNo}`; - if (!grouped.has(key)) { - grouped.set(key, item); + const key = `${item.ancestorDNA}`; + if (!grouped1.has(key)) { + grouped1.set(key, item); } else { // ถ้าเจอซ้ำ ให้เลือก createdAt ล่าสุด - const exist = grouped.get(key); + const exist = grouped1.get(key); if (exist && item.createdAt > exist.createdAt) { - grouped.set(key, item); + 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.profileEmployeeId != null) - .map(async (item: PosMasterEmployeeHistory) => { - let profileEmp = await this.profileEmpRepo.findOne({ - where: { id: item.profileEmployeeId }, - }); + const profileEmployeeIds = Array.from(grouped3.values()) + .filter((x) => x.profileEmployeeId != null) + .map((x) => x.profileEmployeeId); - return { - id: profileEmp?.id, - prefix: profileEmp?.prefix, - firstName: profileEmp?.firstName, - lastName: profileEmp?.lastName, - citizenId: profileEmp?.citizenId ?? null, - dateStart: profileEmp?.dateStart ?? null, - dateAppoint: profileEmp?.dateAppoint ?? null, - keycloak: profileEmp?.keycloak ?? null, - posNo: item.shortName, - position: item.position, - positionLevel: item.posLevel, - positionType: item.posType, - 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"], + }); + + 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 })), ); - - return new HttpSuccess(profile_); } /** From ddf0309b0b05ee5f59b8551a84b329b4b7bcdcab Mon Sep 17 00:00:00 2001 From: harid Date: Wed, 20 May 2026 15:53:10 +0700 Subject: [PATCH 20/83] =?UTF-8?q?fix=20=E0=B8=AA=E0=B9=88=E0=B8=87?= =?UTF-8?q?=E0=B8=A3=E0=B8=B2=E0=B8=A2=E0=B8=8A=E0=B8=B7=E0=B9=88=E0=B8=AD?= =?UTF-8?q?=E0=B8=9A=E0=B8=A3=E0=B8=A3=E0=B8=88=E0=B8=B8=20=E0=B8=84?= =?UTF-8?q?=E0=B8=B3=E0=B8=99=E0=B8=B3=E0=B8=AB=E0=B8=99=E0=B9=89=E0=B8=B2?= =?UTF-8?q?=E0=B9=84=E0=B8=A1=E0=B9=88=E0=B9=81=E0=B8=AA=E0=B8=94=E0=B8=87?= =?UTF-8?q?=20#2506?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/controllers/CommandController.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/controllers/CommandController.ts b/src/controllers/CommandController.ts index 7bac242a..59d75312 100644 --- a/src/controllers/CommandController.ts +++ b/src/controllers/CommandController.ts @@ -6594,8 +6594,8 @@ export class CommandController extends Controller { 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.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; @@ -6658,8 +6658,8 @@ export class CommandController extends Controller { 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.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; @@ -6717,8 +6717,8 @@ export class CommandController extends Controller { profile.lastUpdateFullName = req.user.name; profile.lastUpdatedAt = new Date(); //เพิ่มใหม่จากรับโอน - profile.rank = item?.bodyProfile?.rank ?? null; - profile.prefix = item?.bodyProfile?.rank ?? item?.bodyProfile?.prefix ?? null; + 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 != "" From d0c5d90033e954c9689a46854d535e4554dad854 Mon Sep 17 00:00:00 2001 From: Adisak Date: Wed, 20 May 2026 16:27:39 +0700 Subject: [PATCH 21/83] #2508 --- src/controllers/OrganizationController.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/controllers/OrganizationController.ts b/src/controllers/OrganizationController.ts index eb91c408..960b4815 100644 --- a/src/controllers/OrganizationController.ts +++ b/src/controllers/OrganizationController.ts @@ -5807,6 +5807,7 @@ 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; @@ -5847,6 +5848,7 @@ export class OrganizationController extends Controller { .leftJoin("orgChild1.posMasters", "posMasters") .leftJoin("posMasters.current_holder", "current_holder") .orderBy("orgChild1.orgChild1Order", "ASC") + .addOrderBy("posMasters.posMasterOrder", "ASC") .getMany() : []; @@ -5888,6 +5890,7 @@ export class OrganizationController extends Controller { .leftJoin("orgChild2.posMasters", "posMasters") .leftJoin("posMasters.current_holder", "current_holder") .orderBy("orgChild2.orgChild2Order", "ASC") + .addOrderBy("posMasters.posMasterOrder", "ASC") .getMany() : []; @@ -5929,6 +5932,7 @@ export class OrganizationController extends Controller { .leftJoin("orgChild3.posMasters", "posMasters") .leftJoin("posMasters.current_holder", "current_holder") .orderBy("orgChild3.orgChild3Order", "ASC") + .addOrderBy("posMasters.posMasterOrder", "ASC") .getMany() : []; @@ -5965,6 +5969,7 @@ export class OrganizationController extends Controller { .leftJoin("orgChild4.posMasters", "posMasters") .leftJoin("posMasters.current_holder", "current_holder") .orderBy("orgChild4.orgChild4Order", "ASC") + .addOrderBy("posMasters.posMasterOrder", "ASC") .getMany() : []; From 300f0736385ceeb6fb270275208367b1bc5ad63c Mon Sep 17 00:00:00 2001 From: waruneeauy Date: Wed, 20 May 2026 17:12:16 +0700 Subject: [PATCH 22/83] fixed bug Redis Client Connection Leak --- src/controllers/AuthRoleController.ts | 48 ++++++---- src/controllers/PermissionController.ts | 119 +++++++++++++++++------- 2 files changed, 115 insertions(+), 52 deletions(-) diff --git a/src/controllers/AuthRoleController.ts b/src/controllers/AuthRoleController.ts index 4159c5ec..2e51a79d 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, response: Response) => { + if (err) throw err; + }); - redisClient.del("menu_" + posMaster.current_holderId, (err: Error, response: Response) => { - if (err) throw err; - }); + redisClient.del("menu_" + posMaster.current_holderId, (err: Error, response: Response) => { + if (err) throw err; + }); + } finally { + if (redisClient) { + redisClient.quit(); + } + } } return new HttpSuccess(); @@ -266,14 +273,21 @@ export class AuthRoleController extends Controller { ...newAttrs.map((attr) => this.authRoleAttrRepo.save(attr)), ]); - 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, + }); - await redisClient.flushdb(function (err: any, succeeded: any) { - console.log(succeeded); // will be true if successfull - }); + 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/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(); + } + } } } From 36b14690161665860c1fc5948591fa8d6de7beed Mon Sep 17 00:00:00 2001 From: Adisak Date: Wed, 20 May 2026 17:33:02 +0700 Subject: [PATCH 23/83] fix api sort 504 time out --- src/controllers/PositionController.ts | 70 +++++++++++++++++++++++++-- 1 file changed, 65 insertions(+), 5 deletions(-) diff --git a/src/controllers/PositionController.ts b/src/controllers/PositionController.ts index 98120f30..7973f794 100644 --- a/src/controllers/PositionController.ts +++ b/src/controllers/PositionController.ts @@ -2760,7 +2760,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 +2801,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 +2842,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 +2883,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 +2924,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; } From 9a5184bb55417928ac50704f00951763f105e2c4 Mon Sep 17 00:00:00 2001 From: waruneeauy Date: Wed, 20 May 2026 17:51:15 +0700 Subject: [PATCH 24/83] add script delete org old --- src/scripts/ClearOldOrgRevision.ts | 27 +++ src/services/ClearOldOrgRevisionService.ts | 232 +++++++++++++++++++++ 2 files changed, 259 insertions(+) create mode 100644 src/scripts/ClearOldOrgRevision.ts create mode 100644 src/services/ClearOldOrgRevisionService.ts 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(); +} From bca04f2881e0b32e32ad3f5e35f970d345c5fe59 Mon Sep 17 00:00:00 2001 From: waruneeauy Date: Wed, 20 May 2026 20:23:41 +0700 Subject: [PATCH 25/83] fixed Promise.all Without Error Handling --- src/controllers/AuthRoleController.ts | 28 ++++++++++++++++++++++----- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/src/controllers/AuthRoleController.ts b/src/controllers/AuthRoleController.ts index 2e51a79d..7cbc387d 100644 --- a/src/controllers/AuthRoleController.ts +++ b/src/controllers/AuthRoleController.ts @@ -267,11 +267,29 @@ 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 queryRunner = AppDataSource.createQueryRunner(); + await queryRunner.connect(); + await queryRunner.startTransaction(); + + 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 { From e374f2e339131deb6e523747bfbae50fb36d2fc3 Mon Sep 17 00:00:00 2001 From: waruneeauy Date: Wed, 20 May 2026 20:40:09 +0700 Subject: [PATCH 26/83] fixed service crash --- src/controllers/AuthRoleController.ts | 8 +-- src/controllers/CommandOperatorController.ts | 51 +++++++------------- src/controllers/OrganizationController.ts | 2 + src/services/PositionService.ts | 1 + 4 files changed, 25 insertions(+), 37 deletions(-) diff --git a/src/controllers/AuthRoleController.ts b/src/controllers/AuthRoleController.ts index 7cbc387d..13f5ce04 100644 --- a/src/controllers/AuthRoleController.ts +++ b/src/controllers/AuthRoleController.ts @@ -130,12 +130,12 @@ export class AuthRoleController extends Controller { 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) { 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/OrganizationController.ts b/src/controllers/OrganizationController.ts index 960b4815..e107df5c 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; } } @@ -2529,6 +2530,7 @@ export class OrganizationController extends Controller { await sendToQueueOrg(msg); return new HttpSuccess(); } catch (error: any) { + console.error("Error publishing draft organization:", error); throw error; } } diff --git a/src/services/PositionService.ts b/src/services/PositionService.ts index 29bb168a..b6514eca 100644 --- a/src/services/PositionService.ts +++ b/src/services/PositionService.ts @@ -188,6 +188,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); From 12e8cdb0809a1ef8cb3a558456e991df93fd2540 Mon Sep 17 00:00:00 2001 From: waruneeauy Date: Thu, 21 May 2026 10:30:09 +0700 Subject: [PATCH 27/83] fixed bug sync org to leave service --- src/controllers/ScriptProfileOrgController.ts | 93 ++++++++++++++++--- 1 file changed, 78 insertions(+), 15 deletions(-) diff --git a/src/controllers/ScriptProfileOrgController.ts b/src/controllers/ScriptProfileOrgController.ts index aa6908e2..4881249e 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 @@ -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`, }); From 9fe91ce49c6de5a5e80e2f3b00b2ec56e90bd5fd Mon Sep 17 00:00:00 2001 From: waruneeauy Date: Thu, 21 May 2026 10:53:45 +0700 Subject: [PATCH 28/83] =?UTF-8?q?=E0=B8=95=E0=B8=B1=E0=B8=94=E0=B8=9F?= =?UTF-8?q?=E0=B8=B4=E0=B8=A5=E0=B8=94=E0=B9=8C=E0=B8=8B=E0=B9=89=E0=B8=B3?= =?UTF-8?q?=E0=B9=81=E0=B8=A5=E0=B8=B0=E0=B8=9F=E0=B8=B4=E0=B8=A5=E0=B8=94?= =?UTF-8?q?=E0=B9=8C=20id=20=E0=B8=AD=E0=B8=AD=E0=B8=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/controllers/ApiManageController.ts | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/src/controllers/ApiManageController.ts b/src/controllers/ApiManageController.ts index 01a27eb5..8caad7fd 100644 --- a/src/controllers/ApiManageController.ts +++ b/src/controllers/ApiManageController.ts @@ -325,7 +325,28 @@ export class ApiManageController extends Controller { ]; 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", + "ancestorDNA", + "keycloak", + "commandId", + ]; // ฟิลด์ที่ไม่ต้องการแสดงในผลลัพธ์ private validateSuperAdminRole(user: any): void { if (!user.role.includes("SUPER_ADMIN")) { From b2d59ef6980779678845da34e43315ffe18105b5 Mon Sep 17 00:00:00 2001 From: waruneeauy Date: Thu, 21 May 2026 11:07:15 +0700 Subject: [PATCH 29/83] =?UTF-8?q?=E0=B9=81=E0=B8=81=E0=B9=89=E0=B9=84?= =?UTF-8?q?=E0=B8=82=E0=B8=9B=E0=B8=A3=E0=B8=B0=E0=B9=80=E0=B8=A0=E0=B8=97?= =?UTF-8?q?=20=E0=B8=A3=E0=B8=B0=E0=B8=94=E0=B8=B1=E0=B8=9A=E0=B8=95?= =?UTF-8?q?=E0=B8=B3=E0=B9=81=E0=B8=AB=E0=B8=99=E0=B9=88=E0=B8=87=20?= =?UTF-8?q?=E0=B9=81=E0=B8=A5=E0=B8=B0=E0=B8=88=E0=B8=B1=E0=B8=87=E0=B8=AB?= =?UTF-8?q?=E0=B8=A7=E0=B8=B1=E0=B8=94=20=E0=B8=AD=E0=B8=B3=E0=B9=80?= =?UTF-8?q?=E0=B8=A0=E0=B8=AD=20=E0=B8=95=E0=B8=B3=E0=B8=9A=E0=B8=A5?= =?UTF-8?q?=E0=B9=83=E0=B8=AB=E0=B9=89=E0=B9=81=E0=B8=AA=E0=B8=94=E0=B8=87?= =?UTF-8?q?=E0=B8=9F=E0=B8=B4=E0=B8=A5=E0=B8=94=E0=B9=8C=E0=B8=97=E0=B8=B5?= =?UTF-8?q?=E0=B9=88=E0=B9=80=E0=B8=9B=E0=B9=87=E0=B8=99=E0=B8=8A=E0=B8=B7?= =?UTF-8?q?=E0=B9=88=E0=B8=AD=E0=B9=83=E0=B8=AB=E0=B9=89=E0=B9=80=E0=B8=A5?= =?UTF-8?q?=E0=B8=B7=E0=B8=AD=E0=B8=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/controllers/ApiManageController.ts | 101 +++++++++++++++++++-- src/controllers/ApiWebServiceController.ts | 91 ++++++++++++++++++- 2 files changed, 182 insertions(+), 10 deletions(-) diff --git a/src/controllers/ApiManageController.ts b/src/controllers/ApiManageController.ts index 8caad7fd..22db1c7d 100644 --- a/src/controllers/ApiManageController.ts +++ b/src/controllers/ApiManageController.ts @@ -348,6 +348,69 @@ export class ApiManageController extends Controller { "commandId", ]; // ฟิลด์ที่ไม่ต้องการแสดงในผลลัพธ์ + // การแทนที่ฟิลด์ 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", + }, + }; + private validateSuperAdminRole(user: any): void { if (!user.role.includes("SUPER_ADMIN")) { throw new HttpError(HttpStatusCode.FORBIDDEN, "คุณไม่มีสิทธิ์ในการเข้าถึงข้อมูลนี้"); @@ -385,11 +448,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), @@ -399,8 +459,35 @@ 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]; + } + + 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..8a1e87be 100644 --- a/src/controllers/ApiWebServiceController.ts +++ b/src/controllers/ApiWebServiceController.ts @@ -21,6 +21,53 @@ export class ApiWebServiceController extends Controller { private orgRevisionRepository = AppDataSource.getRepository(OrgRevision); private apiHistoryRepository = AppDataSource.getRepository(ApiHistory); + // การแทนที่ฟิลด์ 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", + }, + }; + /** * list fields by systems * @summary รายการ fields ตาม systems @@ -50,7 +97,7 @@ 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}`); let tbMain: string = ""; let condition: string = "1=1"; @@ -92,6 +139,23 @@ export class ApiWebServiceController extends Controller { ...new Set(propertyKey.map((x) => x.split(".")[0]).filter((tb) => tb !== tbMain)), ]; + // สำหรับ 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; + }); + } + const queryBuilder = repo.createQueryBuilder(tbMain); // join กับตารารอง @@ -107,6 +171,13 @@ export class ApiWebServiceController extends Controller { }); } + // join สำหรับฟิลด์ Profile ที่ต้องการดึงค่าจากตารางอื่น + if (tbMain === "Profile" && Object.keys(profileFieldJoins).length > 0) { + Object.entries(profileFieldJoins).forEach(([alias, relationName]) => { + queryBuilder.leftJoin(`${tbMain}.${relationName}`, alias); + }); + } + // // เพิ่ม Main.id เพราะจะใช้ pk ในการแมบและนับจำนวน // if (!propertyKey.includes(`${Main}.id`)) { // propertyKey.push(`${Main}.id`); @@ -141,8 +212,22 @@ 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]; + } + }); + return flattened; + } + + return rest; }); // console.log("queryBuilder ===> ", queryBuilder.getQuery()); From 168abb1255a64c868ffa9478a0012850a8f130f9 Mon Sep 17 00:00:00 2001 From: Adisak Date: Thu, 21 May 2026 11:26:29 +0700 Subject: [PATCH 30/83] =?UTF-8?q?=E0=B9=81=E0=B8=81=E0=B9=89=20format=20?= =?UTF-8?q?=E0=B8=9F=E0=B8=B4=E0=B8=A7=20posMasterNo=20(1)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/controllers/PositionController.ts | 20 +++++------ src/controllers/ProfileController.ts | 49 ++++++++++++++------------- src/utils/org-formatting.ts | 4 +-- 3 files changed, 37 insertions(+), 36 deletions(-) diff --git a/src/controllers/PositionController.ts b/src/controllers/PositionController.ts index 7973f794..efcd1497 100644 --- a/src/controllers/PositionController.ts +++ b/src/controllers/PositionController.ts @@ -2154,11 +2154,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,posMaster.posMasterNoPrefix,posMaster.posMasterNo,posMaster.posMasterNoSuffix)`; + let searchShortName1 = `CONCAT_WS(" ",orgChild1.orgChild1ShortName,posMaster.posMasterNoPrefix,posMaster.posMasterNo,posMaster.posMasterNoSuffix)`; + let searchShortName2 = `CONCAT_WS(" ",orgChild2.orgChild2ShortName,posMaster.posMasterNoPrefix,posMaster.posMasterNo,posMaster.posMasterNoSuffix)`; + let searchShortName3 = `CONCAT_WS(" ",orgChild3.orgChild3ShortName,posMaster.posMasterNoPrefix,posMaster.posMasterNo,posMaster.posMasterNoSuffix)`; + let searchShortName4 = `CONCAT_WS(" ",orgChild4.orgChild4ShortName,posMaster.posMasterNoPrefix,posMaster.posMasterNo,posMaster.posMasterNoSuffix)`; let _data = await new permission().PermissionOrgList(request, "SYS_ORG"); if (body.type === 0) { typeCondition = { @@ -2168,7 +2168,7 @@ export class PositionController extends Controller { checkChildConditions = { orgChild1Id: IsNull(), }; - searchShortName = `CONCAT(orgRoot.orgRootShortName," ",posMaster.posMasterNo) like '%${body.keyword}%'`; + searchShortName = `CONCAT_WS(" ",orgRoot.orgRootShortName,posMaster.posMasterNoPrefix,posMaster.posMasterNo,posMaster.posMasterNoSuffix) like '%${body.keyword}%'`; } } else if (body.type === 1) { typeCondition = { @@ -2178,7 +2178,7 @@ export class PositionController extends Controller { checkChildConditions = { orgChild2Id: IsNull(), }; - searchShortName = `CONCAT(orgChild1.orgChild1ShortName," ",posMaster.posMasterNo) like '%${body.keyword}%'`; + searchShortName = `CONCAT_WS(" ",orgChild1.orgChild1ShortName,posMaster.posMasterNoPrefix,posMaster.posMasterNo,posMaster.posMasterNoSuffix) like '%${body.keyword}%'`; } } else if (body.type === 2) { typeCondition = { @@ -2188,7 +2188,7 @@ export class PositionController extends Controller { checkChildConditions = { orgChild3Id: IsNull(), }; - searchShortName = `CONCAT(orgChild2.orgChild2ShortName," ",posMaster.posMasterNo) like '%${body.keyword}%'`; + searchShortName = `CONCAT_WS(" ",orgChild2.orgChild2ShortName,posMaster.posMasterNoPrefix,posMaster.posMasterNo,posMaster.posMasterNoSuffix) like '%${body.keyword}%'`; } } else if (body.type === 3) { typeCondition = { @@ -2198,13 +2198,13 @@ export class PositionController extends Controller { checkChildConditions = { orgChild4Id: IsNull(), }; - searchShortName = `CONCAT(orgChild3.orgChild3ShortName," ",posMaster.posMasterNo) like '%${body.keyword}%'`; + searchShortName = `CONCAT_WS(" ",orgChild3.orgChild3ShortName,posMaster.posMasterNoPrefix,posMaster.posMasterNo,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,posMaster.posMasterNoPrefix,posMaster.posMasterNo,posMaster.posMasterNoSuffix) like '%${body.keyword}%'`; } let findPosition: any; let masterId = new Array(); diff --git a/src/controllers/ProfileController.ts b/src/controllers/ProfileController.ts index 3c514b6b..14dc725f 100644 --- a/src/controllers/ProfileController.ts +++ b/src/controllers/ProfileController.ts @@ -6025,12 +6025,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, current_holders.posMasterNoPrefix, current_holders.posMasterNo, current_holders.posMasterNoSuffix) + WHEN current_holders.orgChild3Id IS NOT NULL THEN CONCAT_WS(" ", orgChild3.orgChild3ShortName, current_holders.posMasterNoPrefix, current_holders.posMasterNo, current_holders.posMasterNoSuffix) + WHEN current_holders.orgChild2Id IS NOT NULL THEN CONCAT_WS(" ", orgChild2.orgChild2ShortName, current_holders.posMasterNoPrefix, current_holders.posMasterNo, current_holders.posMasterNoSuffix) + WHEN current_holders.orgChild1Id IS NOT NULL THEN CONCAT_WS(" ", orgChild1.orgChild1ShortName, current_holders.posMasterNoPrefix, current_holders.posMasterNo, current_holders.posMasterNoSuffix) + ELSE CONCAT_WS(" ", orgRoot.orgRootShortName, current_holders.posMasterNoPrefix, current_holders.posMasterNo, current_holders.posMasterNoSuffix) END LIKE :keyword `; } @@ -6615,12 +6615,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, current_holders.posMasterNoPrefix, current_holders.posMasterNo, current_holders.posMasterNoSuffix) + WHEN current_holders.orgChild3Id IS NOT NULL THEN CONCAT_WS(" ", orgChild3.orgChild3ShortName, current_holders.posMasterNoPrefix, current_holders.posMasterNo, current_holders.posMasterNoSuffix) + WHEN current_holders.orgChild2Id IS NOT NULL THEN CONCAT_WS(" ", orgChild2.orgChild2ShortName, current_holders.posMasterNoPrefix, current_holders.posMasterNo, current_holders.posMasterNoSuffix) + WHEN current_holders.orgChild1Id IS NOT NULL THEN CONCAT_WS(" ", orgChild1.orgChild1ShortName, current_holders.posMasterNoPrefix, current_holders.posMasterNo, current_holders.posMasterNoSuffix) + ELSE CONCAT_WS(" ", orgRoot.orgRootShortName, current_holders.posMasterNoPrefix, current_holders.posMasterNo, current_holders.posMasterNoSuffix) END LIKE :keyword `; } @@ -6803,18 +6803,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 +7009,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, current_holders.posMasterNoPrefix, current_holders.posMasterNo, current_holders.posMasterNoSuffix) + WHEN current_holders.orgChild3Id IS NOT NULL THEN CONCAT_WS(" ", orgChild3.orgChild3ShortName, current_holders.posMasterNoPrefix, current_holders.posMasterNo, current_holders.posMasterNoSuffix) + WHEN current_holders.orgChild2Id IS NOT NULL THEN CONCAT_WS(" ", orgChild2.orgChild2ShortName, current_holders.posMasterNoPrefix, current_holders.posMasterNo, current_holders.posMasterNoSuffix) + WHEN current_holders.orgChild1Id IS NOT NULL THEN CONCAT_WS(" ", orgChild1.orgChild1ShortName, current_holders.posMasterNoPrefix, current_holders.posMasterNo, current_holders.posMasterNoSuffix) + ELSE CONCAT_WS(" ", orgRoot.orgRootShortName, current_holders.posMasterNoPrefix, current_holders.posMasterNo, current_holders.posMasterNoSuffix) END LIKE :keyword `; } @@ -7190,7 +7191,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 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(' ')}`; } From b071bc2d924977c84d0f10965e14d7b20f989f7f Mon Sep 17 00:00:00 2001 From: waruneeauy Date: Thu, 21 May 2026 11:44:28 +0700 Subject: [PATCH 31/83] api service add filter by dnaId of Profile --- src/controllers/ApiWebServiceController.ts | 106 +++++++++++++++++++++ src/middlewares/authWebService.ts | 18 +++- src/middlewares/user.ts | 6 ++ 3 files changed, 129 insertions(+), 1 deletion(-) diff --git a/src/controllers/ApiWebServiceController.ts b/src/controllers/ApiWebServiceController.ts index 8a1e87be..7c09fe95 100644 --- a/src/controllers/ApiWebServiceController.ts +++ b/src/controllers/ApiWebServiceController.ts @@ -20,6 +20,7 @@ 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< @@ -68,6 +69,82 @@ export class ApiWebServiceController extends Controller { }, }; + /** + * 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; + }, + ): 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( + `posMaster.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( + `posMaster.orgChild4Id IN (SELECT id FROM orgChild4 WHERE orgRevisionId = "${this.currentRevisionId}" AND ancestorDNA = "${dnaIds.dnaChild4Id}")`, + ); + } else if (dnaIds.dnaChild3Id) { + conditions.push( + `posMaster.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( + `(posMaster.orgChild3Id IN (SELECT id FROM orgChild3 WHERE orgRevisionId = "${this.currentRevisionId}" AND ancestorDNA LIKE "${dnaIds.dnaChild3Id}%") OR posMaster.orgChild4Id IS NOT NULL)`, + ); + } + } else if (dnaIds.dnaChild2Id) { + conditions.push( + `posMaster.orgChild2Id IN (SELECT id FROM orgChild2 WHERE orgRevisionId = "${this.currentRevisionId}" AND ancestorDNA = "${dnaIds.dnaChild2Id}")`, + ); + if (accessType === "CHILD") { + conditions.push( + `(posMaster.orgChild2Id IN (SELECT id FROM orgChild2 WHERE orgRevisionId = "${this.currentRevisionId}" AND ancestorDNA LIKE "${dnaIds.dnaChild2Id}%") OR posMaster.orgChild3Id IS NOT NULL)`, + ); + } + } else if (dnaIds.dnaChild1Id) { + conditions.push( + `posMaster.orgChild1Id IN (SELECT id FROM orgChild1 WHERE orgRevisionId = "${this.currentRevisionId}" AND ancestorDNA = "${dnaIds.dnaChild1Id}")`, + ); + if (accessType === "CHILD") { + conditions.push( + `(posMaster.orgChild1Id IN (SELECT id FROM orgChild1 WHERE orgRevisionId = "${this.currentRevisionId}" AND ancestorDNA LIKE "${dnaIds.dnaChild1Id}%") OR posMaster.orgChild2Id IS NOT NULL)`, + ); + } + } else if (dnaIds.dnaRootId) { + conditions.push( + `posMaster.orgRootId IN (SELECT id FROM orgRoot WHERE orgRevisionId = "${this.currentRevisionId}" AND ancestorDNA = "${dnaIds.dnaRootId}")`, + ); + if (accessType === "CHILD") { + conditions.push( + `(posMaster.orgRootId IN (SELECT id FROM orgRoot WHERE orgRevisionId = "${this.currentRevisionId}" AND ancestorDNA LIKE "${dnaIds.dnaRootId}%") OR posMaster.orgChild1Id IS NOT NULL)`, + ); + } + } + } + + return conditions.length > 0 ? `(${conditions.join(" OR ")})` : "1=1"; + } + /** * list fields by systems * @summary รายการ fields ตาม systems @@ -125,6 +202,29 @@ export class ApiWebServiceController extends Controller { condition = `PosMaster.orgRevisionId = "${revision?.id}"`; } + let posMasterCondition: string = ""; + + // Special handling for Profile system 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 || ""; + + // 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, + }); + } + const repo = AppDataSource.getRepository(tbMain); const metadata = repo.metadata; @@ -178,6 +278,11 @@ export class ApiWebServiceController extends Controller { }); } + // join กับ posMaster สำหรับ Profile เพื่อกรองตามสิทธิ์การเข้าถึง + if (tbMain === "Profile" && posMasterCondition !== "1=1") { + queryBuilder.leftJoin("Profile.current_holders", "posMaster"); + } + // // เพิ่ม Main.id เพราะจะใช้ pk ในการแมบและนับจำนวน // if (!propertyKey.includes(`${Main}.id`)) { // propertyKey.push(`${Main}.id`); @@ -196,6 +301,7 @@ export class ApiWebServiceController extends Controller { const [items, total] = await queryBuilder .select(propertyKey) .where(condition) + .andWhere(posMasterCondition) .orderBy(propertyKey[0], "ASC") .skip(offset) .take(pageSize) 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; }; }; From 44793fbfbbfb3482b5acbbedae3f11e31e085957 Mon Sep 17 00:00:00 2001 From: waruneeauy Date: Thu, 21 May 2026 13:44:03 +0700 Subject: [PATCH 32/83] api web service add join for show name --- src/controllers/ApiManageController.ts | 75 ++++++- src/controllers/ApiWebServiceController.ts | 219 +++++++++++++++++++-- src/services/KeycloakAttributeService.ts | 27 ++- 3 files changed, 290 insertions(+), 31 deletions(-) diff --git a/src/controllers/ApiManageController.ts b/src/controllers/ApiManageController.ts index 22db1c7d..34f0c824 100644 --- a/src/controllers/ApiManageController.ts +++ b/src/controllers/ApiManageController.ts @@ -346,6 +346,10 @@ export class ApiManageController extends Controller { "ancestorDNA", "keycloak", "commandId", + "prefixMain", + "authRoleId", + "next_holderId", + "current_holderId", ]; // ฟิลด์ที่ไม่ต้องการแสดงในผลลัพธ์ // การแทนที่ฟิลด์ ID ด้วยฟิลด์ Name สำหรับ Profile entity @@ -411,6 +415,34 @@ export class ApiManageController extends Controller { }, }; + // การแทนที่ฟิลด์ 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", + }, + }; + private validateSuperAdminRole(user: any): void { if (!user.role.includes("SUPER_ADMIN")) { throw new HttpError(HttpStatusCode.FORBIDDEN, "คุณไม่มีสิทธิ์ในการเข้าถึงข้อมูลนี้"); @@ -466,8 +498,8 @@ export class ApiManageController extends Controller { 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), + columns = columns.filter( + (col: { propertyName: string }) => !replacementKeys.includes(col.propertyName), ); // Add the corresponding name fields @@ -481,6 +513,45 @@ export class ApiManageController extends Controller { 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 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, diff --git a/src/controllers/ApiWebServiceController.ts b/src/controllers/ApiWebServiceController.ts index 7c09fe95..49e9287d 100644 --- a/src/controllers/ApiWebServiceController.ts +++ b/src/controllers/ApiWebServiceController.ts @@ -69,6 +69,28 @@ export class ApiWebServiceController extends Controller { }, }; + // การแทนที่ฟิลด์ 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", + }, + }; + /** * build posMaster permission condition * @summary สร้างเงื่อนไขการกรองข้อมูลตามสิทธิ์การเข้าถึง @@ -82,6 +104,7 @@ export class ApiWebServiceController extends Controller { dnaChild3Id?: string | null; dnaChild4Id?: string | null; }, + tableAlias: string = "posMaster", ): string { // ALL - no filtering if (accessType === "ALL") { @@ -94,49 +117,49 @@ export class ApiWebServiceController extends Controller { if (accessType === "ROOT" && dnaIds.dnaRootId) { // All organizations under this root conditions.push( - `posMaster.orgRootId IN (SELECT id FROM orgRoot WHERE orgRevisionId = "${this.currentRevisionId}" AND ancestorDNA LIKE "${dnaIds.dnaRootId}%")`, + `${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( - `posMaster.orgChild4Id IN (SELECT id FROM orgChild4 WHERE orgRevisionId = "${this.currentRevisionId}" AND ancestorDNA = "${dnaIds.dnaChild4Id}")`, + `${tableAlias}.orgChild4Id IN (SELECT id FROM orgChild4 WHERE orgRevisionId = "${this.currentRevisionId}" AND ancestorDNA = "${dnaIds.dnaChild4Id}")`, ); } else if (dnaIds.dnaChild3Id) { conditions.push( - `posMaster.orgChild3Id IN (SELECT id FROM orgChild3 WHERE orgRevisionId = "${this.currentRevisionId}" AND ancestorDNA = "${dnaIds.dnaChild3Id}")`, + `${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( - `(posMaster.orgChild3Id IN (SELECT id FROM orgChild3 WHERE orgRevisionId = "${this.currentRevisionId}" AND ancestorDNA LIKE "${dnaIds.dnaChild3Id}%") OR posMaster.orgChild4Id IS NOT NULL)`, + `(${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( - `posMaster.orgChild2Id IN (SELECT id FROM orgChild2 WHERE orgRevisionId = "${this.currentRevisionId}" AND ancestorDNA = "${dnaIds.dnaChild2Id}")`, + `${tableAlias}.orgChild2Id IN (SELECT id FROM orgChild2 WHERE orgRevisionId = "${this.currentRevisionId}" AND ancestorDNA = "${dnaIds.dnaChild2Id}")`, ); if (accessType === "CHILD") { conditions.push( - `(posMaster.orgChild2Id IN (SELECT id FROM orgChild2 WHERE orgRevisionId = "${this.currentRevisionId}" AND ancestorDNA LIKE "${dnaIds.dnaChild2Id}%") OR posMaster.orgChild3Id IS NOT NULL)`, + `(${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( - `posMaster.orgChild1Id IN (SELECT id FROM orgChild1 WHERE orgRevisionId = "${this.currentRevisionId}" AND ancestorDNA = "${dnaIds.dnaChild1Id}")`, + `${tableAlias}.orgChild1Id IN (SELECT id FROM orgChild1 WHERE orgRevisionId = "${this.currentRevisionId}" AND ancestorDNA = "${dnaIds.dnaChild1Id}")`, ); if (accessType === "CHILD") { conditions.push( - `(posMaster.orgChild1Id IN (SELECT id FROM orgChild1 WHERE orgRevisionId = "${this.currentRevisionId}" AND ancestorDNA LIKE "${dnaIds.dnaChild1Id}%") OR posMaster.orgChild2Id IS NOT NULL)`, + `(${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( - `posMaster.orgRootId IN (SELECT id FROM orgRoot WHERE orgRevisionId = "${this.currentRevisionId}" AND ancestorDNA = "${dnaIds.dnaRootId}")`, + `${tableAlias}.orgRootId IN (SELECT id FROM orgRoot WHERE orgRevisionId = "${this.currentRevisionId}" AND ancestorDNA = "${dnaIds.dnaRootId}")`, ); if (accessType === "CHILD") { conditions.push( - `(posMaster.orgRootId IN (SELECT id FROM orgRoot WHERE orgRevisionId = "${this.currentRevisionId}" AND ancestorDNA LIKE "${dnaIds.dnaRootId}%") OR posMaster.orgChild1Id IS NOT NULL)`, + `(${tableAlias}.orgRootId IN (SELECT id FROM orgRoot WHERE orgRevisionId = "${this.currentRevisionId}" AND ancestorDNA LIKE "${dnaIds.dnaRootId}%") OR ${tableAlias}.orgChild1Id IS NOT NULL)`, ); } } @@ -203,8 +226,9 @@ export class ApiWebServiceController extends Controller { } let posMasterCondition: string = ""; + let posMasterAlias: string = ""; - // Special handling for Profile system with permission filtering + // Special handling for Profile and ProfileEmployee systems with permission filtering if (system == "registry") { // Get current revision const revision = await this.orgRevisionRepository.findOne({ @@ -214,15 +238,89 @@ export class ApiWebServiceController extends Controller { // 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, + 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); @@ -256,6 +354,23 @@ export class ApiWebServiceController extends Controller { }); } + // สำหรับ Position: ตรวจสอบฟิลด์ที่ต้องการ join และแปลง propertyKey + const positionFieldJoins: Record = {}; // alias -> relationName + if (tbMain === "Position") { + 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; + }); + } + const queryBuilder = repo.createQueryBuilder(tbMain); // join กับตารารอง @@ -278,9 +393,40 @@ export class ApiWebServiceController extends Controller { }); } - // join กับ posMaster สำหรับ Profile เพื่อกรองตามสิทธิ์การเข้าถึง - if (tbMain === "Profile" && posMasterCondition !== "1=1") { - queryBuilder.leftJoin("Profile.current_holders", "posMaster"); + // join สำหรับฟิลด์ Position ที่ต้องการดึงค่าจากตารางอื่น + if (tbMain === "Position" && Object.keys(positionFieldJoins).length > 0) { + Object.entries(positionFieldJoins).forEach(([alias, relationName]) => { + queryBuilder.leftJoin(`${tbMain}.${relationName}`, alias); + }); + } + + // join สำหรับ PosMaster เมื่อต้องการดึงค่าจาก Profile (ข้อมูลคนครอง) + const posMasterProfileFields: string[] = []; + if (tbMain === "PosMaster") { + propertyKey.forEach((key) => { + if (key.startsWith("Profile.")) { + posMasterProfileFields.push(key); + } + }); + } + + // join PosMaster กับ Profile เมื่อมีการขอ Profile fields + if (tbMain === "PosMaster" && posMasterProfileFields.length > 0) { + queryBuilder.leftJoin("PosMaster.current_holder", "Profile"); + } + + // join กับ posMaster/employeePosMaster/employeeTempPosMaster เพื่อกรองตามสิทธิ์การเข้าถึง + if ((tbMain === "Profile" || tbMain === "ProfileEmployee") && posMasterCondition !== "1=1") { + if (tbMain === "Profile") { + queryBuilder.leftJoin("Profile.current_holders", "posMaster"); + } else if (tbMain === "ProfileEmployee") { + // Use the correct relation based on posMasterAlias + if (posMasterAlias === "employeeTempPosMaster") { + queryBuilder.leftJoin("ProfileEmployee.current_holderTemps", "employeeTempPosMaster"); + } else { + queryBuilder.leftJoin("ProfileEmployee.current_holders", "employeePosMaster"); + } + } } // // เพิ่ม Main.id เพราะจะใช้ pk ในการแมบและนับจำนวน @@ -333,6 +479,39 @@ export class ApiWebServiceController extends Controller { return flattened; } + // สำหรับ Position: แปลงฟิลด์ที่มาจาก join กลับเป็นชื่อเดิม + if (tbMain === "Position") { + 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 = `${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]; + }); + return flattened; + } + + // สำหรับ PosMaster: แปลงฟิลด์ Profile ที่มาจาก join กลับเป็นฟิลด์ระดับบน + if (tbMain === "PosMaster" && posMasterProfileFields.length > 0) { + const flattened: any = { ...rest }; + // Extract Profile fields and add them at top level with "profile_" prefix to avoid conflicts + if (rest["Profile"]) { + flattened["profile_prefix"] = rest["Profile"].prefix; + flattened["profile_rank"] = rest["Profile"].rank; + flattened["profile_firstName"] = rest["Profile"].firstName; + flattened["profile_lastName"] = rest["Profile"].lastName; + flattened["profile_citizenId"] = rest["Profile"].citizenId; + // Remove the nested Profile object + delete flattened["Profile"]; + } + return flattened; + } + return rest; }); diff --git a/src/services/KeycloakAttributeService.ts b/src/services/KeycloakAttributeService.ts index 1e0f3f07..5206183b 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.isDeleted": 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); } }, From b7c80ea6d4a4f94ce48c34ac42e2f1dd7c3d4fac Mon Sep 17 00:00:00 2001 From: waruneeauy Date: Thu, 21 May 2026 14:03:30 +0700 Subject: [PATCH 33/83] fixed error --- src/controllers/ScriptProfileOrgController.ts | 2 +- src/services/KeycloakAttributeService.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/controllers/ScriptProfileOrgController.ts b/src/controllers/ScriptProfileOrgController.ts index 4881249e..0494be98 100644 --- a/src/controllers/ScriptProfileOrgController.ts +++ b/src/controllers/ScriptProfileOrgController.ts @@ -49,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"); diff --git a/src/services/KeycloakAttributeService.ts b/src/services/KeycloakAttributeService.ts index 5206183b..7bfe88ed 100644 --- a/src/services/KeycloakAttributeService.ts +++ b/src/services/KeycloakAttributeService.ts @@ -543,7 +543,7 @@ export class KeycloakAttributeService { .createQueryBuilder("p") .where("p.keycloak IS NOT NULL") .andWhere("p.keycloak != :empty", { empty: "" }) - .andWhere({ "p.isDeleted": false }) + .andWhere("p.isDelete = :isDelete", { isDelete: false }) .andWhere("p.lastUpdatedAt BETWEEN :start AND :end", { start: startDate, end: endDate, From 69a7ddaaa3389419b9483c94f0dcae59b2edee28 Mon Sep 17 00:00:00 2001 From: Adisak Date: Thu, 21 May 2026 14:23:59 +0700 Subject: [PATCH 34/83] =?UTF-8?q?=E0=B9=81=E0=B8=81=E0=B9=89=20format=20?= =?UTF-8?q?=E0=B8=9F=E0=B8=B4=E0=B8=A7=20posMasterNo=20(2)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/controllers/EmployeePositionController.ts | 20 ++++++------- src/controllers/ProfileEmployeeController.ts | 28 +++++++++---------- .../ProfileGovernmentEmployeeController.ts | 6 ++-- 3 files changed, 27 insertions(+), 27 deletions(-) diff --git a/src/controllers/EmployeePositionController.ts b/src/controllers/EmployeePositionController.ts index 6ead5522..26146106 100644 --- a/src/controllers/EmployeePositionController.ts +++ b/src/controllers/EmployeePositionController.ts @@ -1058,11 +1058,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,posMaster.posMasterNoPrefix,posMaster.posMasterNo,posMaster.posMasterNoSuffix)`; + let searchShortName1 = `CONCAT_WS(" ",orgChild1.orgChild1ShortName,posMaster.posMasterNoPrefix,posMaster.posMasterNo,posMaster.posMasterNoSuffix)`; + let searchShortName2 = `CONCAT_WS(" ",orgChild2.orgChild2ShortName,posMaster.posMasterNoPrefix,posMaster.posMasterNo,posMaster.posMasterNoSuffix)`; + let searchShortName3 = `CONCAT_WS(" ",orgChild3.orgChild3ShortName,posMaster.posMasterNoPrefix,posMaster.posMasterNo,posMaster.posMasterNoSuffix)`; + let searchShortName4 = `CONCAT_WS(" ",orgChild4.orgChild4ShortName,posMaster.posMasterNoPrefix,posMaster.posMasterNo,posMaster.posMasterNoSuffix)`; let _data = await new permission().PermissionOrgList(request, "SYS_ORG_EMP"); if (body.type === 0) { typeCondition = { @@ -1072,7 +1072,7 @@ export class EmployeePositionController extends Controller { checkChildConditions = { orgChild1Id: IsNull(), }; - searchShortName = `CONCAT(orgRoot.orgRootShortName," ",posMaster.posMasterNo) like '%${body.keyword}%'`; + searchShortName = `CONCAT_WS(" ",orgRoot.orgRootShortName,posMaster.posMasterNoPrefix,posMaster.posMasterNo,posMaster.posMasterNoSuffix) like '%${body.keyword}%'`; } else { } } else if (body.type === 1) { @@ -1083,7 +1083,7 @@ export class EmployeePositionController extends Controller { checkChildConditions = { orgChild2Id: IsNull(), }; - searchShortName = `CONCAT(orgChild1.orgChild1ShortName," ",posMaster.posMasterNo) like '%${body.keyword}%'`; + searchShortName = `CONCAT_WS(" ",orgChild1.orgChild1ShortName,posMaster.posMasterNoPrefix,posMaster.posMasterNo,posMaster.posMasterNoSuffix) like '%${body.keyword}%'`; } else { } } else if (body.type === 2) { @@ -1094,7 +1094,7 @@ export class EmployeePositionController extends Controller { checkChildConditions = { orgChild3Id: IsNull(), }; - searchShortName = `CONCAT(orgChild2.orgChild2ShortName," ",posMaster.posMasterNo) like '%${body.keyword}%'`; + searchShortName = `CONCAT_WS(" ",orgChild2.orgChild2ShortName,posMaster.posMasterNoPrefix,posMaster.posMasterNo,posMaster.posMasterNoSuffix) like '%${body.keyword}%'`; } else { } } else if (body.type === 3) { @@ -1105,14 +1105,14 @@ export class EmployeePositionController extends Controller { checkChildConditions = { orgChild4Id: IsNull(), }; - searchShortName = `CONCAT(orgChild3.orgChild3ShortName," ",posMaster.posMasterNo) like '%${body.keyword}%'`; + searchShortName = `CONCAT_WS(" ",orgChild3.orgChild3ShortName,posMaster.posMasterNoPrefix,posMaster.posMasterNo,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,posMaster.posMasterNoPrefix,posMaster.posMasterNo,posMaster.posMasterNoSuffix) like '%${body.keyword}%'`; } let findPosition: any; let masterId = new Array(); diff --git a/src/controllers/ProfileEmployeeController.ts b/src/controllers/ProfileEmployeeController.ts index c0b36961..e888a6ae 100644 --- a/src/controllers/ProfileEmployeeController.ts +++ b/src/controllers/ProfileEmployeeController.ts @@ -2852,12 +2852,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, current_holders.posMasterNoPrefix, current_holders.posMasterNo, current_holders.posMasterNoSuffix) + WHEN current_holders.orgChild3Id IS NOT NULL THEN CONCAT_WS(" ", orgChild3.orgChild3ShortName, current_holders.posMasterNoPrefix, current_holders.posMasterNo, current_holders.posMasterNoSuffix) + WHEN current_holders.orgChild2Id IS NOT NULL THEN CONCAT_WS(" ", orgChild2.orgChild2ShortName, current_holders.posMasterNoPrefix, current_holders.posMasterNo, current_holders.posMasterNoSuffix) + WHEN current_holders.orgChild1Id IS NOT NULL THEN CONCAT_WS(" ", orgChild1.orgChild1ShortName, current_holders.posMasterNoPrefix, current_holders.posMasterNo, current_holders.posMasterNoSuffix) + ELSE CONCAT_WS(" ", orgRoot.orgRootShortName, current_holders.posMasterNoPrefix, current_holders.posMasterNo, current_holders.posMasterNoSuffix) END LIKE :keyword `; } @@ -3211,12 +3211,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, current_holders.posMasterNoPrefix, current_holders.posMasterNo, current_holders.posMasterNoSuffix) + WHEN current_holders.orgChild3Id IS NOT NULL THEN CONCAT_WS(" ", orgChild3.orgChild3ShortName, current_holders.posMasterNoPrefix, current_holders.posMasterNo, current_holders.posMasterNoSuffix) + WHEN current_holders.orgChild2Id IS NOT NULL THEN CONCAT_WS(" ", orgChild2.orgChild2ShortName, current_holders.posMasterNoPrefix, current_holders.posMasterNo, current_holders.posMasterNoSuffix) + WHEN current_holders.orgChild1Id IS NOT NULL THEN CONCAT_WS(" ", orgChild1.orgChild1ShortName, current_holders.posMasterNoPrefix, current_holders.posMasterNo, current_holders.posMasterNoSuffix) + ELSE CONCAT_WS(" ", orgRoot.orgRootShortName, current_holders.posMasterNoPrefix, current_holders.posMasterNo, current_holders.posMasterNoSuffix) END LIKE :keyword `; } @@ -3369,7 +3369,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 @@ -3846,7 +3846,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); 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*/, //เลขที่ตำแหน่ง From fa63953d75f387f433f723b0cb55616687af2a46 Mon Sep 17 00:00:00 2001 From: Adisak Date: Thu, 21 May 2026 15:47:07 +0700 Subject: [PATCH 35/83] =?UTF-8?q?=E0=B9=81=E0=B8=81=E0=B9=89=20format=20?= =?UTF-8?q?=E0=B8=9F=E0=B8=B4=E0=B8=A7=20posMasterNo=20(3)=20=E0=B8=AA?= =?UTF-8?q?=E0=B8=B3=E0=B8=AB=E0=B8=A3=E0=B8=B1=E0=B8=9A=E0=B8=9A=E0=B8=B1?= =?UTF-8?q?=E0=B8=99=E0=B8=97=E0=B8=B6=E0=B8=81=E0=B8=A5=E0=B8=87=E0=B8=A3?= =?UTF-8?q?=E0=B8=B0=E0=B8=9A=E0=B8=9A=E0=B8=97=E0=B8=B5=E0=B9=88=E0=B8=9E?= =?UTF-8?q?=E0=B8=B1=E0=B8=92=E0=B8=99=E0=B8=B2=E0=B8=9A=E0=B8=99=20.net?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/controllers/ProfileController.ts | 41 ++++++++++------------------ 1 file changed, 15 insertions(+), 26 deletions(-) diff --git a/src/controllers/ProfileController.ts b/src/controllers/ProfileController.ts index 14dc725f..2609cd46 100644 --- a/src/controllers/ProfileController.ts +++ b/src/controllers/ProfileController.ts @@ -407,8 +407,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)) + " " : "" @@ -8797,32 +8797,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: [ From ba185f8de80a3deb49b8d210bcfe7079becea327 Mon Sep 17 00:00:00 2001 From: Adisak Date: Thu, 21 May 2026 16:30:24 +0700 Subject: [PATCH 36/83] =?UTF-8?q?=E0=B9=81=E0=B8=81=E0=B9=89=20format=20?= =?UTF-8?q?=E0=B8=9F=E0=B8=B4=E0=B8=A7=20posMasterNo=20(4)=20=E0=B8=AA?= =?UTF-8?q?=E0=B8=B3=E0=B8=AB=E0=B8=A3=E0=B8=B1=E0=B8=9A=E0=B8=9A=E0=B8=B1?= =?UTF-8?q?=E0=B8=99=E0=B8=97=E0=B8=B6=E0=B8=81=E0=B8=A5=E0=B8=87=E0=B8=A3?= =?UTF-8?q?=E0=B8=B0=E0=B8=9A=E0=B8=9A=E0=B8=97=E0=B8=B5=E0=B9=88=E0=B8=9E?= =?UTF-8?q?=E0=B8=B1=E0=B8=92=E0=B8=99=E0=B8=B2=E0=B8=9A=E0=B8=99=20.net?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/controllers/ProfileController.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/controllers/ProfileController.ts b/src/controllers/ProfileController.ts index 2609cd46..0e5abf92 100644 --- a/src/controllers/ProfileController.ts +++ b/src/controllers/ProfileController.ts @@ -9173,26 +9173,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); } From 3d01a166a8e6a989fba6ebc19c4368b49a256ef2 Mon Sep 17 00:00:00 2001 From: harid Date: Thu, 21 May 2026 16:51:55 +0700 Subject: [PATCH 37/83] =?UTF-8?q?fix=20=E0=B9=80=E0=B8=A5=E0=B8=B7?= =?UTF-8?q?=E0=B8=AD=E0=B8=81=E0=B8=95=E0=B8=B3=E0=B9=81=E0=B8=AB=E0=B8=99?= =?UTF-8?q?=E0=B9=88=E0=B8=87=E0=B9=83=E0=B8=99=E0=B8=AA=E0=B8=B2=E0=B8=A2?= =?UTF-8?q?=E0=B8=87=E0=B8=B2=E0=B8=99=E0=B8=AB=E0=B8=B2=E0=B8=A2=20?= =?UTF-8?q?=E0=B9=80=E0=B8=A1=E0=B8=B7=E0=B9=88=E0=B8=AD=E0=B8=A1=E0=B8=B5?= =?UTF-8?q?=E0=B8=81=E0=B8=B2=E0=B8=A3=E0=B9=80=E0=B8=9B=E0=B8=A5=E0=B8=B5?= =?UTF-8?q?=E0=B9=88=E0=B8=A2=E0=B8=99=E0=B9=81=E0=B8=9B=E0=B8=A5=E0=B8=87?= =?UTF-8?q?=E0=B8=82=E0=B9=89=E0=B8=AD=E0=B8=A1=E0=B8=B9=E0=B8=A5=E0=B8=95?= =?UTF-8?q?=E0=B8=B3=E0=B9=81=E0=B8=AB=E0=B8=99=E0=B9=88=E0=B8=87=E0=B9=83?= =?UTF-8?q?=E0=B8=99=E0=B9=82=E0=B8=84=E0=B8=A3=E0=B8=87=E0=B8=AA=E0=B8=A3?= =?UTF-8?q?=E0=B9=89=E0=B8=B2=E0=B8=87=20#2505?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/controllers/CommandController.ts | 399 ++++++++++++++++++++++++--- 1 file changed, 356 insertions(+), 43 deletions(-) diff --git a/src/controllers/CommandController.ts b/src/controllers/CommandController.ts index 59d75312..42685c20 100644 --- a/src/controllers/CommandController.ts +++ b/src/controllers/CommandController.ts @@ -3589,6 +3589,8 @@ export class CommandController extends Controller { positionLevel: string | null; posmasterId: string; positionId: string; + posExecutiveId?: string | null; + positionField?: string | null; commandId?: string | null; orgRoot?: string | null; orgChild1?: string | null; @@ -3685,12 +3687,51 @@ export class CommandController extends Controller { history.profileSalaryId = data.id; await this.salaryHistoryRepo.save(history, { data: req }); - const posMaster = await this.posMasterRepository.findOne({ + // STEP 1: หา posMaster ที่จะใช้งานตาม id ที่ส่งมา + let posMaster = await this.posMasterRepository.findOne({ where: { id: item.posmasterId }, - relations: ["orgRoot", "orgChild1", "orgChild2", "orgChild3", "orgChild4"], + relations: { + orgRevision: true, + orgRoot: true, + orgChild1: true, + orgChild2: true, + orgChild3: true, + orgChild4: true, + }, }); - if (posMaster == null) + + // เช็คว่า 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( + `[CommandController] PosMaster not found - posMasterId: ${item.posmasterId}, ` + ); throw new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบข้อมูลตำแหน่งนี้"); + } const posMasterOld = await this.posMasterRepository.findOne({ where: { @@ -3716,7 +3757,7 @@ export class CommandController extends Controller { const checkPosition = await this.positionRepository.find({ where: { - posMasterId: item.posmasterId, + posMasterId: posMaster.id, // ใช้ posMaster ตัวใหม่ (ที่อาจจะเปลี่ยนจาก ancestorDNA) positionIsSelected: true, }, }); @@ -3738,13 +3779,107 @@ export class CommandController extends Controller { } await this.posMasterRepository.save(posMaster); - const positionNew = await this.positionRepository.findOne({ - where: { - id: item.positionId, - posMasterId: item.posmasterId, - }, - relations: ["posExecutive"], - }); + // 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; + + // ═══════════════════════════════════════════════════════════ + // 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 && item.positionType && item.positionLevel) { + // สร้าง where clause แบบ dynamic - ใส่เฉพาะฟิลด์ที่มีค่า + const whereCondition: any = { + posMasterId: posMaster.id, + positionName: item.positionName, + posTypeId: item.positionType, // positionType = posTypeId + posLevelId: item.positionLevel, // positionLevel = 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"], + }); + + if (positionBy7Fields) { + positionNew = positionBy7Fields; + } + } + + // ═══════════════════════════════════════════════════════════ + // CONDITION 3: Match 3 ฟิลด์ (ถ้า Condition 2 ไม่ match) + // ═══════════════════════════════════════════════════════════ + if (!positionNew && item.positionName && item.positionType && item.positionLevel) { + const positionBy3Fields = await this.positionRepository.findOne({ + where: { + posMasterId: posMaster.id, + positionName: item.positionName, + posTypeId: item.positionType, // positionType = posTypeId + posLevelId: item.positionLevel, // positionLevel = posLevelId + }, + relations: ["posExecutive"], + }); + + 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; @@ -3984,6 +4119,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; @@ -4207,10 +4344,45 @@ export class CommandController extends Controller { //ปลดตำแหน่งเดิมที่ไม่ถูกปลดออกจากกิ่งครั้งเมื่อออกคำสั่งพักราชการหรือออกราชการไว้ await removeProfileInOrganize(profile.id, "OFFICER"); //ปั๊มตำแหน่งใหม่ - const posMaster = await this.posMasterRepository.findOne({ + // หา 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: { @@ -4230,11 +4402,77 @@ export class CommandController extends Controller { // posMaster.conditionReason = _null; // posMaster.isCondition = false; await this.posMasterRepository.save(posMaster); - const positionNew = await this.positionRepository.findOne({ - where: { + + // Match position ตามลำดับ priority + let positionNew: Position | null = null; + + // CONDITION 1: Match 7 ฟิลด์ (ไม่มี positionId ใน body สำหรับกรณีพักราชการ) + if (item.positionNameNew && item.positionTypeNew && item.positionLevelNew) { + 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"], + }); + + if (positionBy7Fields) { + positionNew = positionBy7Fields; + } + } + + // CONDITION 2: Match 3 ฟิลด์ (ถ้า Condition 1 ไม่ 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"], + }); + + 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 }); @@ -6329,6 +6567,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; @@ -6948,8 +7193,12 @@ export class CommandController extends Controller { }); } - if (posMaster == null) + if (posMaster == null) { + console.error( + `[CreateOfficerProfile] not found posMasterId: ${item.bodyPosition.posmasterId}` + ); throw new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบข้อมูลตำแหน่งนี้"); + } // STEP 2: เคลียร์ข้อมูลตำแหน่งเก่าที่ครองอยู่ ในโครงสร้างปัจจุบัน const posMasterOld = await this.posMasterRepository.findOne({ @@ -7003,41 +7252,105 @@ export class CommandController extends Controller { await this.posMasterRepository.save(posMaster); // STEP 5: กำหนด position ใหม่ - // เช็คว่า posMaster เปลี่ยนจากเก่าเป็นใหม่หรือไม่ - const originalPosMasterId = item.bodyPosition.posmasterId; - const isPosMasterChanged = originalPosMasterId !== posMaster.id; + // 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 = null; + let positionNew: Position | null = 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({ + // ═══════════════════════════════════════════════════════════ + // CONDITION 1: เช็คจาก positionId ตรง + // ═══════════════════════════════════════════════════════════ + if (item.bodyPosition?.positionId) { + const positionById = await this.positionRepository.findOne({ where: { id: item.bodyPosition.positionId, - posMasterId: posMaster.id, + posMasterId: posMaster.id, // ต้องอยู่ใน posMaster ที่ถูกต้อง }, relations: ["posExecutive"], }); + + if (positionById) { + positionNew = positionById; + } } + // ═══════════════════════════════════════════════════════════ + // CONDITION 2: Match 7 ฟิลด์ (ถ้า Condition 1 ไม่ match) + // ═══════════════════════════════════════════════════════════ + if (!positionNew && item.bodyPosition) { + // สร้าง 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"], + }); + + if (positionBy7Fields) { + positionNew = positionBy7Fields; + } + } + + // ═══════════════════════════════════════════════════════════ + // CONDITION 3: Match 3 ฟิลด์ (ถ้า Condition 2 ไม่ match) + // ═══════════════════════════════════════════════════════════ + if (!positionNew && item.bodyPosition) { + const positionBy3Fields = await this.positionRepository.findOne({ + where: { + posMasterId: posMaster.id, + positionName: item.bodyPosition.positionName, + posTypeId: item.bodyPosition.posTypeId, + posLevelId: item.bodyPosition.posLevelId, + }, + relations: ["posExecutive"], + }); + + 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; From d3f01165aeb580246eb39fe7939bae45e5ba6491 Mon Sep 17 00:00:00 2001 From: waruneeauy Date: Thu, 21 May 2026 18:08:20 +0700 Subject: [PATCH 38/83] fix api web service --- src/controllers/ApiManageController.ts | 53 +++- src/controllers/ApiWebServiceController.ts | 350 +++++++++++++++++++-- 2 files changed, 367 insertions(+), 36 deletions(-) diff --git a/src/controllers/ApiManageController.ts b/src/controllers/ApiManageController.ts index 34f0c824..1f89e98d 100644 --- a/src/controllers/ApiManageController.ts +++ b/src/controllers/ApiManageController.ts @@ -316,12 +316,12 @@ export class ApiManageController extends Controller { description: "ข้อมูลส่วนราชการ ระดับที่ 4", system: ["position"], }, - { - name: "Profile", - repository: this.profileRepository, - description: "ข้อมูลคนครอง", - system: ["position"], - }, + // { + // name: "Profile", + // repository: this.profileRepository, + // description: "ข้อมูลคนครอง", + // system: ["position"], + // }, ]; private readonly DEFAULT_PAGE_SIZE = 10; // ขนาดหน้าเริ่มต้น @@ -443,6 +443,27 @@ export class ApiManageController extends Controller { }, }; + // การแทนที่ฟิลด์ 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", + }, + }; + private validateSuperAdminRole(user: any): void { if (!user.role.includes("SUPER_ADMIN")) { throw new HttpError(HttpStatusCode.FORBIDDEN, "คุณไม่มีสิทธิ์ในการเข้าถึงข้อมูลนี้"); @@ -533,6 +554,26 @@ export class ApiManageController extends Controller { 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 PosMaster entity - add Profile fields for holder information if (name === "PosMaster") { // Add Profile fields that are accessible via current_holder relation diff --git a/src/controllers/ApiWebServiceController.ts b/src/controllers/ApiWebServiceController.ts index 49e9287d..8673e684 100644 --- a/src/controllers/ApiWebServiceController.ts +++ b/src/controllers/ApiWebServiceController.ts @@ -8,6 +8,10 @@ import { isPermissionRequest } from "../middlewares/authWebService"; import { RequestWithUserWebService } from "../middlewares/user"; import { OrgRevision } from "../entities/OrgRevision"; import { ApiHistory } from "../entities/ApiHistory"; +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") @@ -91,6 +95,23 @@ export class ApiWebServiceController extends Controller { }, }; + // การแทนที่ฟิลด์ 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 สร้างเงื่อนไขการกรองข้อมูลตามสิทธิ์การเข้าถึง @@ -198,6 +219,13 @@ export class ApiWebServiceController extends Controller { await isPermissionRequest(request, apiName.id); const offset = (page - 1) * pageSize; 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); + }); let tbMain: string = ""; let condition: string = "1=1"; @@ -225,7 +253,7 @@ export class ApiWebServiceController extends Controller { condition = `PosMaster.orgRevisionId = "${revision?.id}"`; } - let posMasterCondition: string = ""; + let posMasterCondition: string = "1=1"; let posMasterAlias: string = ""; // Special handling for Profile and ProfileEmployee systems with permission filtering @@ -337,6 +365,13 @@ 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") { @@ -356,7 +391,7 @@ export class ApiWebServiceController extends Controller { // สำหรับ Position: ตรวจสอบฟิลด์ที่ต้องการ join และแปลง propertyKey const positionFieldJoins: Record = {}; // alias -> relationName - if (tbMain === "Position") { + if (tbMain === "Position" || tbMain === "PosMaster") { propertyKey = propertyKey.map((key) => { const [table, field] = key.split("."); if (table === "Position") { @@ -371,21 +406,56 @@ export class ApiWebServiceController extends Controller { }); } + // สำหรับ 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; + }); + } + 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]) => { @@ -394,8 +464,35 @@ export class ApiWebServiceController extends Controller { } // join สำหรับฟิลด์ Position ที่ต้องการดึงค่าจากตารางอื่น - if (tbMain === "Position" && Object.keys(positionFieldJoins).length > 0) { + 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); }); } @@ -403,15 +500,24 @@ export class ApiWebServiceController extends Controller { // join สำหรับ PosMaster เมื่อต้องการดึงค่าจาก Profile (ข้อมูลคนครอง) const posMasterProfileFields: string[] = []; if (tbMain === "PosMaster") { - propertyKey.forEach((key) => { - if (key.startsWith("Profile.")) { - posMasterProfileFields.push(key); - } - }); + // 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"); } @@ -434,7 +540,7 @@ export class ApiWebServiceController extends Controller { // propertyKey.push(`${Main}.id`); // } - // add FK + // add PK - ensure propertyKey is never empty let pk: string = ""; const primaryColumns = metadata.primaryColumns; primaryColumns.forEach((col) => { @@ -444,14 +550,27 @@ export class ApiWebServiceController extends Controller { } }); - const [items, total] = await queryBuilder - .select(propertyKey) - .where(condition) - .andWhere(posMasterCondition) - .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(); + } // ลบ Main.id // const results = items.map(({ id, ...x }) => x); @@ -480,9 +599,30 @@ export class ApiWebServiceController extends Controller { } // สำหรับ Position: แปลงฟิลด์ที่มาจาก join กลับเป็นชื่อเดิม - if (tbMain === "Position") { + 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 @@ -499,23 +639,173 @@ export class ApiWebServiceController extends Controller { // สำหรับ PosMaster: แปลงฟิลด์ Profile ที่มาจาก join กลับเป็นฟิลด์ระดับบน if (tbMain === "PosMaster" && posMasterProfileFields.length > 0) { const flattened: any = { ...rest }; - // Extract Profile fields and add them at top level with "profile_" prefix to avoid conflicts + 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"]) { - flattened["profile_prefix"] = rest["Profile"].prefix; - flattened["profile_rank"] = rest["Profile"].rank; - flattened["profile_firstName"] = rest["Profile"].firstName; - flattened["profile_lastName"] = rest["Profile"].lastName; - flattened["profile_citizenId"] = rest["Profile"].citizenId; + 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) { + 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"], child1VisibleFields)) + .where("OrgChild1.orgRootId IN (:...rootIds)", { rootIds }) + .orderBy("OrgChild1.id", "ASC") + .getMany(), + AppDataSource.getRepository(OrgChild2) + .createQueryBuilder("OrgChild2") + .select( + buildSelect("OrgChild2", ["id", "orgRootId", "orgChild1Id"], child2VisibleFields), + ) + .where("OrgChild2.orgRootId IN (:...rootIds)", { rootIds }) + .orderBy("OrgChild2.id", "ASC") + .getMany(), + AppDataSource.getRepository(OrgChild3) + .createQueryBuilder("OrgChild3") + .select( + buildSelect( + "OrgChild3", + ["id", "orgRootId", "orgChild1Id", "orgChild2Id"], + 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"], + 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()); + responseTotal = responseData.length; + } // save api history after query success const history = { @@ -565,6 +855,6 @@ export class ApiWebServiceController extends Controller { // return flattenedItem; // }); - return new HttpSuccess({ data: data, total }); + return new HttpSuccess({ data: responseData, total: responseTotal }); } } From 6719585d45462c7e82a381c4365788e74e61c941 Mon Sep 17 00:00:00 2001 From: waruneeauy Date: Fri, 22 May 2026 12:35:21 +0700 Subject: [PATCH 39/83] fixed api service --- src/controllers/ApiManageController.ts | 96 ++++++++++----------- src/controllers/ApiWebServiceController.ts | 99 ++++++++++++++++++++-- 2 files changed, 142 insertions(+), 53 deletions(-) diff --git a/src/controllers/ApiManageController.ts b/src/controllers/ApiManageController.ts index 1f89e98d..3effdbd5 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,49 +273,49 @@ 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: "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, @@ -343,13 +343,13 @@ export class ApiManageController extends Controller { "orgChild2Id", "orgChild3Id", "orgChild4Id", - "ancestorDNA", "keycloak", "commandId", "prefixMain", "authRoleId", "next_holderId", "current_holderId", + "ancestorDNA", ]; // ฟิลด์ที่ไม่ต้องการแสดงในผลลัพธ์ // การแทนที่ฟิลด์ ID ด้วยฟิลด์ Name สำหรับ Profile entity diff --git a/src/controllers/ApiWebServiceController.ts b/src/controllers/ApiWebServiceController.ts index 8673e684..1b4ffb51 100644 --- a/src/controllers/ApiWebServiceController.ts +++ b/src/controllers/ApiWebServiceController.ts @@ -189,6 +189,22 @@ export class ApiWebServiceController extends Controller { 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 * @summary รายการ fields ตาม systems @@ -227,6 +243,28 @@ export class ApiWebServiceController extends Controller { 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") { @@ -594,6 +632,14 @@ export class ApiWebServiceController extends Controller { 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]; + } }); return flattened; } @@ -632,6 +678,14 @@ export class ApiWebServiceController extends Controller { } // 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]; + } }); return flattened; } @@ -679,7 +733,12 @@ export class ApiWebServiceController extends Controller { const out: any = {}; fields.forEach((field) => { if (obj[field] !== undefined) { - out[field] = obj[field]; + // ถ้าเป็น ancestorDNA ให้เปลี่ยนชื่อเป็น id + if (field === "ancestorDNA") { + out.id = obj[field]; + } else { + out[field] = obj[field]; + } } }); return out; @@ -707,14 +766,14 @@ export class ApiWebServiceController extends Controller { const [child1Rows, child2Rows, child3Rows, child4Rows] = await Promise.all([ AppDataSource.getRepository(OrgChild1) .createQueryBuilder("OrgChild1") - .select(buildSelect("OrgChild1", ["id", "orgRootId"], child1VisibleFields)) + .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"], child2VisibleFields), + buildSelect("OrgChild2", ["id", "orgRootId", "orgChild1Id", "ancestorDNA"], child2VisibleFields), ) .where("OrgChild2.orgRootId IN (:...rootIds)", { rootIds }) .orderBy("OrgChild2.id", "ASC") @@ -724,7 +783,7 @@ export class ApiWebServiceController extends Controller { .select( buildSelect( "OrgChild3", - ["id", "orgRootId", "orgChild1Id", "orgChild2Id"], + ["id", "orgRootId", "orgChild1Id", "orgChild2Id", "ancestorDNA"], child3VisibleFields, ), ) @@ -736,7 +795,7 @@ export class ApiWebServiceController extends Controller { .select( buildSelect( "OrgChild4", - ["id", "orgRootId", "orgChild1Id", "orgChild2Id", "orgChild3Id"], + ["id", "orgRootId", "orgChild1Id", "orgChild2Id", "orgChild3Id", "ancestorDNA"], child4VisibleFields, ), ) @@ -804,6 +863,36 @@ export class ApiWebServiceController extends Controller { } 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; } From 442ce20d8069426ec2283210d43fbd154769e38a Mon Sep 17 00:00:00 2001 From: Adisak Date: Fri, 22 May 2026 13:33:22 +0700 Subject: [PATCH 40/83] #1588 and performance --- src/controllers/ProfileController.ts | 2 +- src/controllers/ProfileEmployeeController.ts | 1871 +++++++++--------- src/services/ProfileLeaveService.ts | 507 +++-- 3 files changed, 1225 insertions(+), 1155 deletions(-) diff --git a/src/controllers/ProfileController.ts b/src/controllers/ProfileController.ts index 0e5abf92..2b626697 100644 --- a/src/controllers/ProfileController.ts +++ b/src/controllers/ProfileController.ts @@ -6300,7 +6300,7 @@ export class ProfileController extends Controller { @Query() sortBy: string = "profile.dateLeave", @Query() sort: "ASC" | "DESC" = "ASC", ) { - let _data = await new permission().PermissionOrgList(request, "SYS_REGISTRY_OFFICER"); + let _data = await new permission().PermissionOrgList(request, "SYS_REGISTRY_RETIRE_OFFICER"); const { data, total } = await this.profileLeaveService.getLeaveOfficer(request, { page, diff --git a/src/controllers/ProfileEmployeeController.ts b/src/controllers/ProfileEmployeeController.ts index e888a6ae..14104100 100644 --- a/src/controllers/ProfileEmployeeController.ts +++ b/src/controllers/ProfileEmployeeController.ts @@ -196,7 +196,7 @@ export class ProfileEmployeeController extends Controller { }, }); ImgUrl = response_.data.downloadUrl; - } catch {} + } catch { } } const province = await this.provinceRepository.findOneBy({ id: profile.registrationProvinceId, @@ -208,36 +208,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 +286,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 +335,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 +386,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 +399,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 +483,7 @@ export class ProfileEmployeeController extends Controller { }, }); _ImgUrl[i] = response_.data.downloadUrl; - } catch {} + } catch { } } }), ); @@ -502,7 +497,7 @@ export class ProfileEmployeeController extends Controller { }, }); ImgUrl = response_.data.downloadUrl; - } catch {} + } catch { } } const profileOc = await this.profileRepo.findOne({ relations: [ @@ -541,36 +536,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 +584,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 +605,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 +642,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 +673,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 +720,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 +772,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 +892,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 +1023,7 @@ export class ProfileEmployeeController extends Controller { }, }); _ImgUrl[i] = response_.data.downloadUrl; - } catch {} + } catch { } } }), ); @@ -1042,7 +1037,7 @@ export class ProfileEmployeeController extends Controller { }, }); ImgUrl = response_.data.downloadUrl; - } catch {} + } catch { } } const orgRevision = await this.orgRevisionRepo.findOne({ @@ -1076,36 +1071,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 +1126,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 +1159,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 +1185,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 +1211,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 +1253,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 +1330,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 +1499,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 +1562,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 +1587,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 +1633,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 +1786,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 +1823,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 +1872,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 +1893,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 +2038,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 +2056,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 +2142,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, @@ -2536,8 +2531,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 +2695,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; @@ -2950,57 +2945,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 +3106,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, @@ -3275,7 +3270,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, @@ -3387,40 +3382,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 +3683,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), }); }), ) @@ -3927,37 +3922,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; @@ -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,36 +6427,36 @@ 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; @@ -6469,27 +6464,27 @@ export class ProfileEmployeeController extends Controller { 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 != 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 != 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 + 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 + 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 + 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 _profile: any = { 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 }; } } From ce114cf769a18540e469e907fe1782f2df8fdd2e Mon Sep 17 00:00:00 2001 From: harid Date: Fri, 22 May 2026 13:38:21 +0700 Subject: [PATCH 41/83] =?UTF-8?q?API=20=E0=B9=80=E0=B8=A3=E0=B8=B5?= =?UTF-8?q?=E0=B8=A2=E0=B8=87=E0=B8=A5=E0=B8=B3=E0=B8=94=E0=B8=B1=E0=B8=9A?= =?UTF-8?q?=E0=B8=97=E0=B8=B0=E0=B9=80=E0=B8=9A=E0=B8=B5=E0=B8=A2=E0=B8=99?= =?UTF-8?q?=E0=B8=9B=E0=B8=A3=E0=B8=B0=E0=B8=A7=E0=B8=B1=E0=B8=95=E0=B8=B4?= =?UTF-8?q?=E0=B9=81=E0=B8=A5=E0=B8=B0=E0=B9=80=E0=B8=87=E0=B8=B4=E0=B8=99?= =?UTF-8?q?=E0=B9=80=E0=B8=94=E0=B8=B7=E0=B8=AD=E0=B8=99=E0=B8=97=E0=B8=B5?= =?UTF-8?q?=E0=B9=88=E0=B8=81=E0=B8=B3=E0=B8=A5=E0=B8=B1=E0=B8=87=E0=B9=81?= =?UTF-8?q?=E0=B8=81=E0=B9=89=E0=B9=84=E0=B8=82=E0=B8=95=E0=B8=B2=E0=B8=A1?= =?UTF-8?q?=E0=B8=A7=E0=B8=B1=E0=B8=99=E0=B8=97=E0=B8=B5=E0=B9=88=E0=B8=84?= =?UTF-8?q?=E0=B8=B3=E0=B8=AA=E0=B8=B1=E0=B9=88=E0=B8=87=E0=B8=A1=E0=B8=B5?= =?UTF-8?q?=E0=B8=9C=E0=B8=A5=20#2509?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ProfileSalaryTempController.ts | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) 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(); + } } From 2ce104b852ed422108c8acb30dbd8ed6db8e37d9 Mon Sep 17 00:00:00 2001 From: harid Date: Fri, 22 May 2026 13:55:55 +0700 Subject: [PATCH 42/83] =?UTF-8?q?condition=20=E0=B8=97=E0=B8=B5=E0=B9=88?= =?UTF-8?q?=E0=B8=8A=E0=B8=B7=E0=B9=88=E0=B8=AD=E0=B8=95=E0=B8=B3=E0=B9=81?= =?UTF-8?q?=E0=B8=AB=E0=B8=99=E0=B9=88=E0=B8=87,=20=E0=B8=9B=E0=B8=A3?= =?UTF-8?q?=E0=B8=B0=E0=B9=80=E0=B8=A0=E0=B8=97=20=E0=B9=81=E0=B8=A5?= =?UTF-8?q?=E0=B8=B0=20=E0=B8=A3=E0=B8=B0=E0=B8=94=E0=B8=B1=E0=B8=9A?= =?UTF-8?q?=E0=B8=8B=E0=B9=89=E0=B8=B3=E0=B8=81=E0=B8=B1=E0=B8=99=20?= =?UTF-8?q?=E0=B9=83=E0=B8=AB=E0=B9=89=E0=B9=83=E0=B8=8A=E0=B9=89=E0=B9=81?= =?UTF-8?q?=E0=B8=96=E0=B8=A7=E0=B8=A5=E0=B8=B3=E0=B8=94=E0=B8=B1=E0=B8=9A?= =?UTF-8?q?=E0=B9=81=E0=B8=A3=E0=B8=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/controllers/CommandController.ts | 46 ++++++++++++++++++++++++---- 1 file changed, 40 insertions(+), 6 deletions(-) diff --git a/src/controllers/CommandController.ts b/src/controllers/CommandController.ts index 42685c20..0b2c1b12 100644 --- a/src/controllers/CommandController.ts +++ b/src/controllers/CommandController.ts @@ -3834,6 +3834,7 @@ export class CommandController extends Controller { const positionBy7Fields = await this.positionRepository.findOne({ where: whereCondition, relations: ["posExecutive"], + order: { orderNo: "ASC" } }); if (positionBy7Fields) { @@ -3849,10 +3850,11 @@ export class CommandController extends Controller { where: { posMasterId: posMaster.id, positionName: item.positionName, - posTypeId: item.positionType, // positionType = posTypeId - posLevelId: item.positionLevel, // positionLevel = posLevelId + posTypeId: item.positionType, + posLevelId: item.positionLevel, }, relations: ["posExecutive"], + order: { orderNo: "ASC" } }); if (positionBy3Fields) { @@ -4138,6 +4140,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; @@ -4403,11 +4406,36 @@ export class CommandController extends Controller { // posMaster.isCondition = false; await this.posMasterRepository.save(posMaster); - // Match position ตามลำดับ priority + // 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: Match 7 ฟิลด์ (ไม่มี positionId ใน body สำหรับกรณีพักราชการ) - if (item.positionNameNew && item.positionTypeNew && item.positionLevelNew) { + // ═══════════════════════════════════════════════════════════ + // 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, @@ -4431,6 +4459,7 @@ export class CommandController extends Controller { const positionBy7Fields = await this.positionRepository.findOne({ where: whereCondition, relations: ["posExecutive"], + order: { orderNo: "ASC" } }); if (positionBy7Fields) { @@ -4438,7 +4467,9 @@ export class CommandController extends Controller { } } - // CONDITION 2: Match 3 ฟิลด์ (ถ้า Condition 1 ไม่ match) + // ═══════════════════════════════════════════════════════════ + // CONDITION 3: Match 3 ฟิลด์ (ถ้า Condition 2 ไม่ match) + // ═══════════════════════════════════════════════════════════ if (!positionNew && item.positionNameNew && item.positionTypeNew && item.positionLevelNew) { const positionBy3Fields = await this.positionRepository.findOne({ where: { @@ -4448,6 +4479,7 @@ export class CommandController extends Controller { posLevelId: item.positionLevelNew, }, relations: ["posExecutive"], + order: { orderNo: "ASC" } }); if (positionBy3Fields) { @@ -7305,6 +7337,7 @@ export class CommandController extends Controller { const positionBy7Fields = await this.positionRepository.findOne({ where: whereCondition, relations: ["posExecutive"], + order: { orderNo: "ASC" } }); if (positionBy7Fields) { @@ -7324,6 +7357,7 @@ export class CommandController extends Controller { posLevelId: item.bodyPosition.posLevelId, }, relations: ["posExecutive"], + order: { orderNo: "ASC" } }); if (positionBy3Fields) { From 4cd39bb0e986bb757edbbdb66c194972e4efb12e Mon Sep 17 00:00:00 2001 From: harid Date: Fri, 22 May 2026 18:22:00 +0700 Subject: [PATCH 43/83] =?UTF-8?q?=E0=B9=80=E0=B8=9E=E0=B8=B4=E0=B9=88?= =?UTF-8?q?=E0=B8=A1=20log=20api=20excexute/create-officer-profile?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/controllers/CommandController.ts | 85 +++++++++++++++++++++++++++- 1 file changed, 82 insertions(+), 3 deletions(-) diff --git a/src/controllers/CommandController.ts b/src/controllers/CommandController.ts index 0b2c1b12..3f1f1858 100644 --- a/src/controllers/CommandController.ts +++ b/src/controllers/CommandController.ts @@ -6632,18 +6632,28 @@ export class CommandController extends Controller { }[]; }, ) { + console.log("[Excexute/CreateOfficerProfile] Starting CreateOfficeProfileExcecute"); + console.log("[Excexute/CreateOfficerProfile] Request body count:", body.data?.length); const roleKeycloak = await this.roleKeycloakRepo.findOne({ where: { name: Like("USER") }, }); + console.log("[Excexute/CreateOfficerProfile] roleKeycloak found:", !!roleKeycloak); const list = await getRoles(); - if (!Array.isArray(list)) throw new Error("Failed. Cannot get role(s) data from the server."); + console.log("[Excexute/CreateOfficerProfile] Roles list retrieved, length:", Array.isArray(list) ? list.length : "not array"); + if (!Array.isArray(list)) { + console.error("[Excexute/CreateOfficerProfile] 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("[Excexute/CreateOfficerProfile] Getting command data"); const _command = await this.commandRepository.findOne({ where: { id: body.data.find((x) => x.bodySalarys?.commandId)?.bodySalarys?.commandId ?? "" }, }); + console.log("[Excexute/CreateOfficerProfile] Command found:", !!_command, "isBangkok:", _command?.isBangkok); if (_command) { if (_command?.isBangkok?.toLocaleUpperCase() == "OFFICE") { + console.log("[Excexute/CreateOfficerProfile] Setting position codes for OFFICE"); const orgRootDeputy = await this.orgRootRepository.findOne({ where: { isDeputy: true, @@ -6656,10 +6666,14 @@ export class CommandController extends Controller { }); _posNumCodeSit = orgRootDeputy ? orgRootDeputy?.orgRootName : "สำนักปลัดกรุงเทพมหานคร"; _posNumCodeSitAbb = orgRootDeputy ? orgRootDeputy?.orgRootShortName : "สนป."; + console.log("[Excexute/CreateOfficerProfile] OFFICE position codes set:", _posNumCodeSit, _posNumCodeSitAbb); } else if (_command?.isBangkok?.toLocaleUpperCase() == "BANGKOK") { + console.log("[Excexute/CreateOfficerProfile] Setting position codes for BANGKOK"); _posNumCodeSit = "กรุงเทพมหานคร"; _posNumCodeSitAbb = "กทม."; + console.log("[Excexute/CreateOfficerProfile] BANGKOK position codes set:", _posNumCodeSit, _posNumCodeSitAbb); } else { + console.log("[Excexute/CreateOfficerProfile] Setting position codes from admin profile"); let _profileAdmin = await this.profileRepository.findOne({ where: { keycloak: _command?.createdUserId.toString(), @@ -6678,6 +6692,7 @@ export class CommandController extends Controller { _posNumCodeSitAbb = _profileAdmin?.current_holders.find((x) => x.orgRoot.orgRootShortName)?.orgRoot .orgRootShortName ?? ""; + console.log("[Excexute/CreateOfficerProfile] Admin profile position codes set:", _posNumCodeSit, _posNumCodeSitAbb); } } const before = null; @@ -6689,8 +6704,10 @@ export class CommandController extends Controller { createdAt: new Date(), lastUpdatedAt: new Date(), }; + console.log("[Excexute/CreateOfficerProfile] Starting to process", body.data.length, "profile(s)"); await Promise.all( - body.data.map(async (item) => { + body.data.map(async (item, index) => { + console.log("[Excexute/CreateOfficerProfile] Processing item", index + 1, "of", body.data.length); const _null: any = null; if (item.bodyProfile.posLevelId === "") item.bodyProfile.posLevelId = null; if (item.bodyProfile.posTypeId === "") item.bodyProfile.posTypeId = null; @@ -6698,15 +6715,18 @@ export class CommandController extends Controller { item.bodyProfile.posLevelId && !(await this.posLevelRepo.findOneBy({ id: item.bodyProfile.posLevelId })) ) { + console.error("[Excexute/CreateOfficerProfile] ไม่พบข้อมูลระดับตำแหน่งนี้ posLevelId:", item.bodyProfile.posLevelId); throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูลระดับตำแหน่งนี้"); } if ( item.bodyProfile.posTypeId && !(await this.posTypeRepo.findOneBy({ id: item.bodyProfile.posTypeId })) ) { + console.error("[Excexute/CreateOfficerProfile] ไม่พบข้อมูลประเภทตำแหน่งนี้ posTypeId:", item.bodyProfile.posTypeId); throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูลประเภทตำแหน่งนี้"); } + console.log("[Excexute/CreateOfficerProfile] Processing citizenId:", item.bodyProfile.citizenId); let registrationProvinceId = await this.provinceRepo.findOneBy({ id: item.bodyProfile.registrationProvinceId ?? "", }); @@ -6725,6 +6745,7 @@ export class CommandController extends Controller { let currentSubDistrictId = await this.subDistrictRepo.findOneBy({ id: item.bodyProfile.currentSubDistrictId ?? "", }); + console.log("[Excexute/CreateOfficerProfile] Address validation completed"); let _dateRetire = item.bodyProfile.birthDate == null @@ -6737,8 +6758,11 @@ export class CommandController extends Controller { let userKeycloakId: any; let result: any; + console.log("[Excexute/CreateOfficerProfile] Checking Keycloak user for citizenId:", item.bodyProfile.citizenId); const checkUser = await getUserByUsername(item.bodyProfile.citizenId); + console.log("[Excexute/CreateOfficerProfile] Keycloak user exists:", checkUser.length > 0); if (checkUser.length == 0) { + console.log("[Excexute/CreateOfficerProfile] Creating new Keycloak user"); let password = item.bodyProfile.citizenId; if (item.bodyProfile.birthDate != null) { const _date = new Date(item.bodyProfile.birthDate.toDateString()) @@ -6751,10 +6775,17 @@ export class CommandController extends Controller { const _year = new Date(item.bodyProfile.birthDate.toDateString()).getFullYear() + 543; password = `${_date}${_month}${_year}`; } + console.log("[Excexute/CreateOfficerProfile] Calling createUser for:", item.bodyProfile.citizenId); + console.log("[Excexute/CreateOfficerProfile] createUser data - firstName:", item.bodyProfile.firstName, "lastName:", item.bodyProfile.lastName); userKeycloakId = await createUser(item.bodyProfile.citizenId, password, { firstName: item.bodyProfile.firstName, lastName: item.bodyProfile.lastName, }); + if (userKeycloakId && typeof userKeycloakId === "object" && userKeycloakId.errorMessage) { + console.error("[Excexute/CreateOfficerProfile] createUser FAILED - field:", userKeycloakId.field, "errorMessage:", userKeycloakId.errorMessage, "params:", userKeycloakId.params); + throw new HttpError(HttpStatus.BAD_REQUEST, `Keycloak validation failed: ${userKeycloakId.field} - ${userKeycloakId.errorMessage}`); + } + console.log("[Excexute/CreateOfficerProfile] User created in Keycloak, userKeycloakId:", userKeycloakId); result = await addUserRoles( userKeycloakId, list @@ -6764,14 +6795,18 @@ export class CommandController extends Controller { name: x.name, })), ); + console.log("[Excexute/CreateOfficerProfile] USER role assigned to new user, result:", result); } else { + console.log("[Excexute/CreateOfficerProfile] Updating existing Keycloak user"); userKeycloakId = checkUser[0].id; + console.log("[Excexute/CreateOfficerProfile] Existing userKeycloakId:", userKeycloakId); const rolesData = await getRoleMappings(userKeycloakId); if (rolesData) { const _delRole = rolesData.map((x: any) => ({ id: x.id, name: x.name, })); + console.log("[Excexute/CreateOfficerProfile] Removing old roles:", _delRole.length); await removeUserRoles(userKeycloakId, _delRole); } result = await addUserRoles( @@ -6783,22 +6818,27 @@ export class CommandController extends Controller { name: x.name, })), ); + console.log("[Excexute/CreateOfficerProfile] 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("[Excexute/CreateOfficerProfile] Profile found:", !!profile, "for citizenId:", item.bodyProfile.citizenId); let _oldInsigniaIds: string[] = []; let _oldSalaries: any[] = []; //ลูกจ้างประจำ หรือ บุคคลภายนอก if (!profile) { + console.log("[Excexute/CreateOfficerProfile] 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("[Excexute/CreateOfficerProfile] Employee profile found:", !!profileEmployee); if (profileEmployee) { + console.log("[Excexute/CreateOfficerProfile] Converting employee profile to officer profile"); const _order = await this.salaryRepo.findOne({ where: { profileEmployeeId: profileEmployee.id }, order: { order: "DESC" }, @@ -6885,21 +6925,26 @@ export class CommandController extends Controller { profile.bloodGroup = item.bodyProfile.bloodGroup ?? null; profile.phone = item.bodyProfile.phone ?? null; + console.log("[Excexute/CreateOfficerProfile] Saving new profile"); await this.profileRepository.save(profile); + console.log("[Excexute/CreateOfficerProfile] New profile saved, profileId:", profile.id); // update user attribute in keycloak await updateUserAttributes(profile.keycloak ?? "", { profileId: [profile.id], prefix: [profile.prefix || ""], }); + console.log("[Excexute/CreateOfficerProfile] Keycloak attributes updated"); setLogDataDiff(req, { before, after: profile }); } //ขรก.ในระบบ หรือ ขรก.ในระบบที่สถานะพ้นจากราชการ else { + console.log("[Excexute/CreateOfficerProfile] Existing profile found, isLeave:", profile.isLeave, "leaveType:", profile.leaveType); //สร้างโปรไฟล์ใหม่ ถ้าสถานะพ้นราชการ คำสั่งโอนออกหรือคำสั่งขอลาออก if ( profile.isLeave && ["PLACEMENT_TRANSFER", "RETIRE_RESIGN"].includes(profile.leaveType) ) { + console.log("[Excexute/CreateOfficerProfile] Profile is leaving with eligible leave type, creating new profile record"); //ดึง profileSalary เดิม _oldSalaries = await this.salaryRepo.find({ where: { profileId: profile.id }, @@ -6949,8 +6994,10 @@ export class CommandController extends Controller { profile.bloodGroup = item.bodyProfile.bloodGroup ?? null; profile.phone = item.bodyProfile.phone ?? null; await this.profileRepository.save(profile); + console.log("[Excexute/CreateOfficerProfile] New profile record saved for leaving officer, profileId:", profile.id); setLogDataDiff(req, { before, after: profile }); } else { + console.log("[Excexute/CreateOfficerProfile] Updating existing active profile"); profile.roleKeycloaks = result && roleKeycloak ? [roleKeycloak] : []; profile.keycloak = userKeycloakId && typeof userKeycloakId === "string" ? userKeycloakId : ""; @@ -7037,13 +7084,16 @@ export class CommandController extends Controller { ? item.bodyProfile.phone : profile.phone; await this.profileRepository.save(profile); + console.log("[Excexute/CreateOfficerProfile] Existing active profile updated, profileId:", profile.id); setLogDataDiff(req, { before, after: profile }); } } if (profile && profile.id) { + console.log("[Excexute/CreateOfficerProfile] Processing additional data for profileId:", profile.id); //Educations if (item.bodyEducations && item.bodyEducations.length > 0) { + console.log("[Excexute/CreateOfficerProfile] Processing educations, count:", item.bodyEducations.length); await Promise.all( item.bodyEducations.map(async (education) => { const profileEdu = new ProfileEducation(); @@ -7066,6 +7116,7 @@ export class CommandController extends Controller { } //Certificates if (item.bodyCertificates && item.bodyCertificates.length > 0) { + console.log("[Excexute/CreateOfficerProfile] Processing certificates, count:", item.bodyCertificates.length); await Promise.all( item.bodyCertificates.map(async (cer) => { const profileCer = new ProfileCertificate(); @@ -7082,6 +7133,7 @@ export class CommandController extends Controller { } //FamilyCouple if (item.bodyMarry != null) { + console.log("[Excexute/CreateOfficerProfile] Processing couple/marry data"); const profileCouple = new ProfileFamilyCouple(); const data = { profileId: profile.id, @@ -7103,6 +7155,7 @@ export class CommandController extends Controller { } //FamilyFather if (item.bodyFather != null) { + console.log("[Excexute/CreateOfficerProfile] Processing father data"); const profileFather = new ProfileFamilyFather(); const data = { profileId: profile.id, @@ -7123,6 +7176,7 @@ export class CommandController extends Controller { } //FamilyMother if (item.bodyMother != null) { + console.log("[Excexute/CreateOfficerProfile] Processing mother data"); const profileMother = new ProfileFamilyMother(); const data = { profileId: profile.id, @@ -7144,6 +7198,7 @@ export class CommandController extends Controller { //Salary //insert profileSalary อันเก่า กรณีพ้นราชการแล้วกลับมาบรรจุ if (_oldSalaries.length > 0) { + console.log("[Excexute/CreateOfficerProfile] Restoring old salaries, count:", _oldSalaries.length); await Promise.all( _oldSalaries.map(async (oldSal) => { const profileSal: any = new ProfileSalary(); @@ -7160,6 +7215,7 @@ export class CommandController extends Controller { } //insert item.bodySalarys ต่อจากที่ insert เดิมไปแล้ว if (item.bodySalarys && item.bodySalarys != null) { + console.log("[Excexute/CreateOfficerProfile] Processing new salary data"); const dest_item = await this.salaryRepo.findOne({ where: { profileId: profile.id }, order: { order: "DESC" }, @@ -7184,7 +7240,9 @@ export class CommandController extends Controller { } //Position if (item.bodyPosition && item.bodyPosition != null) { + console.log("[Excexute/CreateOfficerProfile] Processing position assignment"); // STEP 1: หา posMaster ที่จะใช้งานตาม id ที่ส่งมา (อาจเป็นตำแหน่งเก่าหรือใหม่ก็ได้) + console.log("[Excexute/CreateOfficerProfile] STEP 1: Finding posMaster, posmasterId:", item.bodyPosition.posmasterId); let posMaster = await this.posMasterRepository.findOne({ where: { id: item.bodyPosition.posmasterId, @@ -7198,14 +7256,17 @@ export class CommandController extends Controller { orgChild4: true, }, }); + console.log("[Excexute/CreateOfficerProfile] posMaster found:", !!posMaster); // เช็คว่า posMaster ที่หามาอยู่ในโครงสร้างปัจจุบันหรือไม่ const isCurrent = posMaster?.orgRevision?.orgRevisionIsCurrent === true && posMaster?.orgRevision?.orgRevisionIsDraft === false; + console.log("[Excexute/CreateOfficerProfile] posMaster isCurrent:", isCurrent); // ถ้าไม่อยู่ในโครงสร้างปัจจุบัน ให้หาตัวใหม่จาก ancestorDNA if (!isCurrent && posMaster?.ancestorDNA) { + console.log("[Excexute/CreateOfficerProfile] Finding current posMaster from ancestorDNA"); posMaster = await this.posMasterRepository.findOne({ where: { ancestorDNA: posMaster.ancestorDNA, @@ -7223,16 +7284,18 @@ export class CommandController extends Controller { orgChild4: true, }, }); + console.log("[Excexute/CreateOfficerProfile] Current posMaster from ancestorDNA found:", !!posMaster); } if (posMaster == null) { console.error( - `[CreateOfficerProfile] not found posMasterId: ${item.bodyPosition.posmasterId}` + `[Excexute/CreateOfficerProfile] not found posMasterId: ${item.bodyPosition.posmasterId}` ); throw new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบข้อมูลตำแหน่งนี้"); } // STEP 2: เคลียร์ข้อมูลตำแหน่งเก่าที่ครองอยู่ ในโครงสร้างปัจจุบัน + console.log("[Excexute/CreateOfficerProfile] STEP 2: Clearing old position data"); const posMasterOld = await this.posMasterRepository.findOne({ where: { current_holderId: profile.id, @@ -7258,6 +7321,7 @@ export class CommandController extends Controller { } // STEP 3: เคลียร์ position ที่เลือกไว้อื่นๆ ใน posMaster ตัวใหม่ + console.log("[Excexute/CreateOfficerProfile] STEP 3: Clearing other selected positions in new posMaster"); const checkPosition = await this.positionRepository.find({ where: { posMasterId: posMaster.id, @@ -7273,6 +7337,7 @@ export class CommandController extends Controller { } // STEP 4: กำหนดคนครองใหม่ให้กับ posMaster + console.log("[Excexute/CreateOfficerProfile] STEP 4: Assigning new holder to posMaster"); posMaster.current_holderId = profile.id; posMaster.lastUpdatedAt = new Date(); // posMaster.conditionReason = _null; @@ -7282,8 +7347,10 @@ export class CommandController extends Controller { await CreatePosMasterHistoryOfficer(posMasterOld.id, req); } await this.posMasterRepository.save(posMaster); + console.log("[Excexute/CreateOfficerProfile] posMaster saved with new holder"); // STEP 5: กำหนด position ใหม่ + console.log("[Excexute/CreateOfficerProfile] STEP 5: Determining position to assign"); // Match position ตามลำดับ priority: // Condition 1: match จาก positionId // Condition 2: match 7 ฟิลด์ (positionName, posTypeId, posLevelId, positionField, positionArea, positionExecutiveField, posExecutiveId) @@ -7295,6 +7362,7 @@ export class CommandController extends Controller { // ═══════════════════════════════════════════════════════════ // CONDITION 1: เช็คจาก positionId ตรง // ═══════════════════════════════════════════════════════════ + console.log("[Excexute/CreateOfficerProfile] CONDITION 1: Checking by positionId:", item.bodyPosition?.positionId); if (item.bodyPosition?.positionId) { const positionById = await this.positionRepository.findOne({ where: { @@ -7306,6 +7374,7 @@ export class CommandController extends Controller { if (positionById) { positionNew = positionById; + console.log("[Excexute/CreateOfficerProfile] CONDITION 1 matched, positionId:", positionById.id); } } @@ -7313,6 +7382,7 @@ export class CommandController extends Controller { // CONDITION 2: Match 7 ฟิลด์ (ถ้า Condition 1 ไม่ match) // ═══════════════════════════════════════════════════════════ if (!positionNew && item.bodyPosition) { + console.log("[Excexute/CreateOfficerProfile] CONDITION 1 not matched, trying CONDITION 2: Match 7 fields"); // สร้าง where clause แบบ dynamic - ใส่เฉพาะฟิลด์ที่ไม่ใช่ null const whereCondition: any = { posMasterId: posMaster.id, @@ -7342,6 +7412,7 @@ export class CommandController extends Controller { if (positionBy7Fields) { positionNew = positionBy7Fields; + console.log("[Excexute/CreateOfficerProfile] CONDITION 2 matched with 7 fields, positionId:", positionBy7Fields.id); } } @@ -7349,6 +7420,7 @@ export class CommandController extends Controller { // CONDITION 3: Match 3 ฟิลด์ (ถ้า Condition 2 ไม่ match) // ═══════════════════════════════════════════════════════════ if (!positionNew && item.bodyPosition) { + console.log("[Excexute/CreateOfficerProfile] CONDITION 2 not matched, trying CONDITION 3: Match 3 fields"); const positionBy3Fields = await this.positionRepository.findOne({ where: { posMasterId: posMaster.id, @@ -7362,6 +7434,9 @@ export class CommandController extends Controller { if (positionBy3Fields) { positionNew = positionBy3Fields; + console.log("[Excexute/CreateOfficerProfile] CONDITION 3 matched with 3 fields, positionId:", positionBy3Fields.id); + } else { + console.log("[Excexute/CreateOfficerProfile] No position matched for profileId:", profile.id); } } @@ -7387,6 +7462,7 @@ export class CommandController extends Controller { // ถ้าไม่ใช่ตำแหน่งนั่งทับ (isSit = false) ถึงจะอัพเดทตำแหน่งในทะเบียนประวัติ if (positionNew != null) { + console.log("[Excexute/CreateOfficerProfile] Final position assignment, isSit:", posMaster.isSit, "positionId:", positionNew.id); positionNew.positionIsSelected = true; // อัพเดท org และ posMasterNo ตลอดไม่ต้องดัก isSit profile.posMasterNo = getPosMasterNo(posMaster); @@ -7412,6 +7488,7 @@ export class CommandController extends Controller { } // Insignia if (_oldInsigniaIds.length > 0) { + console.log("[Excexute/CreateOfficerProfile] Processing old insignias, count:", _oldInsigniaIds.length); const _insignias = await this.insigniaRepo.find({ where: { id: In(_oldInsigniaIds), isDeleted: false }, order: { createdAt: "ASC" }, @@ -7446,6 +7523,7 @@ export class CommandController extends Controller { } // เพิ่มรูปภาพโปรไฟล์ if (item.bodyProfile.objectRefId) { + console.log("[Excexute/CreateOfficerProfile] Processing profile avatar image, objectRefId:", item.bodyProfile.objectRefId); const _profileAvatar = new ProfileAvatar(); Object.assign(_profileAvatar, { ...meta, @@ -7489,6 +7567,7 @@ export class CommandController extends Controller { } }), ); + console.log("[Excexute/CreateOfficerProfile] CreateOfficeProfileExcecute completed successfully"); return new HttpSuccess(); } From 2abbc6225ecd753950709e0d33dc78b2b60da585 Mon Sep 17 00:00:00 2001 From: waruneeauy Date: Fri, 22 May 2026 21:30:38 +0700 Subject: [PATCH 44/83] fixed api web service --- src/controllers/ApiWebServiceController.ts | 107 ++++++++++++++++++++- 1 file changed, 104 insertions(+), 3 deletions(-) diff --git a/src/controllers/ApiWebServiceController.ts b/src/controllers/ApiWebServiceController.ts index 1b4ffb51..ae8e469d 100644 --- a/src/controllers/ApiWebServiceController.ts +++ b/src/controllers/ApiWebServiceController.ts @@ -8,6 +8,7 @@ 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"; @@ -269,6 +270,7 @@ export class ApiWebServiceController extends Controller { let condition: string = "1=1"; if (system == "registry") { tbMain = "Profile"; + condition = `Profile.isActive = true`; } else if (system == "registry_emp") { tbMain = "ProfileEmployee"; condition = `ProfileEmployee.employeeClass = "PERM"`; @@ -559,17 +561,48 @@ export class ApiWebServiceController extends Controller { queryBuilder.leftJoin("PosMaster.current_holder", "Profile"); } + // สำหรับ registry system: เก็บ posMaster เพื่อดึง org IDs แล้วค่อย query ancestorDNA + let includeOrgAncestorDna = false; + if (system === "registry") { + // 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 (posMasterAlias === "employeeTempPosMaster") { + queryBuilder.innerJoin("ProfileEmployee.current_holderTemps", "employeeTempPosMaster", "employeeTempPosMaster.orgRevisionId = :currentRevisionId"); + } else { + queryBuilder.innerJoin("ProfileEmployee.current_holders", "employeePosMaster", "employeePosMaster.orgRevisionId = :currentRevisionId"); + } + queryBuilder.setParameter("currentRevisionId", this.currentRevisionId); + } + + // Mark that we need to include ancestorDNA fields + includeOrgAncestorDna = true; + } + // join กับ posMaster/employeePosMaster/employeeTempPosMaster เพื่อกรองตามสิทธิ์การเข้าถึง if ((tbMain === "Profile" || tbMain === "ProfileEmployee") && posMasterCondition !== "1=1") { if (tbMain === "Profile") { - queryBuilder.leftJoin("Profile.current_holders", "posMaster"); + queryBuilder.innerJoin("Profile.current_holders", "posMaster", "posMaster.orgRevisionId = :currentRevisionIdPerm"); + queryBuilder.setParameter("currentRevisionIdPerm", this.currentRevisionId); } else if (tbMain === "ProfileEmployee") { // Use the correct relation based on posMasterAlias if (posMasterAlias === "employeeTempPosMaster") { - queryBuilder.leftJoin("ProfileEmployee.current_holderTemps", "employeeTempPosMaster"); + queryBuilder.innerJoin("ProfileEmployee.current_holderTemps", "employeeTempPosMaster", "employeeTempPosMaster.orgRevisionId = :currentRevisionIdPerm"); } else { - queryBuilder.leftJoin("ProfileEmployee.current_holders", "employeePosMaster"); + queryBuilder.innerJoin("ProfileEmployee.current_holders", "employeePosMaster", "employeePosMaster.orgRevisionId = :currentRevisionIdPerm"); } + queryBuilder.setParameter("currentRevisionIdPerm", this.currentRevisionId); } } @@ -610,6 +643,49 @@ export class ApiWebServiceController extends Controller { .getManyAndCount(); } + // สำหรับ registry: ดึง 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) => { + 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); + } + }); + + // 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); // const results = items.map(({ pk, ...x }) => x); @@ -641,6 +717,31 @@ export class ApiWebServiceController extends Controller { 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; } From 33bd92af117af3cb8fb01004722353e0207de87b Mon Sep 17 00:00:00 2001 From: waruneeauy Date: Fri, 22 May 2026 21:36:46 +0700 Subject: [PATCH 45/83] complete api web service registry add org id --- src/controllers/ApiWebServiceController.ts | 68 ++++++++++++++++++++-- 1 file changed, 64 insertions(+), 4 deletions(-) diff --git a/src/controllers/ApiWebServiceController.ts b/src/controllers/ApiWebServiceController.ts index ae8e469d..69781172 100644 --- a/src/controllers/ApiWebServiceController.ts +++ b/src/controllers/ApiWebServiceController.ts @@ -563,7 +563,7 @@ export class ApiWebServiceController extends Controller { // สำหรับ registry system: เก็บ posMaster เพื่อดึง org IDs แล้วค่อย query ancestorDNA let includeOrgAncestorDna = false; - if (system === "registry") { + 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") { @@ -578,12 +578,28 @@ export class ApiWebServiceController extends Controller { propertyKey.push("posMaster.orgChild4Id"); } else if (tbMain === "ProfileEmployee") { // For registry_emp and registry_temp, also use inner join with current revision - if (posMasterAlias === "employeeTempPosMaster") { + 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"); } - queryBuilder.setParameter("currentRevisionId", this.currentRevisionId); } // Mark that we need to include ancestorDNA fields @@ -643,7 +659,7 @@ export class ApiWebServiceController extends Controller { .getManyAndCount(); } - // สำหรับ registry: ดึง ancestorDNA จาก org tables + // สำหรับ registry systems: ดึง ancestorDNA จาก org tables let orgRootAncestorMap: Record = {}; let orgChild1AncestorMap: Record = {}; let orgChild2AncestorMap: Record = {}; @@ -659,6 +675,7 @@ export class ApiWebServiceController extends Controller { 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); @@ -667,6 +684,24 @@ export class ApiWebServiceController extends Controller { 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 @@ -788,6 +823,31 @@ export class ApiWebServiceController extends Controller { 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; } From 0c7c8e9fd3281705cb74f556b1fd31537e36ecf6 Mon Sep 17 00:00:00 2001 From: waruneeauy Date: Fri, 22 May 2026 21:57:25 +0700 Subject: [PATCH 46/83] fixed api web service case select ROOT, NORMAL, CHILD --- src/controllers/ApiWebServiceController.ts | 28 +++++++++++++++------- 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/src/controllers/ApiWebServiceController.ts b/src/controllers/ApiWebServiceController.ts index 69781172..e47c145c 100644 --- a/src/controllers/ApiWebServiceController.ts +++ b/src/controllers/ApiWebServiceController.ts @@ -607,18 +607,28 @@ export class ApiWebServiceController extends Controller { } // 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") { - queryBuilder.innerJoin("Profile.current_holders", "posMaster", "posMaster.orgRevisionId = :currentRevisionIdPerm"); - queryBuilder.setParameter("currentRevisionIdPerm", this.currentRevisionId); - } else if (tbMain === "ProfileEmployee") { - // Use the correct relation based on posMasterAlias - if (posMasterAlias === "employeeTempPosMaster") { - queryBuilder.innerJoin("ProfileEmployee.current_holderTemps", "employeeTempPosMaster", "employeeTempPosMaster.orgRevisionId = :currentRevisionIdPerm"); - } else { - queryBuilder.innerJoin("ProfileEmployee.current_holders", "employeePosMaster", "employeePosMaster.orgRevisionId = :currentRevisionIdPerm"); + // 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); } - queryBuilder.setParameter("currentRevisionIdPerm", this.currentRevisionId); } } From e0f2513ba40fc3c6f0ae3ed1c2d22c9a3720adaf Mon Sep 17 00:00:00 2001 From: waruneeauy Date: Sat, 23 May 2026 00:23:55 +0700 Subject: [PATCH 47/83] add log check isSelected in position --- src/controllers/CommandController.ts | 29 +++++++++++++++++++++++---- src/controllers/PositionController.ts | 25 +++++++++++++++++++++-- src/interfaces/utils.ts | 19 ++++++++++++++++++ 3 files changed, 67 insertions(+), 6 deletions(-) diff --git a/src/controllers/CommandController.ts b/src/controllers/CommandController.ts index 3f1f1858..e164f973 100644 --- a/src/controllers/CommandController.ts +++ b/src/controllers/CommandController.ts @@ -44,6 +44,7 @@ import { checkExceptCommandType, checkCommandType, removePostMasterAct, + logPositionIsSelectedChange, } from "../interfaces/utils"; import { Position } from "../entities/Position"; import { PosMaster } from "../entities/PosMaster"; @@ -3751,6 +3752,13 @@ export class CommandController extends Controller { }, }); if (positionOld != null) { + logPositionIsSelectedChange(positionOld.id, positionOld.positionIsSelected, false, { + posMasterId: posMasterOld?.id, + userId: request.user.sub, + endpoint: "updateMaster", + action: "command_change_reset_old_position", + }); + positionOld.positionIsSelected = false; await this.positionRepository.save(positionOld); } @@ -3762,10 +3770,23 @@ export class CommandController extends Controller { }, }); if (checkPosition.length > 0) { - const clearPosition = checkPosition.map((positions) => ({ - ...positions, - positionIsSelected: false, - })); + console.log( + `[positionIsSelected-DEBUG] Command change: clearing ${checkPosition.length} positions (posMasterId: ${posMaster.id}, userId: ${request.user.sub}, endpoint: updateMaster)` + ); + + const clearPosition = checkPosition.map((positions) => { + logPositionIsSelectedChange(positions.id, positions.positionIsSelected, false, { + posMasterId: posMaster.id, + userId: request.user.sub, + endpoint: "updateMaster", + action: "command_change_clear_positions", + }); + + return { + ...positions, + positionIsSelected: false, + }; + }); await this.positionRepository.save(clearPosition); } diff --git a/src/controllers/PositionController.ts b/src/controllers/PositionController.ts index efcd1497..2f23bdaa 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 { @@ -4034,7 +4044,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, }); diff --git a/src/interfaces/utils.ts b/src/interfaces/utils.ts index 347f28af..be2f3bf9 100644 --- a/src/interfaces/utils.ts +++ b/src/interfaces/utils.ts @@ -751,4 +751,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 From 61a09acbad68e5dc2199be3c94ad37a76d8de623 Mon Sep 17 00:00:00 2001 From: waruneeauy Date: Sat, 23 May 2026 00:30:57 +0700 Subject: [PATCH 48/83] fixed bug --- src/controllers/CommandController.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/controllers/CommandController.ts b/src/controllers/CommandController.ts index e164f973..f4e335ce 100644 --- a/src/controllers/CommandController.ts +++ b/src/controllers/CommandController.ts @@ -3754,7 +3754,7 @@ export class CommandController extends Controller { if (positionOld != null) { logPositionIsSelectedChange(positionOld.id, positionOld.positionIsSelected, false, { posMasterId: posMasterOld?.id, - userId: request.user.sub, + userId: req.user.sub, endpoint: "updateMaster", action: "command_change_reset_old_position", }); @@ -3765,19 +3765,19 @@ export class CommandController extends Controller { const checkPosition = await this.positionRepository.find({ where: { - posMasterId: posMaster.id, // ใช้ posMaster ตัวใหม่ (ที่อาจจะเปลี่ยนจาก ancestorDNA) + 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: ${request.user.sub}, endpoint: updateMaster)` + `[positionIsSelected-DEBUG] Command change: clearing ${checkPosition.length} positions (posMasterId: ${posMaster!.id}, userId: ${req.user.sub}, endpoint: updateMaster)` ); const clearPosition = checkPosition.map((positions) => { logPositionIsSelectedChange(positions.id, positions.positionIsSelected, false, { - posMasterId: posMaster.id, - userId: request.user.sub, + posMasterId: posMaster!.id, + userId: req.user.sub, endpoint: "updateMaster", action: "command_change_clear_positions", }); From 32282b016b0b818116b03113a75e55dd85b6ee5f Mon Sep 17 00:00:00 2001 From: waruneeauy Date: Sat, 23 May 2026 09:17:49 +0700 Subject: [PATCH 49/83] remove log updateUserAttributes success --- src/keycloak/index.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) 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); From 1555e59b881a2048b4dff8185bf050ab64ea5612 Mon Sep 17 00:00:00 2001 From: waruneeauy Date: Mon, 25 May 2026 17:31:58 +0700 Subject: [PATCH 50/83] fixed BATCH_SIZE 100 to 50 --- src/controllers/ProfileSalaryController.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/controllers/ProfileSalaryController.ts b/src/controllers/ProfileSalaryController.ts index 19a9f5e4..8abe9aa2 100644 --- a/src/controllers/ProfileSalaryController.ts +++ b/src/controllers/ProfileSalaryController.ts @@ -70,7 +70,7 @@ export class ProfileSalaryController extends Controller { where: { position: Not(IsNull()) }, }); - const BATCH_SIZE = 100; + const BATCH_SIZE = 50; let successCount = 0; let failCount = 0; const allData: CreateTenurePositionOfficer[] = []; @@ -178,7 +178,7 @@ export class ProfileSalaryController extends Controller { where: { position: Not(IsNull()) }, }); - const BATCH_SIZE = 100; + const BATCH_SIZE = 50; let successCount = 0; let failCount = 0; const allData: CreateTenurePositionEmployee[] = []; @@ -290,7 +290,7 @@ export class ProfileSalaryController extends Controller { }, }); - const BATCH_SIZE = 100; + const BATCH_SIZE = 50; let successCount = 0; let failCount = 0; const allData: CreateTenureLevelOfficer[] = []; @@ -434,7 +434,7 @@ export class ProfileSalaryController extends Controller { }, }); - const BATCH_SIZE = 100; + const BATCH_SIZE = 50; let successCount = 0; let failCount = 0; const allData: CreateTenureLevelEmployee[] = []; @@ -575,7 +575,7 @@ export class ProfileSalaryController extends Controller { where: { posExecutive: Not(IsNull()) }, }); - const BATCH_SIZE = 100; + const BATCH_SIZE = 50; let successCount = 0; let failCount = 0; const allData: CreateTenurePositionExecutiveOfficer[] = []; From eede5f51c42c056c21a52c6b0191225d685a36d5 Mon Sep 17 00:00:00 2001 From: harid Date: Mon, 25 May 2026 17:34:24 +0700 Subject: [PATCH 51/83] =?UTF-8?q?@Route("api/v1/org/dotnet")=20=E0=B8=9B?= =?UTF-8?q?=E0=B8=A3=E0=B8=B1=E0=B8=9A=E0=B9=83=E0=B8=8A=E0=B9=89=20API=20?= =?UTF-8?q?Key?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../OrganizationDotnetController.ts | 67 ++++++++++++++++++- src/middlewares/auth.ts | 6 ++ src/middlewares/authInternal.ts | 30 +++++++++ tsoa.json | 6 ++ 4 files changed, 106 insertions(+), 3 deletions(-) create mode 100644 src/middlewares/authInternal.ts diff --git a/src/controllers/OrganizationDotnetController.ts b/src/controllers/OrganizationDotnetController.ts index fce9bb98..8be7aa0f 100644 --- a/src/controllers/OrganizationDotnetController.ts +++ b/src/controllers/OrganizationDotnetController.ts @@ -40,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, "เกิดข้อผิดพลาด ไม่สามารถแสดงรายการได้ กรุณาลองใหม่ในภายหลัง", @@ -73,6 +73,7 @@ export class OrganizationDotnetController extends Controller { * */ @Post("check-citizen") + @Security("internalAuth") public async CheckCitizen( @Body() body: { @@ -90,6 +91,7 @@ export class OrganizationDotnetController extends Controller { * */ @Post("search") + @Security("internalAuth") public async SearchProfile( @Body() body: { @@ -304,6 +306,7 @@ export class OrganizationDotnetController extends Controller { * */ @Post("search-employee") + @Security("internalAuth") public async SearchProfileEmployee( @Body() body: { @@ -488,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 }, @@ -497,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 }, @@ -506,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 }, @@ -521,6 +527,7 @@ export class OrganizationDotnetController extends Controller { * */ @Get("get-profileId") + @Security("bearerAuth") async getProfileInbox(@Request() request: { user: Record }) { let profile: any; //OFF @@ -556,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: [ @@ -1355,6 +1363,7 @@ export class OrganizationDotnetController extends Controller { } @Get("keycloak/{keycloakId}") + @Security("internalAuth") async GetProfileByKeycloakIdAsync(@Path() keycloakId: string) { /* ========================= * 1. Load profile @@ -1783,6 +1792,7 @@ export class OrganizationDotnetController extends Controller { } @Get("by-keycloak/{keycloakId}") + @Security("internalAuth") async NewGetProfileByKeycloakIdAsync(@Path() keycloakId: string) { /* ========================= * 1. Load profile @@ -2017,6 +2027,7 @@ export class OrganizationDotnetController extends Controller { // เพิ่มที่อยู่ปัจจุบัน + ตำแหน่งหัวหน้า @Get("by-keycloak2/{keycloakId}") + @Security("internalAuth") async NewGetProfileByKeycloak2IdAsync(@Path() keycloakId: string) { /* ========================= * 1. Load profile @@ -2358,8 +2369,11 @@ export class OrganizationDotnetController extends Controller { * @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) * ========================= */ @@ -2379,6 +2393,8 @@ export class OrganizationDotnetController extends Controller { // Employee if (!profile) { + console.log(`[check-keycloak] OFFICER_NOT_FOUND - keycloakId=${keycloakId}, checking EMPLOYEE`); + const empProfile = await this.profileEmpRepo.findOne({ where: { keycloak: keycloakId }, relations: { @@ -2392,7 +2408,12 @@ export class OrganizationDotnetController extends Controller { }, }, }); - if (!empProfile) throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูล"); + + 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 && @@ -2426,9 +2447,15 @@ export class OrganizationDotnetController extends Controller { 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) * ========================================= */ @@ -2467,6 +2494,10 @@ export class OrganizationDotnetController extends Controller { 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) @@ -2485,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 @@ -2564,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: [ @@ -3233,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: [ @@ -3887,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: [ @@ -4199,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: [ @@ -4403,6 +4439,7 @@ export class OrganizationDotnetController extends Controller { * @param {string} keycloakId Id keycloak */ @Post("find/employee/position") + @Security("internalAuth") async GetProfileByPositionEmpAsync( @Body() body: { @@ -4732,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 }, @@ -4750,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 }, @@ -4801,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 }, @@ -4968,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 }, @@ -4984,6 +5025,7 @@ export class OrganizationDotnetController extends Controller { * */ @Get("keycloak") + @Security("internalAuth") async GetProfileWithKeycloak() { const profile = await this.profileRepo.find({ where: { keycloak: Not(IsNull()) }, @@ -5193,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("") }, @@ -5321,6 +5364,7 @@ export class OrganizationDotnetController extends Controller { * */ @Post("keycloak-all-officer") + @Security("internalAuth") async PostProfileWithKeycloakAllOfficer( @Body() body: { @@ -5491,6 +5535,7 @@ export class OrganizationDotnetController extends Controller { * */ @Post("keycloak-all-officer/date") + @Security("internalAuth") async PostProfileWithKeycloakAllOfficerDate( @Body() body: { @@ -5669,6 +5714,7 @@ export class OrganizationDotnetController extends Controller { * */ @Post("none-validate-keycloak-all-officer") + @Security("internalAuth") async PostProfileWithNoneValidateKeycloakAllOfficer( @Body() body: { @@ -5838,6 +5884,7 @@ export class OrganizationDotnetController extends Controller { * */ @Post("find-node-name") + @Security("internalAuth") async findNodeName( @Body() body: { @@ -5946,6 +5993,7 @@ export class OrganizationDotnetController extends Controller { * */ @Post("officer-by-admin-role") + @Security("internalAuth") async GetOfficersByAdminRole( @Body() body: { @@ -6283,6 +6331,7 @@ export class OrganizationDotnetController extends Controller { * */ @Post("keycloak-all-employee") + @Security("internalAuth") async PostProfileWithKeycloakAllEmployee( @Body() body: { @@ -6436,6 +6485,7 @@ export class OrganizationDotnetController extends Controller { * */ @Post("none-validate-keycloak-all-employee") + @Security("internalAuth") async PostProfileWithNoneValidateKeycloakAllEmployee( @Body() body: { @@ -6589,6 +6639,7 @@ export class OrganizationDotnetController extends Controller { * */ @Post("employee-by-admin-role") + @Security("internalAuth") async GetEmployeesByAdminRole( @Body() body: { @@ -6929,8 +6980,9 @@ export class OrganizationDotnetController extends Controller { * @summary รายชื่อลูกจ้างประจำ ตามสิทธิ์ admin */ @Post("employee-by-admin-rolev2") + @Security("internalAuth") async GetEmployeesByAdminRoleV2( - @Request() req: RequestWithUser, + // @Request() req: RequestWithUser, // ไม่ได้ใช้ @Body() body: { node: number; @@ -7199,6 +7251,7 @@ export class OrganizationDotnetController extends Controller { * */ @Put("update-dutytime") + @Security("bearerAuth") async UpdateDutyTimeAsync( @Request() req: RequestWithUser, @Body() @@ -7241,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"); @@ -7317,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; @@ -7604,6 +7659,7 @@ export class OrganizationDotnetController extends Controller { } @Post("profile-leave/keycloak") + @Security("internalAuth") async GetProfileLeaveReportByKeycloakIdAsync( @Body() body: { keycloakId: string; report?: string }, ) { @@ -7908,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() @@ -8037,6 +8094,7 @@ export class OrganizationDotnetController extends Controller { * */ @Post("officer-by-admin-rolev2") + @Security("internalAuth") async GetOfficersByAdminRoleV2( @Body() body: { @@ -8260,6 +8318,7 @@ export class OrganizationDotnetController extends Controller { * */ @Post("officer-by-admin-rolev3") + @Security("internalAuth") async GetOfficersByAdminRoleV3( @Body() body: { @@ -8606,6 +8665,7 @@ export class OrganizationDotnetController extends Controller { * */ @Post("officer-by-admin-rolev4") + @Security("internalAuth") async GetOfficersByAdminRoleV4( @Body() body: { @@ -8882,6 +8942,7 @@ export class OrganizationDotnetController extends Controller { * */ @Post("find-staff") + @Security("internalAuth") async findHigher( @Body() requestBody: { 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..8d29ceb6 --- /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["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/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": [ From 7b22fb2a2df815d9c1a521cd6e51fa0aba6d0844 Mon Sep 17 00:00:00 2001 From: harid Date: Mon, 25 May 2026 17:43:50 +0700 Subject: [PATCH 52/83] fix api key --- src/middlewares/authInternal.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/middlewares/authInternal.ts b/src/middlewares/authInternal.ts index 8d29ceb6..d3d9a5b6 100644 --- a/src/middlewares/authInternal.ts +++ b/src/middlewares/authInternal.ts @@ -7,7 +7,7 @@ import HttpStatus from "../interfaces/http-status"; export async function handleInternalAuth(request: express.Request) { // รองรับ header หลายรูปแบบ const apiKey = - request.headers["api-key"] || request.headers["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"); From 82d81334f5088465843133a181383efbd7e04ff4 Mon Sep 17 00:00:00 2001 From: Adisak Date: Mon, 25 May 2026 18:19:34 +0700 Subject: [PATCH 53/83] #2505 --- src/controllers/CommandController.ts | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/src/controllers/CommandController.ts b/src/controllers/CommandController.ts index f4e335ce..09ab9792 100644 --- a/src/controllers/CommandController.ts +++ b/src/controllers/CommandController.ts @@ -3588,6 +3588,8 @@ 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; @@ -3809,6 +3811,10 @@ export class CommandController extends Controller { 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 ตรง // ═══════════════════════════════════════════════════════════ @@ -3829,13 +3835,13 @@ export class CommandController extends Controller { // ═══════════════════════════════════════════════════════════ // CONDITION 2: Match 7 ฟิลด์ (ถ้า Condition 1 ไม่ match) // ═══════════════════════════════════════════════════════════ - if (!positionNew && item.positionName && item.positionType && item.positionLevel) { + if (!positionNew && item.positionName && posTypeId && posLevelId) { // สร้าง where clause แบบ dynamic - ใส่เฉพาะฟิลด์ที่มีค่า const whereCondition: any = { posMasterId: posMaster.id, positionName: item.positionName, - posTypeId: item.positionType, // positionType = posTypeId - posLevelId: item.positionLevel, // positionLevel = posLevelId + posTypeId: posTypeId, + posLevelId: posLevelId, }; // เพิ่มเฉพาะฟิลด์ที่มีค่า (ไม่ใช่ null, undefined, หรือ string ว่าง) @@ -3866,13 +3872,13 @@ export class CommandController extends Controller { // ═══════════════════════════════════════════════════════════ // CONDITION 3: Match 3 ฟิลด์ (ถ้า Condition 2 ไม่ match) // ═══════════════════════════════════════════════════════════ - if (!positionNew && item.positionName && item.positionType && item.positionLevel) { + if (!positionNew && item.positionName && posTypeId && posLevelId) { const positionBy3Fields = await this.positionRepository.findOne({ where: { posMasterId: posMaster.id, positionName: item.positionName, - posTypeId: item.positionType, - posLevelId: item.positionLevel, + posTypeId: posTypeId, + posLevelId: posLevelId, }, relations: ["posExecutive"], order: { orderNo: "ASC" } From 97df4d6cf5a09891d4c9566ad42ccf84fb89b75f Mon Sep 17 00:00:00 2001 From: waruneeauy Date: Tue, 26 May 2026 12:43:11 +0700 Subject: [PATCH 54/83] fixed api web service explode field --- src/controllers/ApiManageController.ts | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/src/controllers/ApiManageController.ts b/src/controllers/ApiManageController.ts index 3effdbd5..a3a205d1 100644 --- a/src/controllers/ApiManageController.ts +++ b/src/controllers/ApiManageController.ts @@ -350,6 +350,31 @@ export class ApiManageController extends Controller { "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", ]; // ฟิลด์ที่ไม่ต้องการแสดงในผลลัพธ์ // การแทนที่ฟิลด์ ID ด้วยฟิลด์ Name สำหรับ Profile entity From 0cad83af1fca041d215bcfdc46851ae35b83325a Mon Sep 17 00:00:00 2001 From: harid Date: Tue, 26 May 2026 13:20:02 +0700 Subject: [PATCH 55/83] =?UTF-8?q?#2523=20STAFF=20+=20isDirector=20?= =?UTF-8?q?=E0=B9=83=E0=B8=AB=E0=B9=89=E0=B8=A5=E0=B9=89=E0=B8=AD=E0=B8=AA?= =?UTF-8?q?=E0=B8=B4=E0=B8=97=E0=B8=98=E0=B8=B4=E0=B9=8C=E0=B9=80=E0=B8=AB?= =?UTF-8?q?=E0=B8=A1=E0=B8=B7=E0=B8=AD=E0=B8=99=20CHILD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/controllers/CommandController.ts | 8 +++++++- src/interfaces/permission.ts | 7 +++++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/src/controllers/CommandController.ts b/src/controllers/CommandController.ts index f4e335ce..2d8abf02 100644 --- a/src/controllers/CommandController.ts +++ b/src/controllers/CommandController.ts @@ -206,7 +206,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 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"); } From 81e8dadd9bf30d6b653a94b38d96525729942c41 Mon Sep 17 00:00:00 2001 From: harid Date: Tue, 26 May 2026 15:30:51 +0700 Subject: [PATCH 56/83] Migrate update_command_add_shortName #242 --- src/controllers/CommandController.ts | 108 ++++++++++++++---- src/entities/Command.ts | 8 ++ ...9776860350-update_command_add_shortName.ts | 14 +++ 3 files changed, 108 insertions(+), 22 deletions(-) create mode 100644 src/migration/1779776860350-update_command_add_shortName.ts diff --git a/src/controllers/CommandController.ts b/src/controllers/CommandController.ts index a30a3048..92c368e6 100644 --- a/src/controllers/CommandController.ts +++ b/src/controllers/CommandController.ts @@ -372,7 +372,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, @@ -522,7 +522,7 @@ export class CommandController extends Controller { const _command = { id: command.id, status: command.status, - commandNo: command.commandNo, + commandNo: `${command.shortName ?? ""} ${command.commandNo}`.trim(), commandYear: command.commandYear, issue: command.issue, detailHeader: command.detailHeader, @@ -572,6 +572,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(); @@ -1982,7 +2010,7 @@ export class CommandController extends Controller { if (!["C-PM-21", "C-PM-23"].includes(commandCode)) { _command = { issue: issue, - commandNo: command.commandNo == null ? "" : Extension.ToThaiNumber(command.commandNo), + commandNo: command.commandNo == null ? "" : Extension.ToThaiNumber(`${command.shortName ?? ""} ${command.commandNo}`.trim()), commandYear: command.commandYear == null ? "" @@ -2235,7 +2263,7 @@ export class CommandController extends Controller { ); _command = { issue: issue, - commandNo: command.commandNo == null ? "" : Extension.ToThaiNumber(command.commandNo), + commandNo: command.commandNo == null ? "" : Extension.ToThaiNumber(`${command.shortName ?? ""} ${command.commandNo}`.trim()), commandYear: command.commandYear == null ? "" @@ -2374,7 +2402,7 @@ export class CommandController extends Controller { data: { data: _command, issuerOrganizationName: issue, - commandNo: command.commandNo == null ? "" : Extension.ToThaiNumber(command.commandNo), + commandNo: command.commandNo == null ? "" : Extension.ToThaiNumber(`${command.shortName ?? ""} ${command.commandNo}`.trim()), commandYear: command.commandYear == null ? "" @@ -2588,6 +2616,7 @@ export class CommandController extends Controller { let command = new Command(); let commandCode: string = ""; let _null: any = null; + let userProfile: any = null; if ( requestBody.commandId != undefined && requestBody.commandId != null && @@ -2647,6 +2676,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 @@ -2655,24 +2715,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, ); @@ -8658,7 +8722,7 @@ export class CommandController extends Controller { if (issue == null) issue = "..................................."; const _command = { issue: issue, - commandNo: command.commandNo == null ? "" : Extension.ToThaiNumber(command.commandNo), + commandNo: command.commandNo == null ? "" : Extension.ToThaiNumber(`${command.shortName ?? ""} ${command.commandNo}`.trim()), commandYear: command.commandYear == null ? "" @@ -8763,7 +8827,7 @@ export class CommandController extends Controller { data: { data: _command, issuerOrganizationName: issue, - commandNo: command.commandNo == null ? "" : Extension.ToThaiNumber(command.commandNo), + commandNo: command.commandNo == null ? "" : Extension.ToThaiNumber(`${command.shortName ?? ""} ${command.commandNo}`.trim()), commandYear: command.commandYear == null ? "" 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/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\``); + } + +} From f06be7ce77c3213e87e73d70c18c7959ffa29b3f Mon Sep 17 00:00:00 2001 From: waruneeauy Date: Tue, 26 May 2026 15:35:35 +0700 Subject: [PATCH 57/83] fixes for ProfileLeave queries. Changed leaveTypeId to return leaveTypeName from LeaveType master data and added isDeleted filtering for ProfileLeave and 37 other Profile entities. --- src/controllers/ApiManageController.ts | 39 ++++++++++ src/controllers/ApiWebServiceController.ts | 86 +++++++++++++++++++++- 2 files changed, 124 insertions(+), 1 deletion(-) diff --git a/src/controllers/ApiManageController.ts b/src/controllers/ApiManageController.ts index a3a205d1..17f14050 100644 --- a/src/controllers/ApiManageController.ts +++ b/src/controllers/ApiManageController.ts @@ -375,6 +375,11 @@ export class ApiManageController extends Controller { "isUpload", "isDeleted", "isEntry", + "prefixId", + "leaveId", + "leaveTypeId", + "isDeputy", + "isCommission", ]; // ฟิลด์ที่ไม่ต้องการแสดงในผลลัพธ์ // การแทนที่ฟิลด์ ID ด้วยฟิลด์ Name สำหรับ Profile entity @@ -489,6 +494,20 @@ export class ApiManageController extends Controller { }, }; + // การแทนที่ฟิลด์ 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")) { throw new HttpError(HttpStatusCode.FORBIDDEN, "คุณไม่มีสิทธิ์ในการเข้าถึงข้อมูลนี้"); @@ -599,6 +618,26 @@ export class ApiManageController extends Controller { 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 diff --git a/src/controllers/ApiWebServiceController.ts b/src/controllers/ApiWebServiceController.ts index e47c145c..15eb5459 100644 --- a/src/controllers/ApiWebServiceController.ts +++ b/src/controllers/ApiWebServiceController.ts @@ -74,6 +74,18 @@ export class ApiWebServiceController extends Controller { }, }; + // การแทนที่ฟิลด์ 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, @@ -270,7 +282,7 @@ export class ApiWebServiceController extends Controller { let condition: string = "1=1"; if (system == "registry") { tbMain = "Profile"; - condition = `Profile.isActive = true`; + condition = `Profile.isActive = true AND Profile.isDelete = false`; } else if (system == "registry_emp") { tbMain = "ProfileEmployee"; condition = `ProfileEmployee.employeeClass = "PERM"`; @@ -296,6 +308,54 @@ export class ApiWebServiceController extends Controller { 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 @@ -463,6 +523,23 @@ export class ApiWebServiceController extends Controller { }); } + // สำหรับ ProfileLeave: ตรวจสอบฟิลด์ที่ต้องการ join และแปลง propertyKey + const profileLeaveFieldJoins: Record = {}; // alias -> relationName + if (tbMain === "ProfileLeave") { + 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 กับตารารอง @@ -537,6 +614,13 @@ export class ApiWebServiceController extends Controller { }); } + // join สำหรับฟิลด์ ProfileLeave ที่ต้องการดึงค่าจากตารางอื่น + if (tbMain === "ProfileLeave" && Object.keys(profileLeaveFieldJoins).length > 0) { + Object.entries(profileLeaveFieldJoins).forEach(([alias, relationName]) => { + queryBuilder.leftJoin(`${tbMain}.${relationName}`, alias); + }); + } + // join สำหรับ PosMaster เมื่อต้องการดึงค่าจาก Profile (ข้อมูลคนครอง) const posMasterProfileFields: string[] = []; if (tbMain === "PosMaster") { From 2e217a95484fa370518c5c3111ab3f48d11b9590 Mon Sep 17 00:00:00 2001 From: harid Date: Tue, 26 May 2026 15:50:22 +0700 Subject: [PATCH 58/83] no message --- src/controllers/CommandController.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/controllers/CommandController.ts b/src/controllers/CommandController.ts index 92c368e6..5440f3b7 100644 --- a/src/controllers/CommandController.ts +++ b/src/controllers/CommandController.ts @@ -522,7 +522,8 @@ export class CommandController extends Controller { const _command = { id: command.id, status: command.status, - commandNo: `${command.shortName ?? ""} ${command.commandNo}`.trim(), + shortName: command.shortName ?? "", + commandNo: command.commandNo, commandYear: command.commandYear, issue: command.issue, detailHeader: command.detailHeader, From bc418666aceb5c8f37f779062f4492561a5150d8 Mon Sep 17 00:00:00 2001 From: harid Date: Tue, 26 May 2026 16:13:53 +0700 Subject: [PATCH 59/83] fix search commandNo #242 --- src/controllers/CommandController.ts | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/src/controllers/CommandController.ts b/src/controllers/CommandController.ts index 5440f3b7..4630a4f1 100644 --- a/src/controllers/CommandController.ts +++ b/src/controllers/CommandController.ts @@ -356,6 +356,14 @@ export class CommandController extends Controller { { keyword: `%${keyword}%`, }, + ) + .orWhere( + keyword != null && keyword != "" + ? "CONCAT(command.shortName, ' ', command.commandNo) LIKE :keyword" + : "1=1", + { + keyword: `%${keyword}%`, + }, ); }), ) @@ -2011,7 +2019,7 @@ export class CommandController extends Controller { if (!["C-PM-21", "C-PM-23"].includes(commandCode)) { _command = { issue: issue, - commandNo: command.commandNo == null ? "" : Extension.ToThaiNumber(`${command.shortName ?? ""} ${command.commandNo}`.trim()), + commandNo: command.commandNo == null ? "" : Extension.ToThaiNumber(command.commandNo), commandYear: command.commandYear == null ? "" @@ -2264,7 +2272,7 @@ export class CommandController extends Controller { ); _command = { issue: issue, - commandNo: command.commandNo == null ? "" : Extension.ToThaiNumber(`${command.shortName ?? ""} ${command.commandNo}`.trim()), + commandNo: command.commandNo == null ? "" : Extension.ToThaiNumber(command.commandNo), commandYear: command.commandYear == null ? "" @@ -2403,7 +2411,7 @@ export class CommandController extends Controller { data: { data: _command, issuerOrganizationName: issue, - commandNo: command.commandNo == null ? "" : Extension.ToThaiNumber(`${command.shortName ?? ""} ${command.commandNo}`.trim()), + commandNo: command.commandNo == null ? "" : Extension.ToThaiNumber(command.commandNo), commandYear: command.commandYear == null ? "" @@ -8723,7 +8731,7 @@ export class CommandController extends Controller { if (issue == null) issue = "..................................."; const _command = { issue: issue, - commandNo: command.commandNo == null ? "" : Extension.ToThaiNumber(`${command.shortName ?? ""} ${command.commandNo}`.trim()), + commandNo: command.commandNo == null ? "" : Extension.ToThaiNumber(command.commandNo), commandYear: command.commandYear == null ? "" @@ -8828,7 +8836,7 @@ export class CommandController extends Controller { data: { data: _command, issuerOrganizationName: issue, - commandNo: command.commandNo == null ? "" : Extension.ToThaiNumber(`${command.shortName ?? ""} ${command.commandNo}`.trim()), + commandNo: command.commandNo == null ? "" : Extension.ToThaiNumber(command.commandNo), commandYear: command.commandYear == null ? "" From 0aa788fe0b6e8c5eb22e0331ba68d345e09fbe41 Mon Sep 17 00:00:00 2001 From: harid Date: Tue, 26 May 2026 16:42:17 +0700 Subject: [PATCH 60/83] fix search commandNo #242 --- src/controllers/CommandController.ts | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/src/controllers/CommandController.ts b/src/controllers/CommandController.ts index 4630a4f1..5fda4063 100644 --- a/src/controllers/CommandController.ts +++ b/src/controllers/CommandController.ts @@ -343,9 +343,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", { @@ -356,14 +358,6 @@ export class CommandController extends Controller { { keyword: `%${keyword}%`, }, - ) - .orWhere( - keyword != null && keyword != "" - ? "CONCAT(command.shortName, ' ', command.commandNo) LIKE :keyword" - : "1=1", - { - keyword: `%${keyword}%`, - }, ); }), ) From c87b6046857d0fbfc27f89708fbce082ba619191 Mon Sep 17 00:00:00 2001 From: waruneeauy Date: Tue, 26 May 2026 17:37:08 +0700 Subject: [PATCH 61/83] fixed bug api service leave type name --- src/controllers/ApiWebServiceController.ts | 42 +++++++++++++--------- 1 file changed, 25 insertions(+), 17 deletions(-) diff --git a/src/controllers/ApiWebServiceController.ts b/src/controllers/ApiWebServiceController.ts index 15eb5459..fbf0ab78 100644 --- a/src/controllers/ApiWebServiceController.ts +++ b/src/controllers/ApiWebServiceController.ts @@ -525,20 +525,19 @@ export class ApiWebServiceController extends Controller { // สำหรับ ProfileLeave: ตรวจสอบฟิลด์ที่ต้องการ join และแปลง propertyKey const profileLeaveFieldJoins: Record = {}; // alias -> relationName - if (tbMain === "ProfileLeave") { - 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}`; - } + // 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; - }); - } + } + return key; + }); const queryBuilder = repo.createQueryBuilder(tbMain); @@ -615,10 +614,19 @@ export class ApiWebServiceController extends Controller { } // join สำหรับฟิลด์ ProfileLeave ที่ต้องการดึงค่าจากตารางอื่น - if (tbMain === "ProfileLeave" && Object.keys(profileLeaveFieldJoins).length > 0) { - Object.entries(profileLeaveFieldJoins).forEach(([alias, relationName]) => { - queryBuilder.leftJoin(`${tbMain}.${relationName}`, alias); - }); + 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 (ข้อมูลคนครอง) From cc6696cec8452e0a51769af8a680448ac1eb2fca Mon Sep 17 00:00:00 2001 From: Adisak Date: Tue, 26 May 2026 18:10:34 +0700 Subject: [PATCH 62/83] fix: searh org --- src/controllers/EmployeePositionController.ts | 28 ++++++------ .../EmployeeTempPositionController.ts | 28 ++++++------ src/controllers/PositionController.ts | 44 ++++++++----------- src/controllers/ProfileController.ts | 30 ++++++------- src/controllers/ProfileEmployeeController.ts | 20 ++++----- 5 files changed, 70 insertions(+), 80 deletions(-) diff --git a/src/controllers/EmployeePositionController.ts b/src/controllers/EmployeePositionController.ts index 26146106..f5d46585 100644 --- a/src/controllers/EmployeePositionController.ts +++ b/src/controllers/EmployeePositionController.ts @@ -1058,11 +1058,11 @@ export class EmployeePositionController extends Controller { let checkChildConditions: any = {}; let keywordAsInt: any; let searchShortName = "1=1"; - let searchShortName0 = `CONCAT_WS(" ",orgRoot.orgRootShortName,posMaster.posMasterNoPrefix,posMaster.posMasterNo,posMaster.posMasterNoSuffix)`; - let searchShortName1 = `CONCAT_WS(" ",orgChild1.orgChild1ShortName,posMaster.posMasterNoPrefix,posMaster.posMasterNo,posMaster.posMasterNoSuffix)`; - let searchShortName2 = `CONCAT_WS(" ",orgChild2.orgChild2ShortName,posMaster.posMasterNoPrefix,posMaster.posMasterNo,posMaster.posMasterNoSuffix)`; - let searchShortName3 = `CONCAT_WS(" ",orgChild3.orgChild3ShortName,posMaster.posMasterNoPrefix,posMaster.posMasterNo,posMaster.posMasterNoSuffix)`; - let searchShortName4 = `CONCAT_WS(" ",orgChild4.orgChild4ShortName,posMaster.posMasterNoPrefix,posMaster.posMasterNo,posMaster.posMasterNoSuffix)`; + let searchShortName0 = `CONCAT_WS(' ',orgRoot.orgRootShortName,posMaster.posMasterNoPrefix,posMaster.posMasterNo,posMaster.posMasterNoSuffix)`; + let searchShortName1 = `CONCAT_WS(' ',orgChild1.orgChild1ShortName,posMaster.posMasterNoPrefix,posMaster.posMasterNo,posMaster.posMasterNoSuffix)`; + let searchShortName2 = `CONCAT_WS(' ',orgChild2.orgChild2ShortName,posMaster.posMasterNoPrefix,posMaster.posMasterNo,posMaster.posMasterNoSuffix)`; + let searchShortName3 = `CONCAT_WS(' ',orgChild3.orgChild3ShortName,posMaster.posMasterNoPrefix,posMaster.posMasterNo,posMaster.posMasterNoSuffix)`; + let searchShortName4 = `CONCAT_WS(' ',orgChild4.orgChild4ShortName,posMaster.posMasterNoPrefix,posMaster.posMasterNo,posMaster.posMasterNoSuffix)`; let _data = await new permission().PermissionOrgList(request, "SYS_ORG_EMP"); if (body.type === 0) { typeCondition = { @@ -1072,7 +1072,7 @@ export class EmployeePositionController extends Controller { checkChildConditions = { orgChild1Id: IsNull(), }; - searchShortName = `CONCAT_WS(" ",orgRoot.orgRootShortName,posMaster.posMasterNoPrefix,posMaster.posMasterNo,posMaster.posMasterNoSuffix) like '%${body.keyword}%'`; + searchShortName = `CONCAT_WS(' ',orgRoot.orgRootShortName,posMaster.posMasterNoPrefix,posMaster.posMasterNo,posMaster.posMasterNoSuffix) like '%${body.keyword}%'`; } else { } } else if (body.type === 1) { @@ -1083,7 +1083,7 @@ export class EmployeePositionController extends Controller { checkChildConditions = { orgChild2Id: IsNull(), }; - searchShortName = `CONCAT_WS(" ",orgChild1.orgChild1ShortName,posMaster.posMasterNoPrefix,posMaster.posMasterNo,posMaster.posMasterNoSuffix) like '%${body.keyword}%'`; + searchShortName = `CONCAT_WS(' ',orgChild1.orgChild1ShortName,posMaster.posMasterNoPrefix,posMaster.posMasterNo,posMaster.posMasterNoSuffix) like '%${body.keyword}%'`; } else { } } else if (body.type === 2) { @@ -1094,7 +1094,7 @@ export class EmployeePositionController extends Controller { checkChildConditions = { orgChild3Id: IsNull(), }; - searchShortName = `CONCAT_WS(" ",orgChild2.orgChild2ShortName,posMaster.posMasterNoPrefix,posMaster.posMasterNo,posMaster.posMasterNoSuffix) like '%${body.keyword}%'`; + searchShortName = `CONCAT_WS(' ',orgChild2.orgChild2ShortName,posMaster.posMasterNoPrefix,posMaster.posMasterNo,posMaster.posMasterNoSuffix) like '%${body.keyword}%'`; } else { } } else if (body.type === 3) { @@ -1105,14 +1105,14 @@ export class EmployeePositionController extends Controller { checkChildConditions = { orgChild4Id: IsNull(), }; - searchShortName = `CONCAT_WS(" ",orgChild3.orgChild3ShortName,posMaster.posMasterNoPrefix,posMaster.posMasterNo,posMaster.posMasterNoSuffix) like '%${body.keyword}%'`; + searchShortName = `CONCAT_WS(' ',orgChild3.orgChild3ShortName,posMaster.posMasterNoPrefix,posMaster.posMasterNo,posMaster.posMasterNoSuffix) like '%${body.keyword}%'`; } else { } } else if (body.type === 4) { typeCondition = { orgChild4Id: body.id, }; - searchShortName = `CONCAT_WS(" ",orgChild4.orgChild4ShortName,posMaster.posMasterNoPrefix,posMaster.posMasterNo,posMaster.posMasterNoSuffix) like '%${body.keyword}%'`; + searchShortName = `CONCAT_WS(' ',orgChild4.orgChild4ShortName,posMaster.posMasterNoPrefix,posMaster.posMasterNo,posMaster.posMasterNoSuffix) like '%${body.keyword}%'`; } let findPosition: any; let masterId = new Array(); @@ -1140,10 +1140,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 +1156,7 @@ export class EmployeePositionController extends Controller { ...(body.keyword && (masterId.length > 0 ? { id: In(masterId) } - : { posMasterNo: Like(`%${body.keyword}%`) })), + : keywordAsInt != null ? { posMasterNo: keywordAsInt } : {})), }, ]; diff --git a/src/controllers/EmployeeTempPositionController.ts b/src/controllers/EmployeeTempPositionController.ts index e5229e67..da6398b9 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}%`) })), + : keywordAsInt != null ? { posMasterNo: keywordAsInt } : {})), }, ]; let query = AppDataSource.getRepository(EmployeeTempPosMaster) diff --git a/src/controllers/PositionController.ts b/src/controllers/PositionController.ts index 2f23bdaa..f6802130 100644 --- a/src/controllers/PositionController.ts +++ b/src/controllers/PositionController.ts @@ -1787,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 สิทธิ์ @@ -1823,7 +1821,7 @@ export class PositionController extends Controller { ...(body.keyword && (masterId.length > 0 ? { id: In(masterId) } - : { posMasterNo: Like(`%${body.keyword}%`) })), + : keywordAsInt != null ? { posMasterNo: keywordAsInt } : {})), }, ]; let [posMaster, total] = await AppDataSource.getRepository(PosMaster) @@ -2164,11 +2162,11 @@ export class PositionController extends Controller { let checkChildConditions: any = {}; let keywordAsInt: any; let searchShortName = "1=1"; - let searchShortName0 = `CONCAT_WS(" ",orgRoot.orgRootShortName,posMaster.posMasterNoPrefix,posMaster.posMasterNo,posMaster.posMasterNoSuffix)`; - let searchShortName1 = `CONCAT_WS(" ",orgChild1.orgChild1ShortName,posMaster.posMasterNoPrefix,posMaster.posMasterNo,posMaster.posMasterNoSuffix)`; - let searchShortName2 = `CONCAT_WS(" ",orgChild2.orgChild2ShortName,posMaster.posMasterNoPrefix,posMaster.posMasterNo,posMaster.posMasterNoSuffix)`; - let searchShortName3 = `CONCAT_WS(" ",orgChild3.orgChild3ShortName,posMaster.posMasterNoPrefix,posMaster.posMasterNo,posMaster.posMasterNoSuffix)`; - let searchShortName4 = `CONCAT_WS(" ",orgChild4.orgChild4ShortName,posMaster.posMasterNoPrefix,posMaster.posMasterNo,posMaster.posMasterNoSuffix)`; + let searchShortName0 = `CONCAT_WS(' ',orgRoot.orgRootShortName,posMaster.posMasterNoPrefix,posMaster.posMasterNo,posMaster.posMasterNoSuffix)`; + let searchShortName1 = `CONCAT_WS(' ',orgChild1.orgChild1ShortName,posMaster.posMasterNoPrefix,posMaster.posMasterNo,posMaster.posMasterNoSuffix)`; + let searchShortName2 = `CONCAT_WS(' ',orgChild2.orgChild2ShortName,posMaster.posMasterNoPrefix,posMaster.posMasterNo,posMaster.posMasterNoSuffix)`; + let searchShortName3 = `CONCAT_WS(' ',orgChild3.orgChild3ShortName,posMaster.posMasterNoPrefix,posMaster.posMasterNo,posMaster.posMasterNoSuffix)`; + let searchShortName4 = `CONCAT_WS(' ',orgChild4.orgChild4ShortName,posMaster.posMasterNoPrefix,posMaster.posMasterNo,posMaster.posMasterNoSuffix)`; let _data = await new permission().PermissionOrgList(request, "SYS_ORG"); if (body.type === 0) { typeCondition = { @@ -2178,7 +2176,7 @@ export class PositionController extends Controller { checkChildConditions = { orgChild1Id: IsNull(), }; - searchShortName = `CONCAT_WS(" ",orgRoot.orgRootShortName,posMaster.posMasterNoPrefix,posMaster.posMasterNo,posMaster.posMasterNoSuffix) like '%${body.keyword}%'`; + searchShortName = `CONCAT_WS(' ',orgRoot.orgRootShortName,posMaster.posMasterNoPrefix,posMaster.posMasterNo,posMaster.posMasterNoSuffix) like '%${body.keyword}%'`; } } else if (body.type === 1) { typeCondition = { @@ -2188,7 +2186,7 @@ export class PositionController extends Controller { checkChildConditions = { orgChild2Id: IsNull(), }; - searchShortName = `CONCAT_WS(" ",orgChild1.orgChild1ShortName,posMaster.posMasterNoPrefix,posMaster.posMasterNo,posMaster.posMasterNoSuffix) like '%${body.keyword}%'`; + searchShortName = `CONCAT_WS(' ',orgChild1.orgChild1ShortName,posMaster.posMasterNoPrefix,posMaster.posMasterNo,posMaster.posMasterNoSuffix) like '%${body.keyword}%'`; } } else if (body.type === 2) { typeCondition = { @@ -2198,7 +2196,7 @@ export class PositionController extends Controller { checkChildConditions = { orgChild3Id: IsNull(), }; - searchShortName = `CONCAT_WS(" ",orgChild2.orgChild2ShortName,posMaster.posMasterNoPrefix,posMaster.posMasterNo,posMaster.posMasterNoSuffix) like '%${body.keyword}%'`; + searchShortName = `CONCAT_WS(' ',orgChild2.orgChild2ShortName,posMaster.posMasterNoPrefix,posMaster.posMasterNo,posMaster.posMasterNoSuffix) like '%${body.keyword}%'`; } } else if (body.type === 3) { typeCondition = { @@ -2208,13 +2206,13 @@ export class PositionController extends Controller { checkChildConditions = { orgChild4Id: IsNull(), }; - searchShortName = `CONCAT_WS(" ",orgChild3.orgChild3ShortName,posMaster.posMasterNoPrefix,posMaster.posMasterNo,posMaster.posMasterNoSuffix) like '%${body.keyword}%'`; + searchShortName = `CONCAT_WS(' ',orgChild3.orgChild3ShortName,posMaster.posMasterNoPrefix,posMaster.posMasterNo,posMaster.posMasterNoSuffix) like '%${body.keyword}%'`; } } else if (body.type === 4) { typeCondition = { orgChild4Id: body.id, }; - searchShortName = `CONCAT_WS(" ",orgChild4.orgChild4ShortName,posMaster.posMasterNoPrefix,posMaster.posMasterNo,posMaster.posMasterNoSuffix) like '%${body.keyword}%'`; + searchShortName = `CONCAT_WS(' ',orgChild4.orgChild4ShortName,posMaster.posMasterNoPrefix,posMaster.posMasterNo,posMaster.posMasterNoSuffix) like '%${body.keyword}%'`; } let findPosition: any; let masterId = new Array(); @@ -2251,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)]; } @@ -2281,7 +2277,7 @@ export class PositionController extends Controller { ...(body.keyword && (masterId.length > 0 ? { id: In(masterId) } - : { posMasterNo: Like(`%${body.keyword}%`) })), + : keywordAsInt != null ? { posMasterNo: keywordAsInt } : {})), }, ]; @@ -5478,10 +5474,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)]; } @@ -5508,7 +5502,7 @@ export class PositionController extends Controller { ...(body.keyword && (masterId.length > 0 ? { id: In(masterId) } - : { posMasterNo: Like(`%${body.keyword}%`) })), + : keywordAsInt != null ? { posMasterNo: keywordAsInt } : {})), ...(!body.isAll && { isCondition: true }), }, ]; diff --git a/src/controllers/ProfileController.ts b/src/controllers/ProfileController.ts index 2b626697..468a803b 100644 --- a/src/controllers/ProfileController.ts +++ b/src/controllers/ProfileController.ts @@ -6026,11 +6026,11 @@ export class ProfileController extends Controller { } else if (searchField == "posNo") { queryLike = ` CASE - WHEN current_holders.orgChild4Id IS NOT NULL THEN CONCAT_WS(" ", orgChild4.orgChild4ShortName, current_holders.posMasterNoPrefix, current_holders.posMasterNo, current_holders.posMasterNoSuffix) - WHEN current_holders.orgChild3Id IS NOT NULL THEN CONCAT_WS(" ", orgChild3.orgChild3ShortName, current_holders.posMasterNoPrefix, current_holders.posMasterNo, current_holders.posMasterNoSuffix) - WHEN current_holders.orgChild2Id IS NOT NULL THEN CONCAT_WS(" ", orgChild2.orgChild2ShortName, current_holders.posMasterNoPrefix, current_holders.posMasterNo, current_holders.posMasterNoSuffix) - WHEN current_holders.orgChild1Id IS NOT NULL THEN CONCAT_WS(" ", orgChild1.orgChild1ShortName, current_holders.posMasterNoPrefix, current_holders.posMasterNo, current_holders.posMasterNoSuffix) - ELSE CONCAT_WS(" ", orgRoot.orgRootShortName, current_holders.posMasterNoPrefix, current_holders.posMasterNo, current_holders.posMasterNoSuffix) + WHEN current_holders.orgChild4Id IS NOT NULL THEN CONCAT_WS(' ', orgChild4.orgChild4ShortName, current_holders.posMasterNoPrefix, current_holders.posMasterNo, current_holders.posMasterNoSuffix) + WHEN current_holders.orgChild3Id IS NOT NULL THEN CONCAT_WS(' ', orgChild3.orgChild3ShortName, current_holders.posMasterNoPrefix, current_holders.posMasterNo, current_holders.posMasterNoSuffix) + WHEN current_holders.orgChild2Id IS NOT NULL THEN CONCAT_WS(' ', orgChild2.orgChild2ShortName, current_holders.posMasterNoPrefix, current_holders.posMasterNo, current_holders.posMasterNoSuffix) + WHEN current_holders.orgChild1Id IS NOT NULL THEN CONCAT_WS(' ', orgChild1.orgChild1ShortName, current_holders.posMasterNoPrefix, current_holders.posMasterNo, current_holders.posMasterNoSuffix) + ELSE CONCAT_WS(' ', orgRoot.orgRootShortName, current_holders.posMasterNoPrefix, current_holders.posMasterNo, current_holders.posMasterNoSuffix) END LIKE :keyword `; } @@ -6616,11 +6616,11 @@ export class ProfileController extends Controller { } else if (searchField == "posNo") { queryLike = ` CASE - WHEN current_holders.orgChild4Id IS NOT NULL THEN CONCAT_WS(" ", orgChild4.orgChild4ShortName, current_holders.posMasterNoPrefix, current_holders.posMasterNo, current_holders.posMasterNoSuffix) - WHEN current_holders.orgChild3Id IS NOT NULL THEN CONCAT_WS(" ", orgChild3.orgChild3ShortName, current_holders.posMasterNoPrefix, current_holders.posMasterNo, current_holders.posMasterNoSuffix) - WHEN current_holders.orgChild2Id IS NOT NULL THEN CONCAT_WS(" ", orgChild2.orgChild2ShortName, current_holders.posMasterNoPrefix, current_holders.posMasterNo, current_holders.posMasterNoSuffix) - WHEN current_holders.orgChild1Id IS NOT NULL THEN CONCAT_WS(" ", orgChild1.orgChild1ShortName, current_holders.posMasterNoPrefix, current_holders.posMasterNo, current_holders.posMasterNoSuffix) - ELSE CONCAT_WS(" ", orgRoot.orgRootShortName, current_holders.posMasterNoPrefix, current_holders.posMasterNo, current_holders.posMasterNoSuffix) + WHEN current_holders.orgChild4Id IS NOT NULL THEN CONCAT_WS(' ', orgChild4.orgChild4ShortName, current_holders.posMasterNoPrefix, current_holders.posMasterNo, current_holders.posMasterNoSuffix) + WHEN current_holders.orgChild3Id IS NOT NULL THEN CONCAT_WS(' ', orgChild3.orgChild3ShortName, current_holders.posMasterNoPrefix, current_holders.posMasterNo, current_holders.posMasterNoSuffix) + WHEN current_holders.orgChild2Id IS NOT NULL THEN CONCAT_WS(' ', orgChild2.orgChild2ShortName, current_holders.posMasterNoPrefix, current_holders.posMasterNo, current_holders.posMasterNoSuffix) + WHEN current_holders.orgChild1Id IS NOT NULL THEN CONCAT_WS(' ', orgChild1.orgChild1ShortName, current_holders.posMasterNoPrefix, current_holders.posMasterNo, current_holders.posMasterNoSuffix) + ELSE CONCAT_WS(' ', orgRoot.orgRootShortName, current_holders.posMasterNoPrefix, current_holders.posMasterNo, current_holders.posMasterNoSuffix) END LIKE :keyword `; } @@ -7010,11 +7010,11 @@ export class ProfileController extends Controller { } else if (searchField == "posNo") { queryLike = ` CASE - WHEN current_holders.orgChild4Id IS NOT NULL THEN CONCAT_WS(" ", orgChild4.orgChild4ShortName, current_holders.posMasterNoPrefix, current_holders.posMasterNo, current_holders.posMasterNoSuffix) - WHEN current_holders.orgChild3Id IS NOT NULL THEN CONCAT_WS(" ", orgChild3.orgChild3ShortName, current_holders.posMasterNoPrefix, current_holders.posMasterNo, current_holders.posMasterNoSuffix) - WHEN current_holders.orgChild2Id IS NOT NULL THEN CONCAT_WS(" ", orgChild2.orgChild2ShortName, current_holders.posMasterNoPrefix, current_holders.posMasterNo, current_holders.posMasterNoSuffix) - WHEN current_holders.orgChild1Id IS NOT NULL THEN CONCAT_WS(" ", orgChild1.orgChild1ShortName, current_holders.posMasterNoPrefix, current_holders.posMasterNo, current_holders.posMasterNoSuffix) - ELSE CONCAT_WS(" ", orgRoot.orgRootShortName, current_holders.posMasterNoPrefix, current_holders.posMasterNo, current_holders.posMasterNoSuffix) + WHEN current_holders.orgChild4Id IS NOT NULL THEN CONCAT_WS(' ', orgChild4.orgChild4ShortName, current_holders.posMasterNoPrefix, current_holders.posMasterNo, current_holders.posMasterNoSuffix) + WHEN current_holders.orgChild3Id IS NOT NULL THEN CONCAT_WS(' ', orgChild3.orgChild3ShortName, current_holders.posMasterNoPrefix, current_holders.posMasterNo, current_holders.posMasterNoSuffix) + WHEN current_holders.orgChild2Id IS NOT NULL THEN CONCAT_WS(' ', orgChild2.orgChild2ShortName, current_holders.posMasterNoPrefix, current_holders.posMasterNo, current_holders.posMasterNoSuffix) + WHEN current_holders.orgChild1Id IS NOT NULL THEN CONCAT_WS(' ', orgChild1.orgChild1ShortName, current_holders.posMasterNoPrefix, current_holders.posMasterNo, current_holders.posMasterNoSuffix) + ELSE CONCAT_WS(' ', orgRoot.orgRootShortName, current_holders.posMasterNoPrefix, current_holders.posMasterNo, current_holders.posMasterNoSuffix) END LIKE :keyword `; } diff --git a/src/controllers/ProfileEmployeeController.ts b/src/controllers/ProfileEmployeeController.ts index 14104100..faa8cdd3 100644 --- a/src/controllers/ProfileEmployeeController.ts +++ b/src/controllers/ProfileEmployeeController.ts @@ -2848,11 +2848,11 @@ export class ProfileEmployeeController extends Controller { } else if (searchField == "posNo") { queryLike = ` CASE - WHEN current_holders.orgChild4Id IS NOT NULL THEN CONCAT_WS(" ", orgChild4.orgChild4ShortName, current_holders.posMasterNoPrefix, current_holders.posMasterNo, current_holders.posMasterNoSuffix) - WHEN current_holders.orgChild3Id IS NOT NULL THEN CONCAT_WS(" ", orgChild3.orgChild3ShortName, current_holders.posMasterNoPrefix, current_holders.posMasterNo, current_holders.posMasterNoSuffix) - WHEN current_holders.orgChild2Id IS NOT NULL THEN CONCAT_WS(" ", orgChild2.orgChild2ShortName, current_holders.posMasterNoPrefix, current_holders.posMasterNo, current_holders.posMasterNoSuffix) - WHEN current_holders.orgChild1Id IS NOT NULL THEN CONCAT_WS(" ", orgChild1.orgChild1ShortName, current_holders.posMasterNoPrefix, current_holders.posMasterNo, current_holders.posMasterNoSuffix) - ELSE CONCAT_WS(" ", orgRoot.orgRootShortName, current_holders.posMasterNoPrefix, current_holders.posMasterNo, current_holders.posMasterNoSuffix) + WHEN current_holders.orgChild4Id IS NOT NULL THEN CONCAT_WS(' ', orgChild4.orgChild4ShortName, current_holders.posMasterNoPrefix, current_holders.posMasterNo, current_holders.posMasterNoSuffix) + WHEN current_holders.orgChild3Id IS NOT NULL THEN CONCAT_WS(' ', orgChild3.orgChild3ShortName, current_holders.posMasterNoPrefix, current_holders.posMasterNo, current_holders.posMasterNoSuffix) + WHEN current_holders.orgChild2Id IS NOT NULL THEN CONCAT_WS(' ', orgChild2.orgChild2ShortName, current_holders.posMasterNoPrefix, current_holders.posMasterNo, current_holders.posMasterNoSuffix) + WHEN current_holders.orgChild1Id IS NOT NULL THEN CONCAT_WS(' ', orgChild1.orgChild1ShortName, current_holders.posMasterNoPrefix, current_holders.posMasterNo, current_holders.posMasterNoSuffix) + ELSE CONCAT_WS(' ', orgRoot.orgRootShortName, current_holders.posMasterNoPrefix, current_holders.posMasterNo, current_holders.posMasterNoSuffix) END LIKE :keyword `; } @@ -3207,11 +3207,11 @@ export class ProfileEmployeeController extends Controller { } else if (searchField == "posNo") { queryLike = ` CASE - WHEN current_holders.orgChild4Id IS NOT NULL THEN CONCAT_WS(" ", orgChild4.orgChild4ShortName, current_holders.posMasterNoPrefix, current_holders.posMasterNo, current_holders.posMasterNoSuffix) - WHEN current_holders.orgChild3Id IS NOT NULL THEN CONCAT_WS(" ", orgChild3.orgChild3ShortName, current_holders.posMasterNoPrefix, current_holders.posMasterNo, current_holders.posMasterNoSuffix) - WHEN current_holders.orgChild2Id IS NOT NULL THEN CONCAT_WS(" ", orgChild2.orgChild2ShortName, current_holders.posMasterNoPrefix, current_holders.posMasterNo, current_holders.posMasterNoSuffix) - WHEN current_holders.orgChild1Id IS NOT NULL THEN CONCAT_WS(" ", orgChild1.orgChild1ShortName, current_holders.posMasterNoPrefix, current_holders.posMasterNo, current_holders.posMasterNoSuffix) - ELSE CONCAT_WS(" ", orgRoot.orgRootShortName, current_holders.posMasterNoPrefix, current_holders.posMasterNo, current_holders.posMasterNoSuffix) + WHEN current_holders.orgChild4Id IS NOT NULL THEN CONCAT_WS(' ', orgChild4.orgChild4ShortName, current_holders.posMasterNoPrefix, current_holders.posMasterNo, current_holders.posMasterNoSuffix) + WHEN current_holders.orgChild3Id IS NOT NULL THEN CONCAT_WS(' ', orgChild3.orgChild3ShortName, current_holders.posMasterNoPrefix, current_holders.posMasterNo, current_holders.posMasterNoSuffix) + WHEN current_holders.orgChild2Id IS NOT NULL THEN CONCAT_WS(' ', orgChild2.orgChild2ShortName, current_holders.posMasterNoPrefix, current_holders.posMasterNo, current_holders.posMasterNoSuffix) + WHEN current_holders.orgChild1Id IS NOT NULL THEN CONCAT_WS(' ', orgChild1.orgChild1ShortName, current_holders.posMasterNoPrefix, current_holders.posMasterNo, current_holders.posMasterNoSuffix) + ELSE CONCAT_WS(' ', orgRoot.orgRootShortName, current_holders.posMasterNoPrefix, current_holders.posMasterNo, current_holders.posMasterNoSuffix) END LIKE :keyword `; } From 8b08f8b5c85dd9a55bdf8d78b1912b421ec1de06 Mon Sep 17 00:00:00 2001 From: Adisak Date: Tue, 26 May 2026 18:34:47 +0700 Subject: [PATCH 63/83] fix search --- src/controllers/EmployeePositionController.ts | 2 +- src/controllers/EmployeeTempPositionController.ts | 2 +- src/controllers/PositionController.ts | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/controllers/EmployeePositionController.ts b/src/controllers/EmployeePositionController.ts index f5d46585..415e7604 100644 --- a/src/controllers/EmployeePositionController.ts +++ b/src/controllers/EmployeePositionController.ts @@ -1156,7 +1156,7 @@ export class EmployeePositionController extends Controller { ...(body.keyword && (masterId.length > 0 ? { id: In(masterId) } - : keywordAsInt != null ? { posMasterNo: keywordAsInt } : {})), + : /^\d+$/.test(body.keyword) ? { posMasterNo: keywordAsInt } : { posMasterNo: Like(`%${body.keyword}%`) })), }, ]; diff --git a/src/controllers/EmployeeTempPositionController.ts b/src/controllers/EmployeeTempPositionController.ts index da6398b9..ec17bef5 100644 --- a/src/controllers/EmployeeTempPositionController.ts +++ b/src/controllers/EmployeeTempPositionController.ts @@ -875,7 +875,7 @@ export class EmployeeTempPositionController extends Controller { ...(body.keyword && (masterId.length > 0 ? { id: In(masterId) } - : keywordAsInt != null ? { posMasterNo: keywordAsInt } : {})), + : /^\d+$/.test(body.keyword) ? { posMasterNo: keywordAsInt } : { posMasterNo: Like(`%${body.keyword}%`) })), }, ]; let query = AppDataSource.getRepository(EmployeeTempPosMaster) diff --git a/src/controllers/PositionController.ts b/src/controllers/PositionController.ts index f6802130..a91a1b22 100644 --- a/src/controllers/PositionController.ts +++ b/src/controllers/PositionController.ts @@ -1821,7 +1821,7 @@ export class PositionController extends Controller { ...(body.keyword && (masterId.length > 0 ? { id: In(masterId) } - : keywordAsInt != null ? { posMasterNo: keywordAsInt } : {})), + : /^\d+$/.test(body.keyword) ? { posMasterNo: keywordAsInt } : { posMasterNo: Like(`%${body.keyword}%`) })), }, ]; let [posMaster, total] = await AppDataSource.getRepository(PosMaster) @@ -2277,7 +2277,7 @@ export class PositionController extends Controller { ...(body.keyword && (masterId.length > 0 ? { id: In(masterId) } - : keywordAsInt != null ? { posMasterNo: keywordAsInt } : {})), + : /^\d+$/.test(body.keyword) ? { posMasterNo: keywordAsInt } : { posMasterNo: Like(`%${body.keyword}%`) })), }, ]; @@ -5502,7 +5502,7 @@ export class PositionController extends Controller { ...(body.keyword && (masterId.length > 0 ? { id: In(masterId) } - : keywordAsInt != null ? { posMasterNo: keywordAsInt } : {})), + : /^\d+$/.test(body.keyword) ? { posMasterNo: keywordAsInt } : { posMasterNo: Like(`%${body.keyword}%`) })), ...(!body.isAll && { isCondition: true }), }, ]; From 238c4c092f8a37c3e223babe176c51b511abf536 Mon Sep 17 00:00:00 2001 From: Adisak Date: Wed, 27 May 2026 09:51:10 +0700 Subject: [PATCH 64/83] fix search concat_ws skip ' ' --- src/controllers/EmployeePositionController.ts | 20 +++---- src/controllers/PositionController.ts | 60 +++++++++---------- src/controllers/ProfileController.ts | 30 +++++----- src/controllers/ProfileEmployeeController.ts | 20 +++---- 4 files changed, 65 insertions(+), 65 deletions(-) diff --git a/src/controllers/EmployeePositionController.ts b/src/controllers/EmployeePositionController.ts index 415e7604..ecf11619 100644 --- a/src/controllers/EmployeePositionController.ts +++ b/src/controllers/EmployeePositionController.ts @@ -1058,11 +1058,11 @@ export class EmployeePositionController extends Controller { let checkChildConditions: any = {}; let keywordAsInt: any; let searchShortName = "1=1"; - let searchShortName0 = `CONCAT_WS(' ',orgRoot.orgRootShortName,posMaster.posMasterNoPrefix,posMaster.posMasterNo,posMaster.posMasterNoSuffix)`; - let searchShortName1 = `CONCAT_WS(' ',orgChild1.orgChild1ShortName,posMaster.posMasterNoPrefix,posMaster.posMasterNo,posMaster.posMasterNoSuffix)`; - let searchShortName2 = `CONCAT_WS(' ',orgChild2.orgChild2ShortName,posMaster.posMasterNoPrefix,posMaster.posMasterNo,posMaster.posMasterNoSuffix)`; - let searchShortName3 = `CONCAT_WS(' ',orgChild3.orgChild3ShortName,posMaster.posMasterNoPrefix,posMaster.posMasterNo,posMaster.posMasterNoSuffix)`; - let searchShortName4 = `CONCAT_WS(' ',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,''))`; let _data = await new permission().PermissionOrgList(request, "SYS_ORG_EMP"); if (body.type === 0) { typeCondition = { @@ -1072,7 +1072,7 @@ export class EmployeePositionController extends Controller { checkChildConditions = { orgChild1Id: IsNull(), }; - searchShortName = `CONCAT_WS(' ',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) { @@ -1083,7 +1083,7 @@ export class EmployeePositionController extends Controller { checkChildConditions = { orgChild2Id: IsNull(), }; - searchShortName = `CONCAT_WS(' ',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) { @@ -1094,7 +1094,7 @@ export class EmployeePositionController extends Controller { checkChildConditions = { orgChild3Id: IsNull(), }; - searchShortName = `CONCAT_WS(' ',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) { @@ -1105,14 +1105,14 @@ export class EmployeePositionController extends Controller { checkChildConditions = { orgChild4Id: IsNull(), }; - searchShortName = `CONCAT_WS(' ',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_WS(' ',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}%'`; } let findPosition: any; let masterId = new Array(); diff --git a/src/controllers/PositionController.ts b/src/controllers/PositionController.ts index a91a1b22..7ed5330c 100644 --- a/src/controllers/PositionController.ts +++ b/src/controllers/PositionController.ts @@ -1688,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 = { @@ -1702,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) { @@ -1713,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) { @@ -1724,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) { @@ -1735,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; @@ -2162,11 +2162,11 @@ export class PositionController extends Controller { let checkChildConditions: any = {}; let keywordAsInt: any; let searchShortName = "1=1"; - let searchShortName0 = `CONCAT_WS(' ',orgRoot.orgRootShortName,posMaster.posMasterNoPrefix,posMaster.posMasterNo,posMaster.posMasterNoSuffix)`; - let searchShortName1 = `CONCAT_WS(' ',orgChild1.orgChild1ShortName,posMaster.posMasterNoPrefix,posMaster.posMasterNo,posMaster.posMasterNoSuffix)`; - let searchShortName2 = `CONCAT_WS(' ',orgChild2.orgChild2ShortName,posMaster.posMasterNoPrefix,posMaster.posMasterNo,posMaster.posMasterNoSuffix)`; - let searchShortName3 = `CONCAT_WS(' ',orgChild3.orgChild3ShortName,posMaster.posMasterNoPrefix,posMaster.posMasterNo,posMaster.posMasterNoSuffix)`; - let searchShortName4 = `CONCAT_WS(' ',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,''))`; let _data = await new permission().PermissionOrgList(request, "SYS_ORG"); if (body.type === 0) { typeCondition = { @@ -2176,7 +2176,7 @@ export class PositionController extends Controller { checkChildConditions = { orgChild1Id: IsNull(), }; - searchShortName = `CONCAT_WS(' ',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 if (body.type === 1) { typeCondition = { @@ -2186,7 +2186,7 @@ export class PositionController extends Controller { checkChildConditions = { orgChild2Id: IsNull(), }; - searchShortName = `CONCAT_WS(' ',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 if (body.type === 2) { typeCondition = { @@ -2196,7 +2196,7 @@ export class PositionController extends Controller { checkChildConditions = { orgChild3Id: IsNull(), }; - searchShortName = `CONCAT_WS(' ',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 if (body.type === 3) { typeCondition = { @@ -2206,13 +2206,13 @@ export class PositionController extends Controller { checkChildConditions = { orgChild4Id: IsNull(), }; - searchShortName = `CONCAT_WS(' ',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 if (body.type === 4) { typeCondition = { orgChild4Id: body.id, }; - searchShortName = `CONCAT_WS(' ',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}%'`; } let findPosition: any; let masterId = new Array(); @@ -5351,11 +5351,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); @@ -5397,7 +5397,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) { @@ -5408,7 +5408,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) { @@ -5419,7 +5419,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) { @@ -5430,14 +5430,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(); diff --git a/src/controllers/ProfileController.ts b/src/controllers/ProfileController.ts index 468a803b..92943776 100644 --- a/src/controllers/ProfileController.ts +++ b/src/controllers/ProfileController.ts @@ -6026,11 +6026,11 @@ export class ProfileController extends Controller { } else if (searchField == "posNo") { queryLike = ` CASE - WHEN current_holders.orgChild4Id IS NOT NULL THEN CONCAT_WS(' ', orgChild4.orgChild4ShortName, current_holders.posMasterNoPrefix, current_holders.posMasterNo, current_holders.posMasterNoSuffix) - WHEN current_holders.orgChild3Id IS NOT NULL THEN CONCAT_WS(' ', orgChild3.orgChild3ShortName, current_holders.posMasterNoPrefix, current_holders.posMasterNo, current_holders.posMasterNoSuffix) - WHEN current_holders.orgChild2Id IS NOT NULL THEN CONCAT_WS(' ', orgChild2.orgChild2ShortName, current_holders.posMasterNoPrefix, current_holders.posMasterNo, current_holders.posMasterNoSuffix) - WHEN current_holders.orgChild1Id IS NOT NULL THEN CONCAT_WS(' ', orgChild1.orgChild1ShortName, current_holders.posMasterNoPrefix, current_holders.posMasterNo, current_holders.posMasterNoSuffix) - ELSE CONCAT_WS(' ', orgRoot.orgRootShortName, current_holders.posMasterNoPrefix, current_holders.posMasterNo, current_holders.posMasterNoSuffix) + 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 `; } @@ -6616,11 +6616,11 @@ export class ProfileController extends Controller { } else if (searchField == "posNo") { queryLike = ` CASE - WHEN current_holders.orgChild4Id IS NOT NULL THEN CONCAT_WS(' ', orgChild4.orgChild4ShortName, current_holders.posMasterNoPrefix, current_holders.posMasterNo, current_holders.posMasterNoSuffix) - WHEN current_holders.orgChild3Id IS NOT NULL THEN CONCAT_WS(' ', orgChild3.orgChild3ShortName, current_holders.posMasterNoPrefix, current_holders.posMasterNo, current_holders.posMasterNoSuffix) - WHEN current_holders.orgChild2Id IS NOT NULL THEN CONCAT_WS(' ', orgChild2.orgChild2ShortName, current_holders.posMasterNoPrefix, current_holders.posMasterNo, current_holders.posMasterNoSuffix) - WHEN current_holders.orgChild1Id IS NOT NULL THEN CONCAT_WS(' ', orgChild1.orgChild1ShortName, current_holders.posMasterNoPrefix, current_holders.posMasterNo, current_holders.posMasterNoSuffix) - ELSE CONCAT_WS(' ', orgRoot.orgRootShortName, current_holders.posMasterNoPrefix, current_holders.posMasterNo, current_holders.posMasterNoSuffix) + 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 `; } @@ -7010,11 +7010,11 @@ export class ProfileController extends Controller { } else if (searchField == "posNo") { queryLike = ` CASE - WHEN current_holders.orgChild4Id IS NOT NULL THEN CONCAT_WS(' ', orgChild4.orgChild4ShortName, current_holders.posMasterNoPrefix, current_holders.posMasterNo, current_holders.posMasterNoSuffix) - WHEN current_holders.orgChild3Id IS NOT NULL THEN CONCAT_WS(' ', orgChild3.orgChild3ShortName, current_holders.posMasterNoPrefix, current_holders.posMasterNo, current_holders.posMasterNoSuffix) - WHEN current_holders.orgChild2Id IS NOT NULL THEN CONCAT_WS(' ', orgChild2.orgChild2ShortName, current_holders.posMasterNoPrefix, current_holders.posMasterNo, current_holders.posMasterNoSuffix) - WHEN current_holders.orgChild1Id IS NOT NULL THEN CONCAT_WS(' ', orgChild1.orgChild1ShortName, current_holders.posMasterNoPrefix, current_holders.posMasterNo, current_holders.posMasterNoSuffix) - ELSE CONCAT_WS(' ', orgRoot.orgRootShortName, current_holders.posMasterNoPrefix, current_holders.posMasterNo, current_holders.posMasterNoSuffix) + 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 `; } diff --git a/src/controllers/ProfileEmployeeController.ts b/src/controllers/ProfileEmployeeController.ts index faa8cdd3..f79e27cd 100644 --- a/src/controllers/ProfileEmployeeController.ts +++ b/src/controllers/ProfileEmployeeController.ts @@ -2848,11 +2848,11 @@ export class ProfileEmployeeController extends Controller { } else if (searchField == "posNo") { queryLike = ` CASE - WHEN current_holders.orgChild4Id IS NOT NULL THEN CONCAT_WS(' ', orgChild4.orgChild4ShortName, current_holders.posMasterNoPrefix, current_holders.posMasterNo, current_holders.posMasterNoSuffix) - WHEN current_holders.orgChild3Id IS NOT NULL THEN CONCAT_WS(' ', orgChild3.orgChild3ShortName, current_holders.posMasterNoPrefix, current_holders.posMasterNo, current_holders.posMasterNoSuffix) - WHEN current_holders.orgChild2Id IS NOT NULL THEN CONCAT_WS(' ', orgChild2.orgChild2ShortName, current_holders.posMasterNoPrefix, current_holders.posMasterNo, current_holders.posMasterNoSuffix) - WHEN current_holders.orgChild1Id IS NOT NULL THEN CONCAT_WS(' ', orgChild1.orgChild1ShortName, current_holders.posMasterNoPrefix, current_holders.posMasterNo, current_holders.posMasterNoSuffix) - ELSE CONCAT_WS(' ', orgRoot.orgRootShortName, current_holders.posMasterNoPrefix, current_holders.posMasterNo, current_holders.posMasterNoSuffix) + 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 `; } @@ -3207,11 +3207,11 @@ export class ProfileEmployeeController extends Controller { } else if (searchField == "posNo") { queryLike = ` CASE - WHEN current_holders.orgChild4Id IS NOT NULL THEN CONCAT_WS(' ', orgChild4.orgChild4ShortName, current_holders.posMasterNoPrefix, current_holders.posMasterNo, current_holders.posMasterNoSuffix) - WHEN current_holders.orgChild3Id IS NOT NULL THEN CONCAT_WS(' ', orgChild3.orgChild3ShortName, current_holders.posMasterNoPrefix, current_holders.posMasterNo, current_holders.posMasterNoSuffix) - WHEN current_holders.orgChild2Id IS NOT NULL THEN CONCAT_WS(' ', orgChild2.orgChild2ShortName, current_holders.posMasterNoPrefix, current_holders.posMasterNo, current_holders.posMasterNoSuffix) - WHEN current_holders.orgChild1Id IS NOT NULL THEN CONCAT_WS(' ', orgChild1.orgChild1ShortName, current_holders.posMasterNoPrefix, current_holders.posMasterNo, current_holders.posMasterNoSuffix) - ELSE CONCAT_WS(' ', orgRoot.orgRootShortName, current_holders.posMasterNoPrefix, current_holders.posMasterNo, current_holders.posMasterNoSuffix) + 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 `; } From 59c5cfb9bf028f110c6f6d1e0f248380f800b926 Mon Sep 17 00:00:00 2001 From: Adisak Date: Wed, 27 May 2026 11:13:40 +0700 Subject: [PATCH 65/83] #2527 --- src/controllers/CommandController.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/controllers/CommandController.ts b/src/controllers/CommandController.ts index 5fda4063..824773f1 100644 --- a/src/controllers/CommandController.ts +++ b/src/controllers/CommandController.ts @@ -7560,13 +7560,13 @@ export class CommandController extends Controller { // } // } + // อัพเดท org และ posMasterNo ตลอดไม่ต้องดัก isSit + profile.posMasterNo = getPosMasterNo(posMaster); + profile.org = getOrgFullName(posMaster); // ถ้าไม่ใช่ตำแหน่งนั่งทับ (isSit = false) ถึงจะอัพเดทตำแหน่งในทะเบียนประวัติ if (positionNew != null) { console.log("[Excexute/CreateOfficerProfile] Final position assignment, isSit:", posMaster.isSit, "positionId:", positionNew.id); 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; @@ -7577,10 +7577,10 @@ export class CommandController extends Controller { 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 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, From 7ebd01ef19caadc80d6598b036269e31c9f873b6 Mon Sep 17 00:00:00 2001 From: harid Date: Wed, 27 May 2026 12:05:12 +0700 Subject: [PATCH 66/83] =?UTF-8?q?=E0=B8=81=E0=B8=A3=E0=B8=AD=E0=B8=87=20".?= =?UTF-8?q?"=20=E0=B8=AD=E0=B8=AD=E0=B8=81=E0=B8=88=E0=B8=B2=E0=B8=81=20fi?= =?UTF-8?q?rstName=20=E0=B8=81=E0=B9=88=E0=B8=AD=E0=B8=99=E0=B8=AA?= =?UTF-8?q?=E0=B9=88=E0=B8=87=E0=B9=84=E0=B8=9B=20keycloak=20#2517?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/controllers/CommandController.ts | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/controllers/CommandController.ts b/src/controllers/CommandController.ts index 5fda4063..56b07006 100644 --- a/src/controllers/CommandController.ts +++ b/src/controllers/CommandController.ts @@ -4676,8 +4676,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, "") ?? ""; userKeycloakId = await createUser(profile.citizenId, password, { - firstName: profile.firstName, + firstName: sanitizedFirstName, lastName: profile.lastName, }); const list = await getRoles(); @@ -6877,8 +6879,10 @@ export class CommandController extends Controller { } console.log("[Excexute/CreateOfficerProfile] Calling createUser for:", item.bodyProfile.citizenId); console.log("[Excexute/CreateOfficerProfile] 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: item.bodyProfile.firstName, + firstName: sanitizedFirstName, lastName: item.bodyProfile.lastName, }); if (userKeycloakId && typeof userKeycloakId === "object" && userKeycloakId.errorMessage) { @@ -8023,8 +8027,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, }); From fa2d922fc3db59f6e609fb94c4165a4e18d774ca Mon Sep 17 00:00:00 2001 From: Adisak Date: Wed, 27 May 2026 14:24:32 +0700 Subject: [PATCH 67/83] #2529 , #2533 --- src/controllers/CommandController.ts | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/controllers/CommandController.ts b/src/controllers/CommandController.ts index 824773f1..24935202 100644 --- a/src/controllers/CommandController.ts +++ b/src/controllers/CommandController.ts @@ -7006,7 +7006,7 @@ export class CommandController extends Controller { profile.currentSubDistrictId = currentSubDistrictId ? currentSubDistrictId.id : _null; profile.currentZipCode = item.bodyProfile.currentZipCode; profile.email = item.bodyProfile.email; - profile.dateStart = item.bodyProfile.dateStart; + profile.dateStart = item.bodySalarys?.commandDateAffect ?? item.bodyProfile.dateStart; profile.amount = item.bodyProfile.amount ?? null; profile.amountSpecial = item.bodyProfile.amountSpecial ?? null; profile.isProbation = item.bodyProfile.isProbation; @@ -7076,7 +7076,7 @@ export class CommandController extends Controller { profile.currentSubDistrictId = currentSubDistrictId ? currentSubDistrictId.id : _null; profile.currentZipCode = item.bodyProfile.currentZipCode; profile.email = item.bodyProfile.email; - profile.dateStart = item.bodyProfile.dateStart; + profile.dateStart = item.bodySalarys?.commandDateAffect ?? item.bodyProfile.dateStart; profile.amount = item.bodyProfile.amount ?? null; profile.amountSpecial = item.bodyProfile.amountSpecial ?? null; profile.isProbation = item.bodyProfile.isProbation; @@ -7128,7 +7128,7 @@ export class CommandController extends Controller { profile.email = item.bodyProfile.email; profile.telephoneNumber = item.bodyProfile.telephoneNumber; profile.phone = item.bodyProfile.phone; - profile.dateStart = item.bodyProfile.dateStart; + profile.dateStart = item.bodySalarys?.commandDateAffect ?? item.bodyProfile.dateStart; profile.amount = item.bodyProfile.amount ?? null; profile.amountSpecial = item.bodyProfile.amountSpecial ?? null; profile.leaveCommandId = _null; @@ -7578,6 +7578,15 @@ export class CommandController extends Controller { // profile.dateStart = new Date(); } await this.positionRepository.save(positionNew, { data: req }); + } else if (!posMaster.isSit) { + // fallback: ตำแหน่งในโครงสร้างถูกแก้ไข ใช้ข้อมูลตำแหน่งที่สมัครสอบมา + console.log("[Excexute/CreateOfficerProfile] 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 }); From a678f950753cf60d1c095342bf7b798608e15d85 Mon Sep 17 00:00:00 2001 From: Adisak Date: Wed, 27 May 2026 14:58:00 +0700 Subject: [PATCH 68/83] revert #2533 --- src/controllers/CommandController.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/controllers/CommandController.ts b/src/controllers/CommandController.ts index 5dc501ba..eec261f2 100644 --- a/src/controllers/CommandController.ts +++ b/src/controllers/CommandController.ts @@ -7010,7 +7010,7 @@ export class CommandController extends Controller { profile.currentSubDistrictId = currentSubDistrictId ? currentSubDistrictId.id : _null; profile.currentZipCode = item.bodyProfile.currentZipCode; profile.email = item.bodyProfile.email; - profile.dateStart = item.bodySalarys?.commandDateAffect ?? item.bodyProfile.dateStart; + profile.dateStart = item.bodyProfile.dateStart; profile.amount = item.bodyProfile.amount ?? null; profile.amountSpecial = item.bodyProfile.amountSpecial ?? null; profile.isProbation = item.bodyProfile.isProbation; @@ -7080,7 +7080,7 @@ export class CommandController extends Controller { profile.currentSubDistrictId = currentSubDistrictId ? currentSubDistrictId.id : _null; profile.currentZipCode = item.bodyProfile.currentZipCode; profile.email = item.bodyProfile.email; - profile.dateStart = item.bodySalarys?.commandDateAffect ?? item.bodyProfile.dateStart; + profile.dateStart = item.bodyProfile.dateStart; profile.amount = item.bodyProfile.amount ?? null; profile.amountSpecial = item.bodyProfile.amountSpecial ?? null; profile.isProbation = item.bodyProfile.isProbation; @@ -7132,7 +7132,7 @@ export class CommandController extends Controller { profile.email = item.bodyProfile.email; profile.telephoneNumber = item.bodyProfile.telephoneNumber; profile.phone = item.bodyProfile.phone; - profile.dateStart = item.bodySalarys?.commandDateAffect ?? item.bodyProfile.dateStart; + profile.dateStart = item.bodyProfile.dateStart; profile.amount = item.bodyProfile.amount ?? null; profile.amountSpecial = item.bodyProfile.amountSpecial ?? null; profile.leaveCommandId = _null; From 7d463806a9bb84a2f49202af32e1404dceed34a5 Mon Sep 17 00:00:00 2001 From: harid Date: Wed, 27 May 2026 16:41:56 +0700 Subject: [PATCH 69/83] =?UTF-8?q?=E0=B8=9B=E0=B8=B1=E0=B9=8A=E0=B8=A1?= =?UTF-8?q?=E0=B8=9B=E0=B8=A3=E0=B8=B0=E0=B8=A7=E0=B8=B1=E0=B8=95=E0=B8=B4?= =?UTF-8?q?=E0=B8=84=E0=B8=99=E0=B8=84=E0=B8=A3=E0=B8=AD=E0=B8=87=E0=B8=81?= =?UTF-8?q?=E0=B8=A3=E0=B8=93=E0=B8=B5=E0=B8=A1=E0=B8=B5=E0=B9=81=E0=B8=81?= =?UTF-8?q?=E0=B9=89=E0=B9=84=E0=B8=82=E0=B8=8A=E0=B8=B7=E0=B9=88=E0=B8=AD?= =?UTF-8?q?=E0=B9=81=E0=B8=A5=E0=B8=B0=E0=B8=99=E0=B8=B2=E0=B8=A1=E0=B8=AA?= =?UTF-8?q?=E0=B8=81=E0=B8=B8=E0=B8=A5=20=20#244?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ProfileChangeNameController.ts | 4 ++ .../ProfileChangeNameEmployeeController.ts | 4 ++ src/services/PositionService.ts | 58 +++++++++++++++++++ 3 files changed, 66 insertions(+) diff --git a/src/controllers/ProfileChangeNameController.ts b/src/controllers/ProfileChangeNameController.ts index 77cff634..fa88a252 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") @@ -127,6 +128,9 @@ export class ProfileChangeNameController extends Controller { } } + // บันทึกประวัติคนครองตำแหน่ง (ถ้า profile นี้ครองตำแหน่งอยู่) + await updateHolderProfileHistory(profile.id, req); + return new HttpSuccess(data.id); } diff --git a/src/controllers/ProfileChangeNameEmployeeController.ts b/src/controllers/ProfileChangeNameEmployeeController.ts index c2df9c8c..0a6f2ff0 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") @@ -133,6 +134,9 @@ export class ProfileChangeNameEmployeeController extends Controller { } } + // บันทึกประวัติคนครองตำแหน่ง (ถ้า profile นี้ครองตำแหน่งอยู่) + await updateHolderProfileHistory(profile.id, req, "EMPLOYEE"); + return new HttpSuccess(data.id); } diff --git a/src/services/PositionService.ts b/src/services/PositionService.ts index b6514eca..37c5f083 100644 --- a/src/services/PositionService.ts +++ b/src/services/PositionService.ts @@ -501,3 +501,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; + } +} From a36ec74e842c24aa6fa58fcd12a5722efa8f9943 Mon Sep 17 00:00:00 2001 From: harid Date: Wed, 27 May 2026 16:51:03 +0700 Subject: [PATCH 70/83] =?UTF-8?q?=E0=B8=A3=E0=B8=B2=E0=B8=A2=E0=B8=87?= =?UTF-8?q?=E0=B8=B2=E0=B8=99=E0=B9=81=E0=B8=99=E0=B8=9A=E0=B8=97=E0=B9=89?= =?UTF-8?q?=E0=B8=B2=E0=B8=A2=20=E0=B8=82=E0=B9=89=E0=B8=AD=E0=B8=A1?= =?UTF-8?q?=E0=B8=B9=E0=B8=A5=20"=E0=B8=A5=E0=B8=87=E0=B8=A7=E0=B8=B1?= =?UTF-8?q?=E0=B8=99=E0=B8=97=E0=B8=B5=E0=B9=88"=20=E0=B8=AB=E0=B8=B1?= =?UTF-8?q?=E0=B8=A7=E0=B8=81=E0=B8=A3=E0=B8=B0=E0=B8=94=E0=B8=B2=E0=B8=A9?= =?UTF-8?q?=20=E0=B9=83=E0=B8=AB=E0=B9=89=E0=B9=83=E0=B8=8A=E0=B9=89?= =?UTF-8?q?=E0=B8=82=E0=B9=89=E0=B8=AD=E0=B8=A1=E0=B8=B9=E0=B8=A5=20"?= =?UTF-8?q?=E0=B8=A7=E0=B8=B1=E0=B8=99=E0=B8=97=E0=B8=B5=E0=B9=88=E0=B8=A5?= =?UTF-8?q?=E0=B8=87=E0=B8=99=E0=B8=B2=E0=B8=A1"=20#2514?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/controllers/CommandController.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/controllers/CommandController.ts b/src/controllers/CommandController.ts index eec261f2..c676ad51 100644 --- a/src/controllers/CommandController.ts +++ b/src/controllers/CommandController.ts @@ -2411,9 +2411,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) => ({ From 9782871c9c26b30c4071302bc7d1b22fe159066b Mon Sep 17 00:00:00 2001 From: harid Date: Wed, 27 May 2026 17:46:44 +0700 Subject: [PATCH 71/83] =?UTF-8?q?=E0=B8=AA=E0=B9=88=E0=B8=87=E0=B8=A3?= =?UTF-8?q?=E0=B8=B2=E0=B8=A2=E0=B8=8A=E0=B8=B7=E0=B9=88=E0=B8=AD=E0=B8=AD?= =?UTF-8?q?=E0=B8=81=E0=B8=84=E0=B8=B3=E0=B8=AA=E0=B8=B1=E0=B9=88=E0=B8=87?= =?UTF-8?q?=E0=B8=A7=E0=B8=B4=E0=B8=99=E0=B8=B1=E0=B8=A2=20=E0=B9=80?= =?UTF-8?q?=E0=B8=9E=E0=B8=B4=E0=B9=88=E0=B8=A1=20CommandCode,=20CommandId?= =?UTF-8?q?=20#2377?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/controllers/CommandController.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/controllers/CommandController.ts b/src/controllers/CommandController.ts index c676ad51..f114a24c 100644 --- a/src/controllers/CommandController.ts +++ b/src/controllers/CommandController.ts @@ -2618,6 +2618,7 @@ 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 ( @@ -2637,6 +2638,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 { @@ -2651,6 +2653,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; @@ -2795,7 +2798,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), From 399bf87ba6cf814af317868afab27336ae36b1e7 Mon Sep 17 00:00:00 2001 From: Adisak Date: Thu, 28 May 2026 09:08:58 +0700 Subject: [PATCH 72/83] =?UTF-8?q?#246=20=E0=B9=80=E0=B8=84=E0=B8=A5?= =?UTF-8?q?=E0=B8=B5=E0=B8=99=E0=B8=A3=E0=B9=8C=20isSit?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/interfaces/utils.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/interfaces/utils.ts b/src/interfaces/utils.ts index be2f3bf9..c75bf147 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(); From ccfb2754fd70c47ba86f3517cbac82d02e177f62 Mon Sep 17 00:00:00 2001 From: waruneeauy Date: Thu, 28 May 2026 09:49:21 +0700 Subject: [PATCH 73/83] API apiKey list return accessType and orgName --- src/controllers/ApiKeyController.ts | 149 +++++++++++++++++++++++++++- 1 file changed, 147 insertions(+), 2 deletions(-) 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") From 521a748de1df7974c177fa4192e91c9898cee51b Mon Sep 17 00:00:00 2001 From: harid Date: Thu, 28 May 2026 13:02:58 +0700 Subject: [PATCH 74/83] =?UTF-8?q?fix=20=E0=B9=81=E0=B8=81=E0=B9=89?= =?UTF-8?q?=E0=B9=84=E0=B8=82=E0=B8=9B=E0=B8=A3=E0=B8=B0=E0=B8=A7=E0=B8=B1?= =?UTF-8?q?=E0=B8=95=E0=B8=B4=E0=B8=AA=E0=B9=88=E0=B8=A7=E0=B8=99=E0=B8=95?= =?UTF-8?q?=E0=B8=B1=E0=B8=A7=20=E0=B9=84=E0=B8=A1=E0=B9=88=E0=B8=9B?= =?UTF-8?q?=E0=B8=B1=E0=B9=8A=E0=B8=A1=E0=B8=9B=E0=B8=A3=E0=B8=B0=E0=B8=A7?= =?UTF-8?q?=E0=B8=B1=E0=B8=95=E0=B8=B4=E0=B8=A3=E0=B8=B2=E0=B8=A2=E0=B8=81?= =?UTF-8?q?=E0=B8=B2=E0=B8=A3=E0=B8=84=E0=B8=99=E0=B8=84=E0=B8=A3=E0=B8=AD?= =?UTF-8?q?=E0=B8=87=20#244?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/controllers/ProfileController.ts | 25 ++++---------------- src/controllers/ProfileEmployeeController.ts | 24 ++++--------------- 2 files changed, 9 insertions(+), 40 deletions(-) diff --git a/src/controllers/ProfileController.ts b/src/controllers/ProfileController.ts index 92943776..434e7b77 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") @@ -5774,26 +5775,7 @@ 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); @@ -5833,6 +5815,9 @@ export class ProfileController extends Controller { } } + // บันทึกประวัติคนครองตำแหน่ง (ถ้า profile นี้ครองตำแหน่งอยู่) + await updateHolderProfileHistory(record.id, request); + return new HttpSuccess(); } diff --git a/src/controllers/ProfileEmployeeController.ts b/src/controllers/ProfileEmployeeController.ts index f79e27cd..cdf1a4e4 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") @@ -2381,26 +2382,7 @@ 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 }); if (!record) throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูลโปรไฟล์นี้"); @@ -2434,6 +2416,8 @@ export class ProfileEmployeeController extends Controller { }), ); await this.profileRepo.save(record); + // บันทึกประวัติคนครองตำแหน่ง (ถ้า profile นี้ครองตำแหน่งอยู่) + await updateHolderProfileHistory(record.id, request, "EMPLOYEE"); return new HttpSuccess(); } From d495137aaf95dcce36b66c13c4631c5f3afa14ab Mon Sep 17 00:00:00 2001 From: harid Date: Thu, 28 May 2026 17:11:06 +0700 Subject: [PATCH 75/83] =?UTF-8?q?devTest=20Controller=20=E0=B8=AA=E0=B8=B3?= =?UTF-8?q?=E0=B8=AB=E0=B8=A3=E0=B8=B1=E0=B8=9A=E0=B8=97=E0=B8=94=E0=B8=AA?= =?UTF-8?q?=E0=B8=AD=E0=B8=9A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/controllers/DevTestController.ts | 576 +++++++++++++++++++++++++++ 1 file changed, 576 insertions(+) create mode 100644 src/controllers/DevTestController.ts 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)), + }, + }); + } + +} From 755ae992ddc29e8d2abb6b2af1c4fe6de97a7efd Mon Sep 17 00:00:00 2001 From: Adisak Date: Fri, 29 May 2026 10:12:46 +0700 Subject: [PATCH 76/83] =?UTF-8?q?#246=20=E0=B9=80=E0=B8=AA=E0=B8=A3?= =?UTF-8?q?=E0=B8=B4=E0=B8=A1=E0=B8=84=E0=B8=B3=E0=B8=AA=E0=B8=B1=E0=B9=88?= =?UTF-8?q?=E0=B8=87=20C-PM-25,C-PM-26?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/controllers/CommandController.ts | 89 +++++++++------------------- src/interfaces/utils.ts | 51 ++++------------ 2 files changed, 42 insertions(+), 98 deletions(-) diff --git a/src/controllers/CommandController.ts b/src/controllers/CommandController.ts index f114a24c..d732a622 100644 --- a/src/controllers/CommandController.ts +++ b/src/controllers/CommandController.ts @@ -41,7 +41,6 @@ import { removeProfileInOrganize, setLogDataDiff, checkReturnCommandType, - checkExceptCommandType, checkCommandType, removePostMasterAct, logPositionIsSelectedChange, @@ -5629,20 +5628,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) { @@ -5821,32 +5810,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) { @@ -6166,32 +6145,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) { diff --git a/src/interfaces/utils.ts b/src/interfaces/utils.ts index c75bf147..3a48ab2b 100644 --- a/src/interfaces/utils.ts +++ b/src/interfaces/utils.ts @@ -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,6 +414,8 @@ export async function checkCommandType(commandId: string) { "C-PM-23", "C-PM-19", "C-PM-20", + "C-PM-25", + "C-PM-26", "C-PM-43", ].includes(String(_type?.commandType.code)) ) { @@ -500,6 +465,16 @@ 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 = "ให้ออกจากราชการ"; From ad9a7dcbb6b4af069e5311df34ae409912a5137e Mon Sep 17 00:00:00 2001 From: harid Date: Fri, 29 May 2026 14:08:25 +0700 Subject: [PATCH 77/83] =?UTF-8?q?fix=20=E0=B8=A3=E0=B8=B0=E0=B8=9A?= =?UTF-8?q?=E0=B8=9A=E0=B9=84=E0=B8=A1=E0=B9=88=E0=B8=9B=E0=B8=B1=E0=B9=8A?= =?UTF-8?q?=E0=B8=A1=E0=B8=9B=E0=B8=A3=E0=B8=B0=E0=B8=A7=E0=B8=B1=E0=B8=95?= =?UTF-8?q?=E0=B8=B4=E0=B8=82=E0=B9=89=E0=B8=AD=E0=B8=A1=E0=B8=B9=E0=B8=A5?= =?UTF-8?q?=E0=B9=80=E0=B8=94=E0=B8=B4=E0=B8=A1=E0=B9=83=E0=B8=AB=E0=B9=89?= =?UTF-8?q?=20row=20=E0=B9=81=E0=B8=A3=E0=B8=81=20#2535?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../OrganizationDotnetController.ts | 26 +++++++++++++++++++ src/controllers/ProfileController.ts | 16 ++++++++++++ src/controllers/ProfileEmployeeController.ts | 18 +++++++++++++ .../ProfileEmployeeTempController.ts | 18 +++++++++++++ 4 files changed, 78 insertions(+) diff --git a/src/controllers/OrganizationDotnetController.ts b/src/controllers/OrganizationDotnetController.ts index 8be7aa0f..e83d2911 100644 --- a/src/controllers/OrganizationDotnetController.ts +++ b/src/controllers/OrganizationDotnetController.ts @@ -9151,4 +9151,30 @@ export class OrganizationDotnetController extends Controller { }); return new HttpSuccess(filteredPosMasters); } + + /** + * API ตรวจสอบ profileId ที่ลาออกแล้ว + * @summary API ตรวจสอบ profileId ที่ลาออกแล้ว + */ + @Post("check-isLeave") + @Security("internalAuth") + async findProfileIsLeave( + @Body() + req: { profileIds: string[] } + ) { + const profile = await this.profileRepo.find({ + select: { id: true }, + where: { + id: In(req.profileIds), + isLeave: true + } + }); + + if (profile.length === 0) { + return new HttpSuccess([]); + } + + return new HttpSuccess(profile.map(p => p.id)); + } + } diff --git a/src/controllers/ProfileController.ts b/src/controllers/ProfileController.ts index 434e7b77..0fcf3c3c 100644 --- a/src/controllers/ProfileController.ts +++ b/src/controllers/ProfileController.ts @@ -5779,6 +5779,22 @@ export class ProfileController extends Controller { } 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, "ไม่พบข้อมูลโปรไฟล์นี้"); diff --git a/src/controllers/ProfileEmployeeController.ts b/src/controllers/ProfileEmployeeController.ts index cdf1a4e4..9b2537c0 100644 --- a/src/controllers/ProfileEmployeeController.ts +++ b/src/controllers/ProfileEmployeeController.ts @@ -2385,6 +2385,24 @@ export class ProfileEmployeeController extends Controller { 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 == "") { 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 == "") { From 20c6c412b88b55378643dbc1bf572071ba0a9e2d Mon Sep 17 00:00:00 2001 From: waruneeauy Date: Fri, 29 May 2026 14:24:50 +0700 Subject: [PATCH 78/83] remove log success --- src/middlewares/authInternal.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/middlewares/authInternal.ts b/src/middlewares/authInternal.ts index d3d9a5b6..77da531c 100644 --- a/src/middlewares/authInternal.ts +++ b/src/middlewares/authInternal.ts @@ -19,7 +19,7 @@ export async function handleInternalAuth(request: express.Request) { throw new HttpError(HttpStatus.UNAUTHORIZED, "Invalid API Key"); } - console.log(`[InternalAuth] Authentication successful`); + // console.log(`[InternalAuth] Authentication successful`); return { sub: "internal_service", From 185aedc53f96805d7264132ffdfaacc0e6747c12 Mon Sep 17 00:00:00 2001 From: waruneeauy Date: Fri, 29 May 2026 17:23:45 +0700 Subject: [PATCH 79/83] remove log success --- src/controllers/OrganizationDotnetController.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/controllers/OrganizationDotnetController.ts b/src/controllers/OrganizationDotnetController.ts index e83d2911..d785a643 100644 --- a/src/controllers/OrganizationDotnetController.ts +++ b/src/controllers/OrganizationDotnetController.ts @@ -2372,7 +2372,7 @@ export class OrganizationDotnetController extends Controller { @Security("internalAuth") async GetProfileForProcessCheckInAsync(@Path() keycloakId: string) { try { - console.log(`[check-keycloak] START - keycloakId=${keycloakId}`); + // console.log(`[check-keycloak] START - keycloakId=${keycloakId}`); /* ========================= * 1. Load profile (Officer) @@ -2447,14 +2447,14 @@ export class OrganizationDotnetController extends Controller { child4DnaId: currentHolder?.orgChild4?.ancestorDNA ?? null, }; - console.log( - `[check-keycloak] SUCCESS_EMPLOYEE - keycloakId=${keycloakId}, profileType=EMPLOYEE`, - ); + // console.log( + // `[check-keycloak] SUCCESS_EMPLOYEE - keycloakId=${keycloakId}, profileType=EMPLOYEE`, + // ); return new HttpSuccess(mapProfile); } - console.log(`[check-keycloak] OFFICER_FOUND - keycloakId=${keycloakId}`); + // console.log(`[check-keycloak] OFFICER_FOUND - keycloakId=${keycloakId}`); /* ========================================= * 2. current holder (Officer) @@ -2494,9 +2494,9 @@ export class OrganizationDotnetController extends Controller { child4DnaId: currentHolder?.orgChild4?.ancestorDNA ?? null, }; - console.log( - `[check-keycloak] SUCCESS_OFFICER - keycloakId=${keycloakId}, profileType=OFFICER`, - ); + // console.log( + // `[check-keycloak] SUCCESS_OFFICER - keycloakId=${keycloakId}, profileType=OFFICER`, + // ); return new HttpSuccess(mapProfile); } catch (error: any) { From b0cfbc70363b04dd86a9ceee990117b6b07078bd Mon Sep 17 00:00:00 2001 From: harid Date: Fri, 29 May 2026 17:37:19 +0700 Subject: [PATCH 80/83] fix #2510 --- src/controllers/CommandController.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/controllers/CommandController.ts b/src/controllers/CommandController.ts index d732a622..474470de 100644 --- a/src/controllers/CommandController.ts +++ b/src/controllers/CommandController.ts @@ -4312,11 +4312,8 @@ export class CommandController extends Controller { body.data.map(async (item) => { const profile = await this.profileRepository.findOne({ where: { id: item.profileId }, - // relations: ["roleKeycloaks"], relations: { - roleKeycloaks: true, - posType: true, - posLevel: true, + roleKeycloaks: true }, }); if (!profile) { @@ -4612,6 +4609,8 @@ export class CommandController extends Controller { 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, From 219a2908a3c71ca1265f8eaa203af25db0ba37bf Mon Sep 17 00:00:00 2001 From: Adisak Date: Thu, 4 Jun 2026 09:25:01 +0700 Subject: [PATCH 81/83] =?UTF-8?q?=E0=B9=81=E0=B8=81=E0=B9=89=20format=20?= =?UTF-8?q?=E0=B8=9F=E0=B8=B4=E0=B8=A7=20posMasterNo=20(5)=20=E0=B8=AA?= =?UTF-8?q?=E0=B8=B3=E0=B8=AB=E0=B8=A3=E0=B8=B1=E0=B8=9A=E0=B8=9A=E0=B8=B1?= =?UTF-8?q?=E0=B8=99=E0=B8=97=E0=B8=B6=E0=B8=81=E0=B8=A5=E0=B8=87=E0=B8=A3?= =?UTF-8?q?=E0=B8=B0=E0=B8=9A=E0=B8=9A=E0=B8=97=E0=B8=B5=E0=B9=88=E0=B8=9E?= =?UTF-8?q?=E0=B8=B1=E0=B8=92=E0=B8=99=E0=B8=B2=E0=B8=9A=E0=B8=99=20.net?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1.ปรับระดับชั้นงาน-ย้ายลูกจ้าง (เลข Prefix / Suffix ไม่แสดง) 2.รายการอื่นๆ (เลข Prefix / Suffix ไม่แสดง) 3.รายการลาออก (เลข Prefix / Suffix ไม่แสดง) 4.รายการลาออกลูกจ้างฯ (เลข Prefix / Suffix ไม่แสดง) 5.เรื่องร้องเรียน (เลข Prefix / Suffix ไม่แสดง) 6.สืบสวนฯ (เลข Prefix / Suffix ไม่แสดง) 7.สอบสวนฯ (เลข Prefix / Suffix ไม่แสดง) 8.สรุปผลการพิจารณาฯ (เลข Prefix / Suffix ไม่แสดง) --- src/controllers/ProfileController.ts | 80 +++++++++----------- src/controllers/ProfileEmployeeController.ts | 49 ++++-------- 2 files changed, 49 insertions(+), 80 deletions(-) diff --git a/src/controllers/ProfileController.ts b/src/controllers/ProfileController.ts index 434e7b77..65052de9 100644 --- a/src/controllers/ProfileController.ts +++ b/src/controllers/ProfileController.ts @@ -7935,40 +7935,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); } @@ -8108,41 +8106,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); } @@ -9313,26 +9309,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); } @@ -9513,38 +9515,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 cdf1a4e4..bca81433 100644 --- a/src/controllers/ProfileEmployeeController.ts +++ b/src/controllers/ProfileEmployeeController.ts @@ -4003,40 +4003,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); } @@ -6444,33 +6442,7 @@ export class ProfileEmployeeController extends Controller { 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, @@ -6512,7 +6484,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 @@ -6527,22 +6499,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); } From 825263c11cdd0f15bf13059efb533b601d076fdd Mon Sep 17 00:00:00 2001 From: harid Date: Thu, 4 Jun 2026 17:40:36 +0700 Subject: [PATCH 82/83] =?UTF-8?q?API=20=E0=B8=95=E0=B8=A3=E0=B8=A7?= =?UTF-8?q?=E0=B8=88=E0=B8=AA=E0=B8=AD=E0=B8=9A=E0=B8=AA=E0=B8=96=E0=B8=B2?= =?UTF-8?q?=E0=B8=99=E0=B8=B0=E0=B8=9C=E0=B8=B9=E0=B9=89=E0=B8=AA=E0=B8=A1?= =?UTF-8?q?=E0=B8=B1=E0=B8=84=E0=B8=A3=E0=B8=AA=E0=B8=AD=E0=B8=9A=20#2518?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../OrganizationDotnetController.ts | 31 +++++++++++++------ 1 file changed, 22 insertions(+), 9 deletions(-) diff --git a/src/controllers/OrganizationDotnetController.ts b/src/controllers/OrganizationDotnetController.ts index d785a643..8e803569 100644 --- a/src/controllers/OrganizationDotnetController.ts +++ b/src/controllers/OrganizationDotnetController.ts @@ -9153,28 +9153,41 @@ export class OrganizationDotnetController extends Controller { } /** - * API ตรวจสอบ profileId ที่ลาออกแล้ว - * @summary API ตรวจสอบ profileId ที่ลาออกแล้ว + * API ตรวจสอบสถานะผู้สมัครสอบ + * @summary API ตรวจสอบสถานะผู้สมัครสอบ */ @Post("check-isLeave") @Security("internalAuth") async findProfileIsLeave( @Body() - req: { profileIds: string[] } + req: { citizenIds: string[] } ) { - const profile = await this.profileRepo.find({ - select: { id: true }, + + const profiles = await this.profileRepo.find({ + select: { + id: true, + citizenId: true, + isLeave: true, + isActive: true + }, where: { - id: In(req.profileIds), - isLeave: true + citizenId: In(req.citizenIds) } }); - if (profile.length === 0) { + 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 + })) + ); - return new HttpSuccess(profile.map(p => p.id)); } } From 8bd4a816e867569faf12428f3eda20a39dfc1db5 Mon Sep 17 00:00:00 2001 From: harid Date: Fri, 5 Jun 2026 12:04:52 +0700 Subject: [PATCH 83/83] Migrate #2481 --- src/controllers/CommandController.ts | 2 ++ src/entities/ProfileDiscipline.ts | 8 ++++++++ src/entities/ProfileDisciplineHistory.ts | 8 ++++++++ ...1-update_profileDiscipline_add_refCommandId.ts | 15 +++++++++++++++ 4 files changed, 33 insertions(+) create mode 100644 src/migration/1780634210221-update_profileDiscipline_add_refCommandId.ts diff --git a/src/controllers/CommandController.ts b/src/controllers/CommandController.ts index 474470de..828a2fc1 100644 --- a/src/controllers/CommandController.ts +++ b/src/controllers/CommandController.ts @@ -5594,6 +5594,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, @@ -6117,6 +6118,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, }); 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/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\``); + } +}