import { AppDataSource } from "../database/data-source"; import { Profile } from "../entities/Profile"; import { PostRetireToExprofile } from "../controllers/ExRetirementController"; import { Between, MoreThanOrEqual } from "typeorm"; const BATCH_SIZE = 100; const CONCURRENT_PER_BATCH = 10; // ส่ง parallel ทีละ 10 คนในแต่ละ batch export class RetirementService { private profileRepository = AppDataSource.getRepository(Profile); /** * Cronjob สำหรับส่งข้อมูลผู้เกษียณไปยังระบบพ้นราชการ (Exprofile) * ทำงานเวลา 04:30:00 ของทุกวันที่ 1 ตุลาคม * * รายละเอียด: * - Query profiles ที่ leaveDate = วันที่ 1 ตุลาคมของปีนั้น และ leaveType = "RETIRE" * - Batch ทีละ 100 records * - Concurrent ทีละ 10 คน (parallel) ในแต่ละ batch * - ถ้า fail ให้ log error แล้วทำคนต่อไป */ async cronjobPostRetireToExprofile(): Promise<{ success: number; failed: number; failedProfiles: Array<{ id: string; name: string; error: string }>; }> { const result = { success: 0, failed: 0, failedProfiles: [] as Array<{ id: string; name: string; error: string }>, }; try { // หาวันที่ 1 ตุลาคมของปีปัจจุบัน const now = new Date(); const currentYear = now.getFullYear(); // สร้างวันที่ 1 ตุลาคมของปีปัจจุบัน (เวลา 00:00:00) const startDate = new Date(currentYear, 9, 1, 0, 0, 0); // Month 9 = October (0-indexed) const endDate = new Date(currentYear, 9, 1, 23, 59, 59); // Query profiles ที่ leaveDate อยู่ในวันที่ 1 ตุลาคม และ leaveType = "RETIRE" const profiles = await this.profileRepository.find({ where: [ { leaveDate: Between(startDate, endDate), leaveType: "RETIRE" as any }, { leaveDate: MoreThanOrEqual(startDate), leaveType: "RETIRE" as any }, ], relations: ["posLevel", "posType"], }); // Filter เอาเฉพาะวันที่ 1 ตุลาคมเท่านั้น const filteredProfiles = profiles.filter(p => { if (!p.leaveDate) return false; const leaveDate = new Date(p.leaveDate); return ( leaveDate.getFullYear() === currentYear && leaveDate.getMonth() === 9 && // October leaveDate.getDate() === 1 ); }); if (filteredProfiles.length === 0) { return result; } // แบ่ง batch ทีละ 100 records for (let i = 0; i < filteredProfiles.length; i += BATCH_SIZE) { const batch = filteredProfiles.slice(i, i + BATCH_SIZE); // แบ่งเป็น chunk เล็กๆ ทีละ CONCURRENT_PER_BATCH เพื่อส่ง parallel for (let j = 0; j < batch.length; j += CONCURRENT_PER_BATCH) { const chunk = batch.slice(j, j + CONCURRENT_PER_BATCH); // ส่ง parallel ในแต่ละ chunk await Promise.all( chunk.map(async (profile) => { try { await this.postSingleProfileToExprofile(profile); result.success++; } catch (error: any) { result.failed++; const errorInfo = { id: profile.id, name: `${profile.prefix}${profile.firstName} ${profile.lastName}`, error: error.message || String(error), }; result.failedProfiles.push(errorInfo); } }) ); } } } catch (error: any) { // Log error but don't throw - allow cronjob to complete with partial results console.error("[cronjobPostRetireToExprofile] Error:", error); // Return current results instead of throwing return result; } return result; } /** * ส่งข้อมูล profile ไปยัง Exprofile */ private async postSingleProfileToExprofile(profile: Profile): Promise { if (!profile.leaveDate) { return; } if (!profile.citizenId) { return; } const retireDate = new Date(profile.leaveDate); const retireYear = retireDate.getFullYear(); // Validate date is valid if (isNaN(retireYear) || retireYear < 2000 || retireYear > 2100) { throw new Error(`Invalid leaveDate for profile ${profile.id}: ${profile.leaveDate}`); } // ส่งไปยัง Exprofile await PostRetireToExprofile( null, profile.citizenId, profile.prefix || "", profile.firstName || "", profile.lastName || "", retireYear.toString(), profile.position || "", profile.posType?.posTypeName || "", profile.posLevel?.posLevelName || "", retireDate, profile.org || "", profile.leaveReason || "เกษียณอายุราชการ" ); } }