import amqp from "amqplib"; import { AppDataSource } from "../database/data-source"; import { Command } from "../entities/Command"; import { chunkArray, commandTypePath } from "../interfaces/utils"; import CallAPI from "../interfaces/call-api"; import { getPosMasterNo, getOrgFullName } from "../utils/org-formatting"; import HttpError from "../interfaces/http-error"; import HttpStatusCode from "../interfaces/http-status"; import { PosMaster } from "../entities/PosMaster"; import { Profile } from "../entities/Profile"; import { EmployeePosMaster } from "../entities/EmployeePosMaster"; import { EmployeeTempPosMaster } from "../entities/EmployeeTempPosMaster"; import { ProfileEmployee } from "../entities/ProfileEmployee"; import { OrgRevision } from "../entities/OrgRevision"; import { EmployeePosition } from "../entities/EmployeePosition"; import { OrgChild1 } from "../entities/OrgChild1"; import { OrgChild2 } from "../entities/OrgChild2"; import { OrgChild3 } from "../entities/OrgChild3"; import { OrgChild4 } from "../entities/OrgChild4"; import { OrgRoot } from "../entities/OrgRoot"; import { PosMasterAssign, PosMasterAssignDTO } from "../entities/PosMasterAssign"; import { Position } from "../entities/Position"; import { In, Not } from "typeorm"; import { PosMasterAct } from "../entities/PosMasterAct"; import { PermissionOrg } from "../entities/PermissionOrg"; import { sendWebSocket } from "./webSocket"; import { CreatePosMasterHistoryOfficer } from "./PositionService"; import { PayloadSendNoti } from "../interfaces/utils"; import { PermissionProfile } from "../entities/PermissionProfile"; export let sendToQueue: (payload: any) => void; export let sendToQueueOrg: (payload: any) => void; export let sendToQueueOrgDraft: (payload: any) => void; export let sendToQueueCommandNoti: (payload: any) => void; export async function init() { //----> (1) Producer if ( !process.env.AMQ_URL || !process.env.AMQ_QUEUE || !process.env.AMQ_QUEUE_ORG || !process.env.AMQ_QUEUE_ORG_DRAFT || !process.env.AMQ_QUEUE_COMMAND_NOTI ) return; const { AMQ_URL: url, AMQ_QUEUE: queue, AMQ_QUEUE_ORG: queue_org, AMQ_QUEUE_ORG_DRAFT: queue_org_draft, AMQ_QUEUE_COMMAND_NOTI: queue_command_noti, } = process.env; //----> (1.2) get url and queue from .env const connection = await amqp.connect(url); //----> (1.3) set up url with amqp protocol console.log(connection ? "[AMQ] Connection success" : "[AMQ] Connection failed"); const channel = await connection.createChannel(); //----> (1.4) create Channel console.log(channel ? "[AMQ] Create channel success" : "[AMQ] Create channel failed"); channel.assertQueue(queue, { durable: true }), //----> (1.5) assert queue and set durable (if "true" save to disk on RabbitMQ) channel.assertQueue(queue_org, { durable: true }), channel.assertQueue(queue_org_draft, { durable: true }), channel.assertQueue(queue_command_noti, { durable: true }), channel.prefetch(1); sendToQueue = (payload: any, persistent = true) => { //----> (2) sendQueue To RabbitMQ and set persistent (if "true" redo the failed queue when server run again) channel.sendToQueue(queue, Buffer.from(JSON.stringify(payload)), { persistent, }); }; sendToQueueOrg = (payload: any, persistent = true) => { channel.sendToQueue(queue_org, Buffer.from(JSON.stringify(payload)), { persistent }); }; sendToQueueOrgDraft = (payload: any, persistent = true) => { channel.sendToQueue(queue_org_draft, Buffer.from(JSON.stringify(payload)), { persistent }); }; sendToQueueCommandNoti = (payload: any, persistent = true) => { channel.sendToQueue(queue_command_noti, Buffer.from(JSON.stringify(payload)), { persistent }); }; console.log("[AMQ] Listening for message..."); createConsumer(queue, channel, handler), //----> (3) Process Consumer createConsumer(queue_org, channel, handler_org); createConsumer(queue_org_draft, channel, handler_org_draft); createConsumer(queue_command_noti, channel, handler_command_noti); // createConsumer(queue2, channel, handler2); } function createConsumer( //----> consumer queue: string, channel: amqp.Channel, handler: (msg: amqp.ConsumeMessage) => Promise | boolean, ) { let retries = 0; channel.consume( queue, async (msg) => { if (!msg) return; if ((await handler(msg)) || retries++ >= 3) { retries = 0; console.log("[AMQ] Process Consumer success"); return channel.ack(msg); } console.log("[AMQ] Process Consumer failed"); return await new Promise((resolve) => setTimeout(() => resolve(channel.nack(msg)), 3000)); }, { noAck: false }, ); } async function handler(msg: amqp.ConsumeMessage): Promise { //----> condition before process consumer // const repo = AppDataSource.getRepository(Command); // const { data, token, user } = JSON.parse(msg.content.toString()); // const { id, status, lastUpdateUserId, lastUpdateFullName, lastUpdatedAt } = data; // const command = await repo.findOne({ // where: { id: id }, // relations: ["commandType", "commandRecives"], // }); // if (!command) return true; // let waiting_message = `ระบบทำการออกคำสั่งเลขที่ ${command.commandNo}/${command.commandYear + 543}`; // let success_message = `ระบบออกคำสั่งเลขที่ ${command.commandNo}/${command.commandYear + 543} เสร็จสิ้น`; // let error_message = `ระบบออกคำสั่งเลขที่ ${command.commandNo}/${command.commandYear + 543} ผิดพลาด` // if(command.commandType?.code == "C-PM-47"){ // waiting_message = `ระบบทำการออกคำสั่งเลขที่ ${command.commandNo}`; // success_message = `ระบบออกคำสั่งเลขที่ ${command.commandNo} เสร็จสิ้น`; // error_message = `ระบบออกคำสั่งเลขที่ ${command.commandNo} ผิดพลาด`; // } // if (user) { // sendWebSocket( // "send-command-notification", // { // success: true, // message: waiting_message, // payload: command, // }, // { userId: user?.sub }, // ).catch(console.error); // } // const path = commandTypePath(command.commandType.code); // if (path == null) throw new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบประเภทคำสั่งนี้ในระบบ"); // return await new CallAPI() // //chunk 50 // .PostData( // { // headers: { authorization: token }, // }, // path + "/excecute", // { // refIds: command.commandRecives //chunk // .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, // })), // }, // false, // ) // .then(async (res) => { // console.log("[AMQ] Excecute Command Success"); // Object.assign(command, { status, lastUpdateUserId, lastUpdateFullName, lastUpdatedAt }); // const result = await repo.save(command).catch((e) => console.log(e)); // if (user) { // sendWebSocket( // "send-command-notification", // { // success: true, // message: success_message, // payload: command, // }, // { userId: user?.sub }, // ).catch(console.error); // } // return !!result; // }) // .catch((e) => { // console.error(e); // if (user) { // sendWebSocket( // "send-command-notification", // { // success: false, // message: error_message, // payload: command, // }, // { userId: user?.sub }, // ).catch(console.error); // } // return false; // }); const repo = AppDataSource.getRepository(Command); const { data, token, user } = JSON.parse(msg.content.toString()); const { id, status, lastUpdateUserId, lastUpdateFullName, lastUpdatedAt } = data; const command = await repo.findOne({ where: { id: id }, relations: ["commandType", "commandRecives"], }); if (!command) return true; let waiting_message = `ระบบทำการออกคำสั่งเลขที่ ${command.commandNo}/${command.commandYear + 543}`; let success_message = `ระบบออกคำสั่งเลขที่ ${command.commandNo}/${command.commandYear + 543} เสร็จสิ้น`; let error_message = `ระบบออกคำสั่งเลขที่ ${command.commandNo}/${command.commandYear + 543} ผิดพลาด`; if (command.commandType?.code == "C-PM-47") { waiting_message = `ระบบทำการออกคำสั่งเลขที่ ${command.commandNo}`; success_message = `ระบบออกคำสั่งเลขที่ ${command.commandNo} เสร็จสิ้น`; error_message = `ระบบออกคำสั่งเลขที่ ${command.commandNo} ผิดพลาด`; } if (user) { sendWebSocket( "send-command-notification", { success: true, message: waiting_message, payload: command }, { userId: user?.sub }, ).catch(console.error); } const path = commandTypePath(command.commandType.code); if (path == null) throw new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบประเภทคำสั่งนี้ในระบบ"); try { const allRefIds = new Set(); const chunks = chunkArray( command.commandRecives .filter((x) => x.refId != null) .map((x) => { const key = `${x.refId}-${command.id}`; if (allRefIds.has(key)) { return null; } allRefIds.add(key); return { 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, }; }) .filter(Boolean), 20, ); for (const chunk of chunks) { await new CallAPI().PostData( { headers: { authorization: token } }, path + "/excecute", { refIds: chunk }, false, ); } Object.assign(command, { status, lastUpdateUserId, lastUpdateFullName, lastUpdatedAt }); const result = await repo.save(command); if (user) { sendWebSocket( "send-command-notification", { success: true, message: success_message, payload: command }, { userId: user?.sub }, ).catch(console.error); } console.log("[AMQ] Excecute Command Success"); return !!result; } catch (e) { console.error(e); if (user) { sendWebSocket( "send-command-notification", { success: false, message: error_message, payload: command }, { userId: user?.sub }, ).catch(console.error); } return false; } } // async function handler(msg: amqp.ConsumeMessage): Promise { // const repo = AppDataSource.getRepository(Command); // const { data, token, user } = JSON.parse(msg.content.toString()); // const { id, status, lastUpdateUserId, lastUpdateFullName, lastUpdatedAt } = data; // const command = await repo.findOne({ // where: { id }, // relations: ["commandType", "commandRecives"], // }); // if (!command) return true; // if (user) { // sendWebSocket( // "send-command-notification", // { // success: true, // message: `ระบบทำการออกคำสั่งเลขที่ ${command.commandNo}/${command.commandYear + 543}`, // payload: command, // }, // { userId: user?.sub }, // ).catch(console.error); // } // const path = commandTypePath(command.commandType.code); // if (path == null) throw new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบประเภทคำสั่งนี้ในระบบ"); // const recivers = 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, // })); // const batchSize = 50; // const batches = Array.from({ length: Math.ceil(recivers.length / batchSize) }, (_, i) => // recivers.slice(i * batchSize, i * batchSize + batchSize) // ); // const api = new CallAPI(); // try { // for (const batch of batches) { // await api.PostData( // { headers: { authorization: token } }, // path + "/excecute", // { refIds: batch }, // false // ); // } // console.log("[AMQ] Excecute Command Success"); // Object.assign(command, { status, lastUpdateUserId, lastUpdateFullName, lastUpdatedAt }); // const result = await repo.save(command); // if (user) { // sendWebSocket( // "send-command-notification", // { // success: true, // message: `ระบบออกคำสั่งเลขที่ ${command.commandNo}/${command.commandYear + 543} เสร็จสิ้น`, // payload: command, // }, // { userId: user?.sub }, // ).catch(console.error); // } // return !!result; // } catch (e) { // console.error(e); // if (user) { // sendWebSocket( // "send-command-notification", // { // success: false, // message: `ระบบออกคำสั่งเลขที่ ${command.commandNo}/${command.commandYear + 543} ผิดพลาด`, // payload: command, // }, // { userId: user?.sub }, // ).catch(console.error); // } // return false; // } // } async function handler_command_noti(msg: amqp.ConsumeMessage): Promise { const { data, token, user } = JSON.parse(msg.content.toString()); const { profiles, command } = data; try { let profilesNotiRequest: Promise | undefined; if (!["C-PM-10"].includes(command.commandType.code)) { profilesNotiRequest = new CallAPI() .PostData( { headers: { authorization: token } }, "/placement/noti/profiles", { subject: `${command.issue}`, body: `${command.issue}`, receiverUserIds: profiles, payload: "", // แนบไฟล์ (ถ้าจำเป็น) isSendMail: true, isSendInbox: true, isSendNotification: true, }, false, ) .catch((error) => { if (error.response) { // Server ตอบกลับ (มี status code 4xx หรือ 5xx) console.error("Error status:", error.response.status); console.error("Error data:", error.response.data); console.error("Error headers:", error.response.headers); } else if (error.request) { // ไม่มีการตอบกลับจาก server console.error("No response received:", error.request); } else { // เกิดข้อผิดพลาดอื่น เช่น โค้ด Axios ผิด console.error("Axios error:", error.message); } console.error("Full error object:", error); }); } let profilesSend = command && command.commandSends.length > 0 ? command.commandSends .filter((x: any) => x.profileId != null) .map((x: any) => ({ receiverUserId: x.profileId, notiLink: "", isSendMail: x.commandSendCCs.map((x: any) => x.name == "EMAIL").length > 0, isSendInbox: x.commandSendCCs.map((x: any) => x.name == "INBOX").length > 0, isSendNotification: true, })) : []; const payloadStr = await PayloadSendNoti(command.id); const profilesSendRequest = new CallAPI() .PostData( { headers: { authorization: token } }, "/placement/noti/profiles-send", { subject: `${command.issue}`, body: `${command.issue}`, receiverUserIds: profilesSend, payload: payloadStr, // แนบไฟล์ (ถ้าจำเป็น) }, false, ) .catch((error) => { if (error.response) { // Server ตอบกลับ (มี status code 4xx หรือ 5xx) console.error("Error status:", error.response.status); console.error("Error data:", error.response.data); console.error("Error headers:", error.response.headers); } else if (error.request) { // ไม่มีการตอบกลับจาก server console.error("No response received:", error.request); } else { // เกิดข้อผิดพลาดอื่น เช่น โค้ด Axios ผิด console.error("Axios error:", error.message); } console.error("Full error object:", error); }); /*เฉพาะคำสั่ง C-PM-10 ให้ตัด profilesNotiRequest ที่ส่ง noti ครั้งแรกออก*/ if (["C-PM-10"].includes(command.commandType.code)) { await Promise.all([profilesSendRequest]); } else { await Promise.all([profilesNotiRequest!, profilesSendRequest]); } console.log("[AMQ] Send Notification Success"); return true; } catch (error) { console.error("[AMQ] Error:", error); return false; } } async function handler_org(msg: amqp.ConsumeMessage): Promise { //----> condition before process consume console.time("[AMQ] handler_org_total"); const startTime = Date.now(); console.log(`[AMQ] handler_org START at ${new Date(startTime).toISOString()}`); 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 repoProfile = AppDataSource.getRepository(Profile); 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 { id, status, lastUpdateUserId, lastUpdateFullName, lastUpdatedAt } = data; console.log(`[AMQ] Received message - revisionId: ${id}, status: ${status}`); const targetOrgRevision = await repoOrgRevision.findOne({ where: { id }, }); if (!targetOrgRevision) { console.error(`[AMQ] Skip publish: revision ${id} not found`); console.timeEnd("[AMQ] handler_org_total"); return true; } if (targetOrgRevision.orgRevisionIsCurrent && !targetOrgRevision.orgRevisionIsDraft) { console.log(`[AMQ] Skip publish: revision ${id} is already current`); console.timeEnd("[AMQ] handler_org_total"); return true; } if (!targetOrgRevision.orgRevisionIsDraft || targetOrgRevision.orgRevisionIsCurrent) { console.log( `[AMQ] Skip publish: revision ${id} is no longer publishable (isDraft=${targetOrgRevision.orgRevisionIsDraft}, isCurrent=${targetOrgRevision.orgRevisionIsCurrent})`, ); console.timeEnd("[AMQ] handler_org_total"); return true; } if (user) { sendWebSocket( "send-publish-org", { success: true, message: `ระบบกำลังทำการเผยแพร่โครงสร้างหน่วยงาน`, }, { userId: user?.sub }, ).catch(console.error); } console.time("[AMQ] query_revisions"); const [orgRevisionPublish, orgRevisionDraft] = await Promise.all([ repoOrgRevision .createQueryBuilder("orgRevision") .where("orgRevision.orgRevisionIsDraft = false") .andWhere("orgRevision.orgRevisionIsCurrent = true") .getOne(), repoOrgRevision .createQueryBuilder("orgRevision") .where("orgRevision.orgRevisionIsDraft = true") .andWhere("orgRevision.orgRevisionIsCurrent = false") .getOne(), ]); console.timeEnd("[AMQ] query_revisions"); console.log( `[AMQ] orgRevisionPublish found: ${orgRevisionPublish ? orgRevisionPublish.id : "null"}`, ); console.log(`[AMQ] orgRevisionDraft found: ${orgRevisionDraft ? orgRevisionDraft.id : "null"}`); // Validate: ต้องมี orgRevisionPublish เสมอสำหรับการเผยแพร่ if (!orgRevisionPublish) { console.error( "[AMQ] Cannot publish: No current org revision found (isDraft=false, isCurrent=true)", ); if (user) { sendWebSocket( "send-publish-org", { success: false, message: `ไม่พบข้อมูลโครงสร้างหน่วยงานปัจจุบัน ไม่สามารถเผยแพร่ได้`, }, { userId: user?.sub }, ).catch(console.error); } return false; } // Validate: ต้องมี orgRevisionDraft ที่จะเผยแพร่ if (!orgRevisionDraft) { console.error( "[AMQ] Cannot publish: No draft org revision found (isDraft=true, isCurrent=false)", ); if (user) { sendWebSocket( "send-publish-org", { success: false, message: `ไม่พบข้อมูลโครงสร้างหน่วยงานแบบร่าง ไม่สามารถเผยแพร่ได้`, }, { userId: user?.sub }, ).catch(console.error); } return false; } if (orgRevisionDraft.id !== targetOrgRevision.id) { console.log( `[AMQ] Skip publish: revision ${id} is stale because draft ${orgRevisionDraft.id} is now the active publish candidate`, ); console.timeEnd("[AMQ] handler_org_total"); return true; } // NOTE: ย้ายการอัปเดตสถานะไปไว้หลังจากทำงานเสร็จทั้งหมด // เพื่อป้องกันกรณี timeout/retry ทำให้สถานะเพี้ยน (ทุก row เป็น false,false) try { console.time("[AMQ] query_posMaster"); const posMaster = await repoPosmaster.find({ where: { orgRevisionId: id }, relations: [ "orgRoot", "orgChild4", "orgChild3", "orgChild2", "orgChild1", "positions", "positions.posLevel", "positions.posType", "positions.posExecutive", ], }); console.timeEnd("[AMQ] query_posMaster"); console.log(`[AMQ] posMaster count: ${posMaster.length}`); console.time("[AMQ] query_old_data"); const oldPosMasters = await repoPosmaster.find({ where: { orgRevisionId: orgRevisionPublish.id, }, select: ["id", "current_holderId", "ancestorDNA"], }); // Task #2160 ดึง posMasterAssign ของ revision เดิม const oldposMasterAssigns = await posMasterAssignRepository.find({ relations: ["posMaster"], where: { posMaster: { orgRevisionId: orgRevisionPublish.id, }, }, }); console.timeEnd("[AMQ] query_old_data"); console.log(`[AMQ] oldPosMasters count: ${oldPosMasters.length}`); console.log(`[AMQ] oldposMasterAssigns count: ${oldposMasterAssigns.length}`); console.time("[AMQ] build_assignMap"); // สร้าง assignMap เอาไว้เก็บ posMasterAssign.ancestorDNA ของ revision เดิม const assignMap = new Map(); for (const posmasterAssign of oldposMasterAssigns) { const dna = posmasterAssign.posMaster.ancestorDNA; if (!assignMap.has(dna)) { assignMap.set(dna, []); } assignMap.get(dna)!.push({ id: posmasterAssign.id, posMasterId: posmasterAssign.posMasterId, assignId: posmasterAssign.assignId, }); } console.timeEnd("[AMQ] build_assignMap"); console.time("[AMQ] query_oldposMasterAct"); // ดึง posMasterAct ของ revision เดิม xxx const oldposMasterAct = await posMasterActRepository.find({ relations: ["posMaster", "posMasterChild"], where: { posMaster: { orgRevisionId: orgRevisionPublish.id, }, }, }); console.timeEnd("[AMQ] query_oldposMasterAct"); console.log(`[AMQ] oldposMasterAct count: ${oldposMasterAct.length}`); type ActKey = string; // `${parentDNA}|${childDNA}` console.time("[AMQ] build_maps"); const posMasterActMap = new Map(); for (const act of oldposMasterAct) { const parentDNA = act.posMaster?.ancestorDNA?.trim() ?? ""; const childDNA = act.posMasterChild?.ancestorDNA?.trim() ?? ""; const key = `${parentDNA}|${childDNA}`; if (!posMasterActMap.has(key)) { posMasterActMap.set(key, []); } posMasterActMap.get(key)!.push(act); } const posMasterIdMap = new Map(); for (const pm of posMaster) { posMasterIdMap.set(pm.ancestorDNA?.trim() ?? "", pm.id); } const oldPosMasterMap = new Map(); for (const oldPm of oldPosMasters) { const dna = oldPm.ancestorDNA?.trim(); if (dna) { oldPosMasterMap.set(dna, oldPm); } } console.timeEnd("[AMQ] build_maps"); const _null: any = null; // ===== BATCH PROCESSING: เตรียมข้อมูลก่อน loop ===== console.time("[AMQ] prepare_batch_data"); // 1. รวบรวม profileIds ทั้งหมดที่ต้องอัพเดท const profileIds = posMaster .filter((item) => item.next_holderId != null) .map((item) => item.next_holderId!) .filter((id) => id != null && id !== ""); // 2. Batch load profiles ทั้งหมดในครั้งเดียว (แก้ปัญหา N+1 Query) const profilesMap = new Map(); if (profileIds.length > 0) { const profiles = await repoProfile.findBy({ id: In(profileIds), }); profiles.forEach((p) => profilesMap.set(p.id, p)); } console.log(`[AMQ] profiles to update: ${profilesMap.size}`); // 3. เตรียม arrays สำหรับ batch operations const profilesToSave: Profile[] = []; const posMasterAssignsToSave: PosMasterAssign[] = []; const historyCreateIds: string[] = []; const posMasterUpdates: { id: string; current_holderId: string | null | undefined }[] = []; // ===== LOOP: เก็บข้อมูลทั้งหมด ===== for (const item of posMaster) { const dna = item.ancestorDNA?.trim(); const oldPm = dna ? oldPosMasterMap.get(dna) : null; // Task #2160 Clone posMasterAssign const assigns = assignMap.get(item.ancestorDNA); if (assigns && assigns.length > 0) { const newAssigns = assigns.map(({ id, ...fields }) => posMasterAssignRepository.create({ ...fields, posMasterId: item.id, createdAt: lastUpdatedAt, createdFullName: lastUpdateFullName, createdUserId: lastUpdateUserId, lastUpdatedAt: lastUpdatedAt, lastUpdateFullName: lastUpdateFullName, lastUpdateUserId: lastUpdateUserId, }), ); posMasterAssignsToSave.push(...newAssigns); } // เตรียมข้อมูลสำหรับ update profile if (item.next_holderId != null && item.next_holderId !== "") { const profile = profilesMap.get(item.next_holderId); if (profile) { profile.posMasterNo = getPosMasterNo(item) ?? _null; profile.org = getOrgFullName(item) ?? _null; // ถ้าไม่ใช่ตำแหน่งนั่งทับ (isSit = false) ถึงจะอัพเดทตำแหน่งในทะเบียนประวัติ if (!item.isSit && item.positions.length > 0) { let position = item.positions.find((x) => x.positionIsSelected == true); if (position == null) { position = item.positions.find((x) => x.posLevelId == profile?.posLevelId); if (position == null) { const sorted = [...item.positions].sort((a, b) => a.orderNo - b.orderNo); position = sorted[0]; } } profile.posLevelId = position?.posLevelId ?? _null; profile.posTypeId = position?.posTypeId ?? _null; profile.position = position?.positionName ?? _null; profile.positionField = position?.positionField ?? _null; profile.posExecutive = position?.posExecutive?.posExecutiveName ?? _null; profile.positionArea = position?.positionArea ?? _null; profile.positionExecutiveField = position?.positionExecutiveField ?? _null; } profilesToSave.push(profile); } } // เก็บข้อมูลสำหรับ update posMaster posMasterUpdates.push({ id: item.id, current_holderId: item.next_holderId, }); // เก็บ IDs ที่ต้องสร้าง history const oldHolderId = oldPm ? oldPm.current_holderId : null; const newHolderId = item?.next_holderId; const isHolderChanged = oldHolderId !== newHolderId; if (isHolderChanged) { historyCreateIds.push(item.id); } } console.timeEnd("[AMQ] prepare_batch_data"); console.log( `[AMQ] Prepared - posMasterAssignsToSave: ${posMasterAssignsToSave.length}, profilesToSave: ${profilesToSave.length}, posMasterUpdates: ${posMasterUpdates.length}, historyCreateIds: ${historyCreateIds.length}`, ); // ===== BATCH EXECUTION: save ทีละ batch ===== let shouldSkipPublishInTransaction = false; await AppDataSource.transaction(async (manager) => { const repoPosmaster = manager.getRepository(PosMaster); const posMasterAssignRepository = manager.getRepository(PosMasterAssign); const posMasterActRepository = manager.getRepository(PosMasterAct); const permissionProfilesRepository = manager.getRepository(PermissionProfile); const repoEmployeePosmaster = manager.getRepository(EmployeePosMaster); const repoEmployeeTempPosmaster = manager.getRepository(EmployeeTempPosMaster); const repoProfile = manager.getRepository(Profile); const repoProfileEmployee = manager.getRepository(ProfileEmployee); const employeePositionRepository = manager.getRepository(EmployeePosition); const repoOrgRevision = manager.getRepository(OrgRevision); const orgRootRepository = manager.getRepository(OrgRoot); const child1Repository = manager.getRepository(OrgChild1); const child2Repository = manager.getRepository(OrgChild2); const child3Repository = manager.getRepository(OrgChild3); const child4Repository = manager.getRepository(OrgChild4); const targetOrgRevision = await repoOrgRevision .createQueryBuilder("orgRevision") .setLock("pessimistic_write") .where("orgRevision.id = :id", { id }) .getOne(); if (!targetOrgRevision) { shouldSkipPublishInTransaction = true; return; } if (targetOrgRevision.orgRevisionIsCurrent && !targetOrgRevision.orgRevisionIsDraft) { shouldSkipPublishInTransaction = true; return; } if (!targetOrgRevision.orgRevisionIsDraft || targetOrgRevision.orgRevisionIsCurrent) { shouldSkipPublishInTransaction = true; return; } const orgRevisionPublish = await repoOrgRevision .createQueryBuilder("orgRevision") .setLock("pessimistic_write") .where("orgRevision.orgRevisionIsDraft = false") .andWhere("orgRevision.orgRevisionIsCurrent = true") .getOne(); if (!orgRevisionPublish) { throw new Error("[AMQ] Cannot publish in transaction: no current org revision found"); } const orgRevisionDraft = await repoOrgRevision .createQueryBuilder("orgRevision") .setLock("pessimistic_write") .where("orgRevision.id = :id", { id }) .andWhere("orgRevision.orgRevisionIsDraft = true") .andWhere("orgRevision.orgRevisionIsCurrent = false") .getOne(); if (!orgRevisionDraft) { shouldSkipPublishInTransaction = true; return; } // 4. Batch save posMasterAssign (chunk 500) console.time("[AMQ] batch_save_posMasterAssign"); if (posMasterAssignsToSave.length > 0) { const chunks = chunkArray(posMasterAssignsToSave, 500); for (const chunk of chunks) { await posMasterAssignRepository.save(chunk); } } console.timeEnd("[AMQ] batch_save_posMasterAssign"); // 5. Batch save profiles (chunk 200) console.time("[AMQ] batch_save_profiles"); if (profilesToSave.length > 0) { const chunks = chunkArray(profilesToSave, 200); for (const chunk of chunks) { await repoProfile.save(chunk); } } console.timeEnd("[AMQ] batch_save_profiles"); // 6. Batch update posMasters console.time("[AMQ] batch_update_posMasters"); for (const update of posMasterUpdates) { await repoPosmaster.update(update.id, { current_holderId: update.current_holderId, next_holderId: null, lastUpdateUserId, lastUpdateFullName, lastUpdatedAt, }); } console.timeEnd("[AMQ] batch_update_posMasters"); // 7. Batch create history console.time("[AMQ] batch_create_history"); for (const id of historyCreateIds) { await CreatePosMasterHistoryOfficer(id, null, undefined, undefined, manager); } console.timeEnd("[AMQ] batch_create_history"); // Clone oldposMasterAct console.time("[AMQ] clone_oldposMasterAct"); for (const act of oldposMasterAct) { const parentDNA = act.posMaster?.ancestorDNA?.trim()?.toLowerCase() ?? ""; const childDNA = act.posMasterChild?.ancestorDNA?.trim()?.toLowerCase() ?? ""; const newParentId = posMasterIdMap.get(parentDNA); const newChildId = posMasterIdMap.get(childDNA); if (!newParentId || !newChildId) continue; const { id, posMaster, posMasterChild, ...fields } = act; const newAct = { ...fields, posMasterId: newParentId, posMasterChildId: newChildId, createdAt: new Date(), createdFullName: user ? user.name : "system", createdUserId: user ? user.sub : "system", lastUpdatedAt: new Date(), lastUpdateFullName: user ? user.name : "system", lastUpdateUserId: user ? user.sub : "system", }; await posMasterActRepository.save(newAct); } console.timeEnd("[AMQ] clone_oldposMasterAct"); console.time("[AMQ] clone_org_structure"); //new main revision const before = null; //ทุก orgRoot และ orgChild ข้างล่างนี้จะเป็นตัวเก่าที่ไม่ได้เป็น current revision //cone tree console.time("[AMQ] query_old_org_structure"); //หา dna tree const orgRoot = await orgRootRepository.find({ where: { orgRevisionId: orgRevisionPublish.id }, }); const orgChild1 = await child1Repository.find({ where: { orgRevisionId: orgRevisionPublish.id }, }); const orgChild2 = await child2Repository.find({ where: { orgRevisionId: orgRevisionPublish.id }, }); const orgChild3 = await child3Repository.find({ where: { orgRevisionId: orgRevisionPublish.id }, }); const orgChild4 = await child4Repository.find({ where: { orgRevisionId: orgRevisionPublish.id }, }); console.timeEnd("[AMQ] query_old_org_structure"); console.log( `[AMQ] Old structure - orgRoot: ${orgRoot.length}, orgChild1: ${orgChild1.length}, orgChild2: ${orgChild2.length}, orgChild3: ${orgChild3.length}, orgChild4: ${orgChild4.length}`, ); // Task #2172 ดึง orgRoot ของ revision ใหม่ const newRoots = await orgRootRepository.find({ where: { orgRevisionId: orgRevisionDraft.id }, }); // สร้าง newRootMap เอาไว้เก็บ orgRoot.ancestorDNA ของ revision ใหม่ const newRootMap = new Map(newRoots.map((r) => [r.ancestorDNA, r.id])); const emptyAncestorDNA = "00000000-0000-0000-0000-000000000000"; const hasEmptyAncestorDNA = (ancestorDNA?: string | null) => ancestorDNA == null || ancestorDNA === emptyAncestorDNA; const hasSelfOrEmptyAncestorDNA = (node: { id: string; ancestorDNA: string | null }) => node.ancestorDNA === node.id || hasEmptyAncestorDNA(node.ancestorDNA); const findMatchedNodeByAncestorDNA = ( nodes: T[], node: T, ) => nodes.find((item) => { if (hasSelfOrEmptyAncestorDNA(node)) { return hasEmptyAncestorDNA(item.ancestorDNA); } return item.ancestorDNA === node.ancestorDNA; }); const [ orgRootCurrent, orgChild1Current, orgChild2Current, orgChild3Current, orgChild4Current, ] = await Promise.all([ orgRootRepository.find({ where: { orgRevisionId: orgRevisionDraft.id } }), child1Repository.find({ where: { orgRevisionId: orgRevisionDraft.id } }), child2Repository.find({ where: { orgRevisionId: orgRevisionDraft.id } }), child3Repository.find({ where: { orgRevisionId: orgRevisionDraft.id } }), child4Repository.find({ where: { orgRevisionId: orgRevisionDraft.id } }), ]); console.time("[AMQ] clone_permissionProfiles"); // ดึง permissionProfiles ของ revision เดิม const oldPermissionProfiles = await permissionProfilesRepository.find({ relations: ["orgRootTree"], where: { orgRootTree: { orgRevisionId: orgRevisionPublish.id, }, }, }); const inserts: any[] = []; for (const permiss of oldPermissionProfiles) { // หา orgRootId ใหม่จาก newRootMap const newRootId = newRootMap.get(permiss.orgRootTree.ancestorDNA); if (!newRootId) continue; // ตัด id กับ orgRootTree ออกแล้วสร้าง object ใหม่ const { id, orgRootTree, ...fields } = permiss; // เตรียมข้อมูลสำหรับ insert inserts.push({ ...fields, orgRootId: newRootId, createdAt: lastUpdatedAt, createdFullName: lastUpdateFullName, createdUserId: lastUpdateUserId, lastUpdatedAt: lastUpdatedAt, lastUpdateFullName: lastUpdateFullName, lastUpdateUserId: lastUpdateUserId, }); } // ทำการ insert ข้อมูลใหม่ครั้งเดียว if (inserts.length > 0) { await permissionProfilesRepository.insert(inserts); } console.timeEnd("[AMQ] clone_permissionProfiles"); //หา dna posmaster ถ้าไม่มีให้เอาตัวเองเป็น dna console.time("[AMQ] query_employeePosMaster"); const orgemployeePosMaster = await repoEmployeePosmaster.find({ where: { orgRevisionId: orgRevisionPublish.id }, relations: ["positions"], }); console.timeEnd("[AMQ] query_employeePosMaster"); console.log(`[AMQ] orgemployeePosMaster count: ${orgemployeePosMaster.length}`); let _orgemployeePosMaster: EmployeePosMaster[]; const validProfileIds = new Set( (await repoProfileEmployee.find({ select: ["id"] })).map((p) => p.id), ); _orgemployeePosMaster = orgemployeePosMaster.map((x) => ({ ...x, current_holderId: x.current_holderId && validProfileIds.has(x.current_holderId) ? x.current_holderId : null, ancestorDNA: !x.ancestorDNA || x.ancestorDNA === "00000000-0000-0000-0000-000000000000" ? x.id : x.ancestorDNA, })); console.time("[AMQ] insert_employeePosMaster"); await repoEmployeePosmaster .createQueryBuilder() .insert() .into(EmployeePosMaster) .values(_orgemployeePosMaster) .orUpdate({ conflict_target: ["id"], overwrite: ["ancestorDNA"], }) .execute(); console.timeEnd("[AMQ] insert_employeePosMaster"); //หา dna posmaster ถ้าไม่มีให้เอาตัวเองเป็น dna console.time("[AMQ] query_employeeTempPosMaster"); const orgemployeeTempPosMaster = await repoEmployeeTempPosmaster.find({ where: { orgRevisionId: orgRevisionPublish.id }, relations: ["positions"], }); console.timeEnd("[AMQ] query_employeeTempPosMaster"); console.log(`[AMQ] orgemployeeTempPosMaster count: ${orgemployeeTempPosMaster.length}`); let _orgemployeeTempPosMaster: EmployeeTempPosMaster[]; _orgemployeeTempPosMaster = orgemployeeTempPosMaster.map((x) => ({ ...x, ancestorDNA: x.ancestorDNA == null || x.ancestorDNA == "00000000-0000-0000-0000-000000000000" ? x.id : x.ancestorDNA, })); await repoEmployeeTempPosmaster .createQueryBuilder() .insert() .into(EmployeeTempPosMaster) .values(_orgemployeeTempPosMaster) .orUpdate({ conflict_target: ["id"], overwrite: ["ancestorDNA"], }) .execute(); //create org console.time("[AMQ] forEach_orgRoot"); console.log(`[AMQ] Starting forEach orgRoot loop (${orgRoot.length} items)`); for (const x of orgRoot) { const dataId = x.id; const matchedOrgRoot = findMatchedNodeByAncestorDNA(orgRootCurrent, x); const filteredEmployeePosMaster = _orgemployeePosMaster.filter( (x: EmployeePosMaster) => x.orgRootId == dataId && x.orgChild1Id == null, ); await Promise.all( filteredEmployeePosMaster.map(async (item: any) => { delete item.id; const employeePosMaster = Object.assign(new EmployeePosMaster(), item); employeePosMaster.positions = []; employeePosMaster.orgRevisionId = orgRevisionDraft.id; employeePosMaster.orgRootId = matchedOrgRoot?.id ?? null; employeePosMaster.createdUserId = ""; employeePosMaster.createdFullName = "System Administrator"; employeePosMaster.createdAt = new Date(); employeePosMaster.lastUpdateUserId = ""; employeePosMaster.lastUpdateFullName = "System Administrator"; employeePosMaster.lastUpdatedAt = new Date(); await repoEmployeePosmaster.save(employeePosMaster); await Promise.all( item.positions.map(async (pos: any) => { delete pos.id; const employeePosition: EmployeePosition = Object.assign( new EmployeePosition(), pos, ); employeePosition.posMasterId = employeePosMaster.id; employeePosition.createdUserId = ""; employeePosition.createdFullName = "System Administrator"; employeePosition.createdAt = new Date(); employeePosition.lastUpdateUserId = ""; employeePosition.lastUpdateFullName = "System Administrator"; employeePosition.lastUpdatedAt = new Date(); await employeePositionRepository.save(employeePosition); }), ); }), ); await Promise.all( _orgemployeeTempPosMaster .filter((x: EmployeeTempPosMaster) => x.orgRootId == dataId && x.orgChild1Id == null) .map(async (item: any) => { delete item.id; const employeeTempPosMaster = Object.assign(new EmployeeTempPosMaster(), item); employeeTempPosMaster.positions = []; employeeTempPosMaster.orgRevisionId = orgRevisionDraft.id; employeeTempPosMaster.orgRootId = matchedOrgRoot?.id ?? null; employeeTempPosMaster.createdUserId = ""; employeeTempPosMaster.createdFullName = "System Administrator"; employeeTempPosMaster.createdAt = new Date(); employeeTempPosMaster.lastUpdateUserId = ""; employeeTempPosMaster.lastUpdateFullName = "System Administrator"; employeeTempPosMaster.lastUpdatedAt = new Date(); await repoEmployeeTempPosmaster.save(employeeTempPosMaster); await Promise.all( item.positions.map(async (pos: any) => { delete pos.id; const employeePosition: EmployeePosition = Object.assign( new EmployeePosition(), pos, ); employeePosition.posMasterTempId = employeeTempPosMaster.id; employeePosition.createdUserId = ""; employeePosition.createdFullName = "System Administrator"; employeePosition.createdAt = new Date(); employeePosition.lastUpdateUserId = ""; employeePosition.lastUpdateFullName = "System Administrator"; employeePosition.lastUpdatedAt = new Date(); await employeePositionRepository.save(employeePosition); }), ); }), ); for (const x of orgChild1.filter((item: OrgChild1) => item.orgRootId == dataId)) { const data1Id = x.id; const matchedOrgChild1 = findMatchedNodeByAncestorDNA(orgChild1Current, x); await Promise.all( _orgemployeePosMaster .filter((x: EmployeePosMaster) => x.orgChild1Id == data1Id && x.orgChild2Id == null) .map(async (item: any) => { delete item.id; const employeePosMaster = Object.assign(new EmployeePosMaster(), item); employeePosMaster.positions = []; employeePosMaster.orgRevisionId = orgRevisionDraft.id; employeePosMaster.orgRootId = matchedOrgRoot?.id ?? null; employeePosMaster.orgChild1Id = matchedOrgChild1?.id ?? null; employeePosMaster.createdUserId = ""; employeePosMaster.createdFullName = "System Administrator"; employeePosMaster.createdAt = new Date(); employeePosMaster.lastUpdateUserId = ""; employeePosMaster.lastUpdateFullName = "System Administrator"; employeePosMaster.lastUpdatedAt = new Date(); await repoEmployeePosmaster.save(employeePosMaster); await Promise.all( item.positions.map(async (pos: any) => { delete pos.id; const employeePosition: EmployeePosition = Object.assign( new EmployeePosition(), pos, ); employeePosition.posMasterId = employeePosMaster.id; employeePosition.createdUserId = ""; employeePosition.createdFullName = "System Administrator"; employeePosition.createdAt = new Date(); employeePosition.lastUpdateUserId = ""; employeePosition.lastUpdateFullName = "System Administrator"; employeePosition.lastUpdatedAt = new Date(); await employeePositionRepository.save(employeePosition); }), ); }), ); await Promise.all( _orgemployeeTempPosMaster .filter( (x: EmployeeTempPosMaster) => x.orgChild1Id == data1Id && x.orgChild2Id == null, ) .map(async (item: any) => { delete item.id; const employeeTempPosMaster = Object.assign(new EmployeeTempPosMaster(), item); employeeTempPosMaster.positions = []; employeeTempPosMaster.orgRevisionId = orgRevisionDraft.id; employeeTempPosMaster.orgRootId = matchedOrgRoot?.id ?? null; employeeTempPosMaster.orgChild1Id = matchedOrgChild1?.id ?? null; employeeTempPosMaster.createdUserId = ""; employeeTempPosMaster.createdFullName = "System Administrator"; employeeTempPosMaster.createdAt = new Date(); employeeTempPosMaster.lastUpdateUserId = ""; employeeTempPosMaster.lastUpdateFullName = "System Administrator"; employeeTempPosMaster.lastUpdatedAt = new Date(); await repoEmployeeTempPosmaster.save(employeeTempPosMaster); await Promise.all( item.positions.map(async (pos: any) => { delete pos.id; const employeePosition: EmployeePosition = Object.assign( new EmployeePosition(), pos, ); employeePosition.posMasterTempId = employeeTempPosMaster.id; employeePosition.createdUserId = ""; employeePosition.createdFullName = "System Administrator"; employeePosition.createdAt = new Date(); employeePosition.lastUpdateUserId = ""; employeePosition.lastUpdateFullName = "System Administrator"; employeePosition.lastUpdatedAt = new Date(); await employeePositionRepository.save(employeePosition); }), ); }), ); for (const x of orgChild2.filter((item: OrgChild2) => item.orgChild1Id == data1Id)) { const data2Id = x.id; const matchedOrgChild2 = findMatchedNodeByAncestorDNA(orgChild2Current, x); await Promise.all( _orgemployeePosMaster .filter((x: EmployeePosMaster) => x.orgChild2Id == data2Id && x.orgChild3Id == null) .map(async (item: any) => { delete item.id; const employeePosMaster = Object.assign(new EmployeePosMaster(), item); employeePosMaster.positions = []; employeePosMaster.orgRevisionId = orgRevisionDraft.id; employeePosMaster.orgRootId = matchedOrgRoot?.id ?? null; employeePosMaster.orgChild1Id = matchedOrgChild1?.id ?? null; employeePosMaster.orgChild2Id = matchedOrgChild2?.id ?? null; employeePosMaster.createdUserId = ""; employeePosMaster.createdFullName = "System Administrator"; employeePosMaster.createdAt = new Date(); employeePosMaster.lastUpdateUserId = ""; employeePosMaster.lastUpdateFullName = "System Administrator"; employeePosMaster.lastUpdatedAt = new Date(); await repoEmployeePosmaster.save(employeePosMaster); await Promise.all( item.positions.map(async (pos: any) => { delete pos.id; const employeePosition: EmployeePosition = Object.assign( new EmployeePosition(), pos, ); employeePosition.posMasterId = employeePosMaster.id; employeePosition.createdUserId = ""; employeePosition.createdFullName = "System Administrator"; employeePosition.createdAt = new Date(); employeePosition.lastUpdateUserId = ""; employeePosition.lastUpdateFullName = "System Administrator"; employeePosition.lastUpdatedAt = new Date(); await employeePositionRepository.save(employeePosition); }), ); }), ); await Promise.all( _orgemployeeTempPosMaster .filter( (x: EmployeeTempPosMaster) => x.orgChild2Id == data2Id && x.orgChild3Id == null, ) .map(async (item: any) => { delete item.id; const employeeTempPosMaster = Object.assign(new EmployeeTempPosMaster(), item); employeeTempPosMaster.positions = []; employeeTempPosMaster.orgRevisionId = orgRevisionDraft.id; employeeTempPosMaster.orgRootId = dataId; employeeTempPosMaster.orgChild1Id = data1Id; employeeTempPosMaster.orgChild2Id = data2Id; employeeTempPosMaster.createdUserId = ""; employeeTempPosMaster.createdFullName = "System Administrator"; employeeTempPosMaster.createdAt = new Date(); employeeTempPosMaster.lastUpdateUserId = ""; employeeTempPosMaster.lastUpdateFullName = "System Administrator"; employeeTempPosMaster.lastUpdatedAt = new Date(); await repoEmployeeTempPosmaster.save(employeeTempPosMaster); await Promise.all( item.positions.map(async (pos: any) => { delete pos.id; const employeePosition: EmployeePosition = Object.assign( new EmployeePosition(), pos, ); employeePosition.posMasterTempId = employeeTempPosMaster.id; employeePosition.createdUserId = ""; employeePosition.createdFullName = "System Administrator"; employeePosition.createdAt = new Date(); employeePosition.lastUpdateUserId = ""; employeePosition.lastUpdateFullName = "System Administrator"; employeePosition.lastUpdatedAt = new Date(); await employeePositionRepository.save(employeePosition); }), ); }), ); for (const x of orgChild3.filter((item: OrgChild3) => item.orgChild2Id == data2Id)) { const data3Id = x.id; const matchedOrgChild3 = findMatchedNodeByAncestorDNA(orgChild3Current, x); await Promise.all( _orgemployeePosMaster .filter( (x: EmployeePosMaster) => x.orgChild3Id == data3Id && x.orgChild4Id == null, ) .map(async (item: any) => { delete item.id; const employeePosMaster = Object.assign(new EmployeePosMaster(), item); employeePosMaster.positions = []; employeePosMaster.orgRevisionId = orgRevisionDraft.id; employeePosMaster.orgRootId = matchedOrgRoot?.id ?? null; employeePosMaster.orgChild1Id = matchedOrgChild1?.id ?? null; employeePosMaster.orgChild2Id = matchedOrgChild2?.id ?? null; employeePosMaster.orgChild3Id = matchedOrgChild3?.id ?? null; employeePosMaster.createdUserId = ""; employeePosMaster.createdFullName = "System Administrator"; employeePosMaster.createdAt = new Date(); employeePosMaster.lastUpdateUserId = ""; employeePosMaster.lastUpdateFullName = "System Administrator"; employeePosMaster.lastUpdatedAt = new Date(); await repoEmployeePosmaster.save(employeePosMaster); await Promise.all( item.positions.map(async (pos: any) => { delete pos.id; const employeePosition: EmployeePosition = Object.assign( new EmployeePosition(), pos, ); employeePosition.posMasterId = employeePosMaster.id; employeePosition.createdUserId = ""; employeePosition.createdFullName = "System Administrator"; employeePosition.createdAt = new Date(); employeePosition.lastUpdateUserId = ""; employeePosition.lastUpdateFullName = "System Administrator"; employeePosition.lastUpdatedAt = new Date(); await employeePositionRepository.save(employeePosition); }), ); }), ); await Promise.all( _orgemployeeTempPosMaster .filter( (x: EmployeeTempPosMaster) => x.orgChild3Id == data3Id && x.orgChild4Id == null, ) .map(async (item: any) => { delete item.id; const employeeTempPosMaster = Object.assign(new EmployeeTempPosMaster(), item); employeeTempPosMaster.positions = []; employeeTempPosMaster.orgRevisionId = orgRevisionDraft.id; employeeTempPosMaster.orgRootId = matchedOrgRoot?.id ?? null; employeeTempPosMaster.orgChild1Id = matchedOrgChild1?.id ?? null; employeeTempPosMaster.orgChild2Id = matchedOrgChild2?.id ?? null; employeeTempPosMaster.orgChild3Id = matchedOrgChild3?.id ?? null; employeeTempPosMaster.createdUserId = ""; employeeTempPosMaster.createdFullName = "System Administrator"; employeeTempPosMaster.createdAt = new Date(); employeeTempPosMaster.lastUpdateUserId = ""; employeeTempPosMaster.lastUpdateFullName = "System Administrator"; employeeTempPosMaster.lastUpdatedAt = new Date(); await repoEmployeeTempPosmaster.save(employeeTempPosMaster); await Promise.all( item.positions.map(async (pos: any) => { delete pos.id; const employeePosition: EmployeePosition = Object.assign( new EmployeePosition(), pos, ); employeePosition.posMasterTempId = employeeTempPosMaster.id; employeePosition.createdUserId = ""; employeePosition.createdFullName = "System Administrator"; employeePosition.createdAt = new Date(); employeePosition.lastUpdateUserId = ""; employeePosition.lastUpdateFullName = "System Administrator"; employeePosition.lastUpdatedAt = new Date(); await employeePositionRepository.save(employeePosition); }), ); }), ); for (const x of orgChild4.filter((item: OrgChild4) => item.orgChild3Id == data3Id)) { const data4Id = x.id; const matchedOrgChild4 = findMatchedNodeByAncestorDNA(orgChild4Current, x); await Promise.all( _orgemployeePosMaster .filter((x: EmployeePosMaster) => x.orgChild4Id == data4Id) .map(async (item: any) => { delete item.id; const employeePosMaster = Object.assign(new EmployeePosMaster(), item); employeePosMaster.positions = []; employeePosMaster.orgRevisionId = orgRevisionDraft.id; employeePosMaster.orgRootId = matchedOrgRoot?.id ?? null; employeePosMaster.orgChild1Id = matchedOrgChild1?.id ?? null; employeePosMaster.orgChild2Id = matchedOrgChild2?.id ?? null; employeePosMaster.orgChild3Id = matchedOrgChild3?.id ?? null; employeePosMaster.orgChild4Id = matchedOrgChild4?.id ?? null; employeePosMaster.createdUserId = ""; employeePosMaster.createdFullName = "System Administrator"; employeePosMaster.createdAt = new Date(); employeePosMaster.lastUpdateUserId = ""; employeePosMaster.lastUpdateFullName = "System Administrator"; employeePosMaster.lastUpdatedAt = new Date(); await repoEmployeePosmaster.save(employeePosMaster); await Promise.all( item.positions.map(async (pos: any) => { delete pos.id; const employeePosition: EmployeePosition = Object.assign( new EmployeePosition(), pos, ); employeePosition.posMasterId = employeePosMaster.id; employeePosition.createdUserId = ""; employeePosition.createdFullName = "System Administrator"; employeePosition.createdAt = new Date(); employeePosition.lastUpdateUserId = ""; employeePosition.lastUpdateFullName = "System Administrator"; employeePosition.lastUpdatedAt = new Date(); await employeePositionRepository.save(employeePosition); }), ); }), ); await Promise.all( _orgemployeeTempPosMaster .filter((x: EmployeeTempPosMaster) => x.orgChild4Id == data4Id) .map(async (item: any) => { delete item.id; const employeeTempPosMaster = Object.assign( new EmployeeTempPosMaster(), item, ); employeeTempPosMaster.positions = []; employeeTempPosMaster.orgRevisionId = orgRevisionDraft.id; employeeTempPosMaster.orgRootId = matchedOrgRoot?.id ?? null; employeeTempPosMaster.orgChild1Id = matchedOrgChild1?.id ?? null; employeeTempPosMaster.orgChild2Id = matchedOrgChild2?.id ?? null; employeeTempPosMaster.orgChild3Id = matchedOrgChild3?.id ?? null; employeeTempPosMaster.orgChild4Id = matchedOrgChild4?.id ?? null; employeeTempPosMaster.createdUserId = ""; employeeTempPosMaster.createdFullName = "System Administrator"; employeeTempPosMaster.createdAt = new Date(); employeeTempPosMaster.lastUpdateUserId = ""; employeeTempPosMaster.lastUpdateFullName = "System Administrator"; employeeTempPosMaster.lastUpdatedAt = new Date(); await repoEmployeeTempPosmaster.save(employeeTempPosMaster); await Promise.all( item.positions.map(async (pos: any) => { delete pos.id; const employeePosition: EmployeePosition = Object.assign( new EmployeePosition(), pos, ); employeePosition.posMasterTempId = employeeTempPosMaster.id; employeePosition.createdUserId = ""; employeePosition.createdFullName = "System Administrator"; employeePosition.createdAt = new Date(); employeePosition.lastUpdateUserId = ""; employeePosition.lastUpdateFullName = "System Administrator"; employeePosition.lastUpdatedAt = new Date(); await employeePositionRepository.save(employeePosition); }), ); }), ); } } } } } const employeePosMaster = await repoEmployeePosmaster.find({ where: { orgRevisionId: orgRevisionDraft.id }, relations: ["positions", "positions.posLevel", "positions.posType"], }); for (const item of employeePosMaster) { if (item.next_holderId != null) { const profile = await repoProfileEmployee.findOne({ where: { id: item.next_holderId == null ? "" : item.next_holderId }, }); const position = await item.positions.find((x) => x.positionIsSelected == true); const _null: any = null; if (profile != null) { profile.posLevelId = position?.posLevelId ?? _null; profile.posTypeId = position?.posTypeId ?? _null; profile.position = position?.positionName ?? _null; await repoProfileEmployee.save(profile); } } item.lastUpdateUserId = lastUpdateUserId; item.lastUpdateFullName = lastUpdateFullName; item.lastUpdatedAt = lastUpdatedAt; await repoEmployeePosmaster.save(item); } const employeeTempPosMaster = await repoEmployeeTempPosmaster.find({ where: { orgRevisionId: orgRevisionDraft.id }, relations: ["positions", "positions.posLevel", "positions.posType"], }); for (const item of employeeTempPosMaster) { if (item.next_holderId != null) { const profile = await repoProfileEmployee.findOne({ where: { id: item.next_holderId == null ? "" : item.next_holderId }, }); const position = await item.positions.find((x) => x.positionIsSelected == true); const _null: any = null; if (profile != null) { profile.posLevelId = position?.posLevelId ?? _null; profile.posTypeId = position?.posTypeId ?? _null; profile.position = position?.positionName ?? _null; await repoProfileEmployee.save(profile); } } item.lastUpdateUserId = lastUpdateUserId; item.lastUpdateFullName = lastUpdateFullName; item.lastUpdatedAt = lastUpdatedAt; await repoEmployeeTempPosmaster.save(item); } console.timeEnd("[AMQ] clone_org_structure"); console.time("[AMQ] save_revision_status"); orgRevisionPublish.orgRevisionIsDraft = false; orgRevisionPublish.orgRevisionIsCurrent = false; await repoOrgRevision.save(orgRevisionPublish); orgRevisionDraft.orgRevisionIsCurrent = true; orgRevisionDraft.orgRevisionIsDraft = false; await repoOrgRevision.save(orgRevisionDraft); console.timeEnd("[AMQ] save_revision_status"); }); if (shouldSkipPublishInTransaction) { console.log( `[AMQ] Skip publish in transaction: revision ${id} state changed before write phase`, ); console.timeEnd("[AMQ] handler_org_total"); return true; } console.log("[AMQ] Excecute Organization Success"); if (user) { sendWebSocket( "send-publish-org", { success: true, message: `ระบบทำการเผยแพร่โครงสร้างหน่วยงานเรียบร้อยแล้ว`, }, { userId: user?.sub }, ).catch(console.error); } console.log(`[AMQ] handler_org SUCCESS - Total time: ${Date.now() - startTime}ms`); console.timeEnd("[AMQ] handler_org_total"); return true; } catch (error) { const totalTime = Date.now() - startTime; console.error(`[AMQ] handler_org ERROR after ${totalTime}ms:`, error); if (user) { sendWebSocket( "send-publish-org", { success: false, message: `ระบบทำการเผยแพร่โครงสร้างหน่วยงานไม่สำเร็จ`, }, { userId: user?.sub }, ).catch(console.error); } console.timeEnd("[AMQ] handler_org_total"); return false; } } async function handler_org_draft(msg: amqp.ConsumeMessage): Promise { const { data, token, user } = JSON.parse(msg.content.toString()); const { requestBody, request, revision } = data; const posMasterRepository = AppDataSource.getRepository(PosMaster); const positionRepository = AppDataSource.getRepository(Position); const employeePosMasterRepository = AppDataSource.getRepository(EmployeePosMaster); const employeeTempPosMasterRepository = AppDataSource.getRepository(EmployeeTempPosMaster); const posMasterAssignRepository = AppDataSource.getRepository(PosMasterAssign); const posMasterActRepository = AppDataSource.getRepository(PosMasterAct); const permissionOrgRepository = AppDataSource.getRepository(PermissionOrg); const employeePositionRepository = AppDataSource.getRepository(EmployeePosition); const orgRevisionRepository = 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); if (user) { sendWebSocket( "send-create-draft-org", { success: true, message: `ระบบกำลังทำการสร้างแบบร่างโครงสร้างหน่วยงาน`, }, { userId: user?.sub }, ).catch(console.error); } try { //cone tree if ( requestBody.typeDraft.toUpperCase() == "ORG" || requestBody.typeDraft.toUpperCase() == "ORG_POSITION" || requestBody.typeDraft.toUpperCase() == "ORG_POSITION_PERSON" || requestBody.typeDraft.toUpperCase() == "ORG_POSITION_ROLE" || requestBody.typeDraft.toUpperCase() == "ORG_POSITION_PERSON_ROLE" ) { //cone by revisionId if (requestBody.orgRevisionId == null) throw new HttpError(HttpStatusCode.NOT_FOUND, "not found."); const _revision = await orgRevisionRepository.findOne({ where: { id: requestBody.orgRevisionId }, }); if (!_revision) throw new HttpError(HttpStatusCode.NOT_FOUND, "not found."); //หา dna tree ถ้าไม่มีให้เอาตัวเองเป็น dna const orgRoot = await orgRootRepository.find({ where: { orgRevisionId: requestBody.orgRevisionId }, order: { orgRootOrder: "ASC" }, }); let _orgRoot: any = orgRoot.map((x) => ({ ...x, ancestorDNA: x.ancestorDNA == null || x.ancestorDNA == "00000000-0000-0000-0000-000000000000" ? x.id : x.ancestorDNA, })); await orgRootRepository.save(_orgRoot); const orgChild1 = await child1Repository.find({ where: { orgRevisionId: requestBody.orgRevisionId }, order: { orgChild1Order: "ASC" }, }); let _orgChild1: any = orgChild1.map((x) => ({ ...x, ancestorDNA: x.ancestorDNA == null || x.ancestorDNA == "00000000-0000-0000-0000-000000000000" ? x.id : x.ancestorDNA, })); await child1Repository.save(_orgChild1); const orgChild2 = await child2Repository.find({ where: { orgRevisionId: requestBody.orgRevisionId }, order: { orgChild2Order: "ASC" }, }); let _orgChild2: any = orgChild2.map((x) => ({ ...x, ancestorDNA: x.ancestorDNA == null || x.ancestorDNA == "00000000-0000-0000-0000-000000000000" ? x.id : x.ancestorDNA, })); await child2Repository.save(_orgChild2); const orgChild3 = await child3Repository.find({ where: { orgRevisionId: requestBody.orgRevisionId }, order: { orgChild3Order: "ASC" }, }); let _orgChild3: any = orgChild3.map((x) => ({ ...x, ancestorDNA: x.ancestorDNA == null || x.ancestorDNA == "00000000-0000-0000-0000-000000000000" ? x.id : x.ancestorDNA, })); await child3Repository.save(_orgChild3); const orgChild4 = await child4Repository.find({ where: { orgRevisionId: requestBody.orgRevisionId }, order: { orgChild4Order: "ASC" }, }); let _orgChild4: any = orgChild4.map((x) => ({ ...x, ancestorDNA: x.ancestorDNA == null || x.ancestorDNA == "00000000-0000-0000-0000-000000000000" ? x.id : x.ancestorDNA, })); await child4Repository.save(_orgChild4); //หา dna posmaster ถ้าไม่มีให้เอาตัวเองเป็น dna const orgPosMaster = await posMasterRepository.find({ where: { orgRevisionId: requestBody.orgRevisionId }, relations: ["positions"], }); let _orgPosMaster: PosMaster[] = []; if ( requestBody.typeDraft.toUpperCase() == "ORG_POSITION" || requestBody.typeDraft.toUpperCase() == "ORG_POSITION_PERSON" || requestBody.typeDraft.toUpperCase() == "ORG_POSITION_ROLE" || requestBody.typeDraft.toUpperCase() == "ORG_POSITION_PERSON_ROLE" ) { _orgPosMaster = orgPosMaster .filter( (x) => x.ancestorDNA == null || x.ancestorDNA == "00000000-0000-0000-0000-000000000000", ) .map((x) => ({ ...x, ancestorDNA: x.ancestorDNA == null || x.ancestorDNA == "00000000-0000-0000-0000-000000000000" ? x.id : x.ancestorDNA, })); await posMasterRepository.save(_orgPosMaster); } // Create org for await (const x0 of _orgRoot) { var dataId = x0.id; var _null: any = null; // console.log(`ch0 ${x0.orgRootOrder}`); // console.log(dataId); delete x0.id; const data = Object.assign(new OrgRoot(), x0); data.orgRevisionId = revision.id; data.createdUserId = request.sub; data.createdFullName = request.name; data.createdAt = new Date(); data.lastUpdateUserId = request.sub; data.lastUpdateFullName = request.name; data.lastUpdatedAt = new Date(); await orgRootRepository.save(data); const orgTypes = new Set([ "ORG_POSITION", "ORG_POSITION_PERSON", "ORG_POSITION_ROLE", "ORG_POSITION_PERSON_ROLE", ]); if (orgTypes.has(requestBody.typeDraft.toUpperCase())) { // Create posMaster for await (const item of orgPosMaster.filter( (x: PosMaster) => x.orgRootId == dataId && x.orgChild1Id == null, ) as any) { let posMasterAssign = await posMasterAssignRepository.find({ where: { posMasterId: item.id }, }); delete item.id; const posMaster = Object.assign(new PosMaster(), item); posMaster.positions = []; if ( ["ORG_POSITION_PERSON", "ORG_POSITION_PERSON_ROLE"].includes( requestBody.typeDraft.toUpperCase(), ) ) { posMaster.next_holderId = item.current_holderId; // if (posMaster.next_holderId) { // posMaster.conditionReason = _null; // posMaster.isCondition = false; // } } else { posMaster.next_holderId = null; posMaster.isSit = false; } if ( ["ORG_POSITION_ROLE", "ORG_POSITION_PERSON_ROLE"].includes( requestBody.typeDraft.toUpperCase(), ) ) { posMaster.authRoleId = item.authRoleId; } else { posMaster.authRoleId = null; } posMaster.current_holderId = null; posMaster.orgRevisionId = revision.id; posMaster.orgRootId = data.id; posMaster.createdUserId = request.sub; posMaster.createdFullName = request.name; posMaster.createdAt = new Date(); posMaster.lastUpdateUserId = request.sub; posMaster.lastUpdateFullName = request.name; posMaster.lastUpdatedAt = new Date(); await posMasterRepository.save(posMaster); // // Copy assignments // await posMasterAssignRepository.save( // posMasterAssign.map(({ id, ...rest }: PosMasterAssign) => ({ // ...rest, // posMasterId: posMaster.id, // })), // ); // Create positions for await (const pos of item.positions) { delete pos.id; const position = Object.assign(new Position(), pos); position.posMasterId = posMaster.id; if ( ["ORG_POSITION", "ORG_POSITION_ROLE"].includes(requestBody.typeDraft.toUpperCase()) ) { position.positionIsSelected = false; } position.createdUserId = request.sub; position.createdFullName = request.name; position.createdAt = new Date(); position.lastUpdateUserId = request.sub; position.lastUpdateFullName = request.name; position.lastUpdatedAt = new Date(); await positionRepository.save(position); } } } // Create orgChild1 for await (const x1 of _orgChild1.filter((x: OrgChild1) => x.orgRootId == dataId)) { var data1Id = x1.id; // console.log(`ch1 ${x1.orgChild1Order}`); // console.log(data1Id); delete x1.id; const data1 = Object.assign(new OrgChild1(), x1); data1.orgRootId = data.id; data1.orgRevisionId = revision.id; data1.createdUserId = request.sub; data1.createdFullName = request.name; data1.createdAt = new Date(); data1.lastUpdateUserId = request.sub; data1.lastUpdateFullName = request.name; data1.lastUpdatedAt = new Date(); await child1Repository.save(data1); if (orgTypes.has(requestBody.typeDraft.toUpperCase())) { // Create posMaster for await (const item of orgPosMaster.filter( (x: PosMaster) => x.orgChild1Id == data1Id && x.orgChild2Id == null, ) as any) { let posMasterAssign = await posMasterAssignRepository.find({ where: { posMasterId: item.id }, }); delete item.id; const posMaster = Object.assign(new PosMaster(), item); posMaster.positions = []; if ( ["ORG_POSITION_PERSON", "ORG_POSITION_PERSON_ROLE"].includes( requestBody.typeDraft.toUpperCase(), ) ) { posMaster.next_holderId = item.current_holderId; // if (posMaster.next_holderId) { // posMaster.conditionReason = _null; // posMaster.isCondition = false; // } } else { posMaster.next_holderId = null; posMaster.isSit = false; } if ( ["ORG_POSITION_ROLE", "ORG_POSITION_PERSON_ROLE"].includes( requestBody.typeDraft.toUpperCase(), ) ) { posMaster.authRoleId = item.authRoleId; } else { posMaster.authRoleId = null; } posMaster.current_holderId = null; posMaster.orgRevisionId = revision.id; posMaster.orgRootId = data.id; posMaster.orgChild1Id = data1.id; posMaster.createdUserId = request.sub; posMaster.createdFullName = request.name; posMaster.createdAt = new Date(); posMaster.lastUpdateUserId = request.sub; posMaster.lastUpdateFullName = request.name; posMaster.lastUpdatedAt = new Date(); await posMasterRepository.save(posMaster); // // Copy assignments // await posMasterAssignRepository.save( // posMasterAssign.map(({ id, ...rest }: PosMasterAssign) => ({ // ...rest, // posMasterId: posMaster.id, // })), // ); // Create positions for await (const pos of item.positions) { delete pos.id; const position = Object.assign(new Position(), pos); position.posMasterId = posMaster.id; if ( ["ORG_POSITION", "ORG_POSITION_ROLE"].includes( requestBody.typeDraft.toUpperCase(), ) ) { position.positionIsSelected = false; } position.createdUserId = request.sub; position.createdFullName = request.name; position.createdAt = new Date(); position.lastUpdateUserId = request.sub; position.lastUpdateFullName = request.name; position.lastUpdatedAt = new Date(); await positionRepository.save(position); } } } for await (const x2 of _orgChild2.filter((x: OrgChild2) => x.orgChild1Id == data1Id)) { var data2Id = x2.id; // console.log(`ch2 ${x2.orgChild2Order}`); // console.log(data2Id); delete x2.id; const data2 = Object.assign(new OrgChild2(), x2); data2.orgChild1Id = data1.id; data2.orgRootId = data.id; data2.orgRevisionId = revision.id; data2.createdUserId = request.sub; data2.createdFullName = request.name; data2.createdAt = new Date(); data2.lastUpdateUserId = request.sub; data2.lastUpdateFullName = request.name; data2.lastUpdatedAt = new Date(); await child2Repository.save(data2); if (orgTypes.has(requestBody.typeDraft.toUpperCase())) { // Create posMaster for await (const item of orgPosMaster.filter( (x: PosMaster) => x.orgChild2Id == data2Id && x.orgChild3Id == null, ) as any) { let posMasterAssign = await posMasterAssignRepository.find({ where: { posMasterId: item.id }, }); delete item.id; const posMaster = Object.assign(new PosMaster(), item); posMaster.positions = []; if ( ["ORG_POSITION_PERSON", "ORG_POSITION_PERSON_ROLE"].includes( requestBody.typeDraft.toUpperCase(), ) ) { posMaster.next_holderId = item.current_holderId; // if (posMaster.next_holderId) { // posMaster.conditionReason = _null; // posMaster.isCondition = false; // } } else { posMaster.next_holderId = null; posMaster.isSit = false; } if ( ["ORG_POSITION_ROLE", "ORG_POSITION_PERSON_ROLE"].includes( requestBody.typeDraft.toUpperCase(), ) ) { posMaster.authRoleId = item.authRoleId; } else { posMaster.authRoleId = null; } posMaster.current_holderId = null; posMaster.orgRevisionId = revision.id; posMaster.orgRootId = data.id; posMaster.orgChild1Id = data1.id; posMaster.orgChild2Id = data2.id; posMaster.createdUserId = request.sub; posMaster.createdFullName = request.name; posMaster.createdAt = new Date(); posMaster.lastUpdateUserId = request.sub; posMaster.lastUpdateFullName = request.name; posMaster.lastUpdatedAt = new Date(); await posMasterRepository.save(posMaster); // // Copy assignments // await posMasterAssignRepository.save( // posMasterAssign.map(({ id, ...rest }: PosMasterAssign) => ({ // ...rest, // posMasterId: posMaster.id, // })), // ); // Create positions for await (const pos of item.positions) { delete pos.id; const position = Object.assign(new Position(), pos); position.posMasterId = posMaster.id; if ( ["ORG_POSITION", "ORG_POSITION_ROLE"].includes( requestBody.typeDraft.toUpperCase(), ) ) { position.positionIsSelected = false; } position.createdUserId = request.sub; position.createdFullName = request.name; position.createdAt = new Date(); position.lastUpdateUserId = request.sub; position.lastUpdateFullName = request.name; position.lastUpdatedAt = new Date(); await positionRepository.save(position); } } } // Create org for await (const x3 of _orgChild3.filter((x: OrgChild3) => x.orgChild2Id == data2Id)) { var data3Id = x3.id; // console.log(`ch3 ${x3.orgChild3Order}`); // console.log(data3Id); delete x3.id; const data3 = Object.assign(new OrgChild3(), x3); data3.orgChild2Id = data2.id; data3.orgChild1Id = data1.id; data3.orgRootId = data.id; data3.orgRevisionId = revision.id; data3.createdUserId = request.sub; data3.createdFullName = request.name; data3.createdAt = new Date(); data3.lastUpdateUserId = request.sub; data3.lastUpdateFullName = request.name; data3.lastUpdatedAt = new Date(); await child3Repository.save(data3); if (orgTypes.has(requestBody.typeDraft.toUpperCase())) { // Create posMaster for await (const item of orgPosMaster.filter( (x: PosMaster) => x.orgChild3Id == data3Id && x.orgChild4Id == null, ) as any) { let posMasterAssign = await posMasterAssignRepository.find({ where: { posMasterId: item.id }, }); delete item.id; const posMaster = Object.assign(new PosMaster(), item); posMaster.positions = []; if ( ["ORG_POSITION_PERSON", "ORG_POSITION_PERSON_ROLE"].includes( requestBody.typeDraft.toUpperCase(), ) ) { posMaster.next_holderId = item.current_holderId; // if (posMaster.next_holderId) { // posMaster.conditionReason = _null; // posMaster.isCondition = false; // } } else { posMaster.next_holderId = null; posMaster.isSit = false; } if ( ["ORG_POSITION_ROLE", "ORG_POSITION_PERSON_ROLE"].includes( requestBody.typeDraft.toUpperCase(), ) ) { posMaster.authRoleId = item.authRoleId; } else { posMaster.authRoleId = null; } posMaster.current_holderId = null; posMaster.orgRevisionId = revision.id; posMaster.orgRootId = data.id; posMaster.orgChild1Id = data1.id; posMaster.orgChild2Id = data2.id; posMaster.orgChild3Id = data3.id; posMaster.createdUserId = request.sub; posMaster.createdFullName = request.name; posMaster.createdAt = new Date(); posMaster.lastUpdateUserId = request.sub; posMaster.lastUpdateFullName = request.name; posMaster.lastUpdatedAt = new Date(); await posMasterRepository.save(posMaster); // // Copy assignments // await posMasterAssignRepository.save( // posMasterAssign.map(({ id, ...rest }: PosMasterAssign) => ({ // ...rest, // posMasterId: posMaster.id, // })), // ); // Create positions for await (const pos of item.positions) { delete pos.id; const position = Object.assign(new Position(), pos); position.posMasterId = posMaster.id; if ( ["ORG_POSITION", "ORG_POSITION_ROLE"].includes( requestBody.typeDraft.toUpperCase(), ) ) { position.positionIsSelected = false; } position.createdUserId = request.sub; position.createdFullName = request.name; position.createdAt = new Date(); position.lastUpdateUserId = request.sub; position.lastUpdateFullName = request.name; position.lastUpdatedAt = new Date(); await positionRepository.save(position); } } } // Create org for await (const x4 of _orgChild4.filter( (x: OrgChild4) => x.orgChild3Id == data3Id, )) { var data4Id = x4.id; // console.log(`ch4 ${x4.orgChild4Order}`); // console.log(data4Id); delete x4.id; const data4 = Object.assign(new OrgChild4(), x4); data4.orgChild3Id = data3.id; data4.orgChild2Id = data2.id; data4.orgChild1Id = data1.id; data4.orgRootId = data.id; data4.orgRevisionId = revision.id; data4.createdUserId = request.sub; data4.createdFullName = request.name; data4.createdAt = new Date(); data4.lastUpdateUserId = request.sub; data4.lastUpdateFullName = request.name; data4.lastUpdatedAt = new Date(); await child4Repository.save(data4); if (orgTypes.has(requestBody.typeDraft.toUpperCase())) { // Create posMaster for await (const item of orgPosMaster.filter( (x: PosMaster) => x.orgChild4Id == data4Id, ) as any) { let posMasterAssign = await posMasterAssignRepository.find({ where: { posMasterId: item.id }, }); delete item.id; const posMaster = Object.assign(new PosMaster(), item); posMaster.positions = []; if ( ["ORG_POSITION_PERSON", "ORG_POSITION_PERSON_ROLE"].includes( requestBody.typeDraft.toUpperCase(), ) ) { posMaster.next_holderId = item.current_holderId; // if (posMaster.next_holderId) { // posMaster.conditionReason = _null; // posMaster.isCondition = false; // } } else { posMaster.next_holderId = null; posMaster.isSit = false; } if ( ["ORG_POSITION_ROLE", "ORG_POSITION_PERSON_ROLE"].includes( requestBody.typeDraft.toUpperCase(), ) ) { posMaster.authRoleId = item.authRoleId; } else { posMaster.authRoleId = null; } posMaster.current_holderId = null; posMaster.orgRevisionId = revision.id; posMaster.orgRootId = data.id; posMaster.orgChild1Id = data1.id; posMaster.orgChild2Id = data2.id; posMaster.orgChild3Id = data3.id; posMaster.orgChild4Id = data4.id; posMaster.createdUserId = request.sub; posMaster.createdFullName = request.name; posMaster.createdAt = new Date(); posMaster.lastUpdateUserId = request.sub; posMaster.lastUpdateFullName = request.name; posMaster.lastUpdatedAt = new Date(); await posMasterRepository.save(posMaster); // // Copy assignments // await posMasterAssignRepository.save( // posMasterAssign.map(({ id, ...rest }: PosMasterAssign) => ({ // ...rest, // posMasterId: posMaster.id, // })), // ); // Create positions for await (const pos of item.positions) { delete pos.id; const position = Object.assign(new Position(), pos); position.posMasterId = posMaster.id; if ( ["ORG_POSITION", "ORG_POSITION_ROLE"].includes( requestBody.typeDraft.toUpperCase(), ) ) { position.positionIsSelected = false; } position.createdUserId = request.sub; position.createdFullName = request.name; position.createdAt = new Date(); position.lastUpdateUserId = request.sub; position.lastUpdateFullName = request.name; position.lastUpdatedAt = new Date(); await positionRepository.save(position); } } } } } } } } } //ลบ daft เก่าที่ค้างแล้วยังไม่ได้เผยแพร่ const _orgRevisions = await orgRevisionRepository.find({ where: [{ orgRevisionIsDraft: true, id: Not(revision.id) }], }); const _roots = await orgRootRepository.find({ where: [{ orgRevisionId: In(_orgRevisions.map((x) => x.id)) }], }); const _posMasters = await posMasterRepository.find({ where: [{ orgRevisionId: In(_orgRevisions.map((x) => x.id)) }], }); const _employeePosMasters = await employeePosMasterRepository.find({ where: [{ orgRevisionId: In(_orgRevisions.map((x) => x.id)) }], }); const _employeeTempPosMasters = await employeeTempPosMasterRepository.find({ where: [{ orgRevisionId: In(_orgRevisions.map((x) => x.id)) }], }); await positionRepository.delete({ posMasterId: In(_posMasters.map((x) => x.id)) }); await employeePositionRepository.delete({ posMasterId: In(_employeePosMasters.map((x) => x.id)), }); await employeePositionRepository.delete({ posMasterTempId: In(_employeeTempPosMasters.map((x) => x.id)), }); await posMasterAssignRepository.delete({ posMasterId: In(_posMasters.map((x) => x.id)) }); await posMasterActRepository.delete({ posMasterId: In(_posMasters.map((x) => x.id)) }); //ใช้ posMasterId ของ revision: draft *แต่ยังไม่เจอช็อดไหนที่ใช้โครงสร้างแบบร่างในรักษาการแทน await posMasterActRepository.delete({ //ใช้ posMasterId ของ revision: draft *แต่ยังไม่เจอช็อดไหนที่ใช้โครงสร้างแบบร่างในรักษาการแทน posMasterChildId: In(_posMasters.map((x) => x.id)), }); // await posMasterRepository.remove(_posMasters); const batchSize = 1000; const removeInBatches = async (repository: any, data: any, label: any) => { for (let i = 0; i < data.length; i += batchSize) { const batch = data.slice(i, i + batchSize); try { await repository.remove(batch); console.log(`Removed ${label} batch ${i / batchSize + 1}`); } catch (error) { console.log(`Failed to remove ${label} batch ${i / batchSize + 1}`); } } }; await removeInBatches(posMasterRepository, _posMasters, "PosMaster"); await removeInBatches(employeePosMasterRepository, _employeePosMasters, "EmployeePosMaster"); await removeInBatches( employeeTempPosMasterRepository, _employeeTempPosMasters, "EmployeeTempPosMaster", ); await child4Repository.delete({ orgRevisionId: In(_orgRevisions.map((x) => x.id)) }); await child3Repository.delete({ orgRevisionId: In(_orgRevisions.map((x) => x.id)) }); await child2Repository.delete({ orgRevisionId: In(_orgRevisions.map((x) => x.id)) }); await child1Repository.delete({ orgRevisionId: In(_orgRevisions.map((x) => x.id)) }); // Task #2160 อัพเดทหน้าที่จัดการโครงสร้างแบบร่าง if ( [ "ORG", "ORG_POSITION", "ORG_POSITION_PERSON", "ORG_POSITION_ROLE", "ORG_POSITION_PERSON_ROLE", ].includes(requestBody.typeDraft?.toUpperCase()) ) { const _newRoots = await orgRootRepository.find({ where: { orgRevisionId: revision.id }, }); const newRootMap = new Map(_newRoots.map((r) => [r.ancestorDNA, r.id])); for (const oldRoot of _roots) { const newRootId = newRootMap.get(oldRoot.ancestorDNA); if (!newRootId) continue; // อัพเดท orgRootId ที่อยู่ภายใต้ orgRevision แบบร่างเดิมเป็นของ orgRevision แบบร่างใหม่ await permissionOrgRepository.update({ orgRootId: oldRoot.id }, { orgRootId: newRootId }); } } else { await permissionOrgRepository.delete({ orgRootId: In(_roots.map((x) => x.id)), }); } await orgRootRepository.delete({ orgRevisionId: In(_orgRevisions.map((x) => x.id)) }); await orgRevisionRepository.remove(_orgRevisions); console.log("[AMQ] Create Draft Success"); if (user) { await sendWebSocket( "send-create-draft-org", { success: true, message: `ระบบทำการสร้างแบบร่างโครงสร้างหน่วยงานเรียบร้อยแล้ว`, }, { userId: user?.sub }, ).catch(console.error); } return true; } catch (error) { console.error(error); if (user) { await sendWebSocket( "send-create-draft-org", { success: false, message: `ระบบทำการสร้างแบบร่างโครงสร้างหน่วยงานไม่สำเร็จ`, }, { userId: user?.sub }, ).catch(console.error); } return false; } }