diff --git a/src/services/rabbitmq.ts b/src/services/rabbitmq.ts index 6ba40258..fe70b580 100644 --- a/src/services/rabbitmq.ts +++ b/src/services/rabbitmq.ts @@ -631,43 +631,67 @@ async function handler_org(msg: amqp.ConsumeMessage): Promise { } const _null: any = null; - for (const item of posMaster) { + // ===== BATCH PROCESSING: เตรียมข้อมูลก่อน loop ===== + // 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)); + } + + // 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 + // Task #2160 Clone posMasterAssign const assigns = assignMap.get(item.ancestorDNA); if (assigns && assigns.length > 0) { - const newAssigns = assigns.map(({ id, ...fields }) => ({ - ...fields, // copy ทุก field ยกเว้น id - posMasterId: item.id, // ผูกกับ posMasterId ใหม่ - createdAt: lastUpdatedAt, - createdFullName: lastUpdateFullName, - createdUserId: lastUpdateUserId, - lastUpdatedAt: lastUpdatedAt, - lastUpdateFullName: lastUpdateFullName, - lastUpdateUserId: lastUpdateUserId, - })); - await posMasterAssignRepository.save(newAssigns); + 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); } - // อัพเดท org และ posMasterNo ตลอดไม่ต้องดัก isSit - if (item.next_holderId != null) { - const profile = await repoProfile.findOne({ - where: { id: item.next_holderId == null ? "" : item.next_holderId }, - }); - if (profile != null) { + // เตรียมข้อมูลสำหรับ 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 = await item.positions.find((x) => x.positionIsSelected == true); + let position = item.positions.find((x) => x.positionIsSelected == true); if (position == null) { - position = await item.positions.find((x) => x.posLevelId == profile?.posLevelId); + position = item.positions.find((x) => x.posLevelId == profile?.posLevelId); if (position == null) { - position = await item.positions.sort((a, b) => a.orderNo - b.orderNo)[0]; + const sorted = [...item.positions].sort((a, b) => a.orderNo - b.orderNo); + position = sorted[0]; } } @@ -679,29 +703,59 @@ async function handler_org(msg: amqp.ConsumeMessage): Promise { profile.positionArea = position?.positionArea ?? _null; profile.positionExecutiveField = position?.positionExecutiveField ?? _null; } - await repoProfile.save(profile); + + profilesToSave.push(profile); } } - // item.current_holderId = item.next_holderId; - // item.next_holderId = null; - // item.lastUpdateUserId = lastUpdateUserId; - // item.lastUpdateFullName = lastUpdateFullName; - // item.lastUpdatedAt = lastUpdatedAt; - await repoPosmaster.update(item.id, { + + // เก็บข้อมูลสำหรับ 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); + } + } + + // ===== BATCH EXECUTION: save ทีละ batch ===== + + // 4. Batch save posMasterAssign (chunk 500) + if (posMasterAssignsToSave.length > 0) { + const chunks = chunkArray(posMasterAssignsToSave, 500); + for (const chunk of chunks) { + await posMasterAssignRepository.save(chunk); + } + } + + // 5. Batch save profiles (chunk 200) + if (profilesToSave.length > 0) { + const chunks = chunkArray(profilesToSave, 200); + for (const chunk of chunks) { + await repoProfile.save(chunk); + } + } + + // 6. Batch update posMasters + for (const update of posMasterUpdates) { + await repoPosmaster.update(update.id, { + current_holderId: update.current_holderId, next_holderId: null, lastUpdateUserId, lastUpdateFullName, lastUpdatedAt, }); + } - const oldHolderId = oldPm ? oldPm.current_holderId : null; - const newHolderId = item ? item.next_holderId : null; - const isHolderChanged = oldHolderId !== newHolderId; - - if (isHolderChanged) { - await CreatePosMasterHistoryOfficer(item.id, null); - } + // 7. Batch create history + for (const id of historyCreateIds) { + await CreatePosMasterHistoryOfficer(id, null); } for (const act of oldposMasterAct) {