2026-05-05 16:43:47 +07:00
|
|
|
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 ตุลาคมเท่านั้น
|
2026-05-08 14:47:58 +07:00
|
|
|
const filteredProfiles = profiles.filter((p) => {
|
2026-05-05 16:43:47 +07:00
|
|
|
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);
|
|
|
|
|
}
|
2026-05-08 14:47:58 +07:00
|
|
|
}),
|
2026-05-05 16:43:47 +07:00
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} catch (error: any) {
|
2026-05-08 14:45:17 +07:00
|
|
|
// 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;
|
2026-05-05 16:43:47 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* ส่งข้อมูล profile ไปยัง Exprofile
|
|
|
|
|
*/
|
|
|
|
|
private async postSingleProfileToExprofile(profile: Profile): Promise<void> {
|
|
|
|
|
if (!profile.leaveDate) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!profile.citizenId) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const retireDate = new Date(profile.leaveDate);
|
2026-05-08 14:45:17 +07:00
|
|
|
const retireYear = retireDate.getFullYear();
|
|
|
|
|
|
|
|
|
|
// Validate date is valid
|
2026-05-08 14:47:58 +07:00
|
|
|
if (isNaN(retireYear) || retireYear < 2000) {
|
2026-05-08 14:45:17 +07:00
|
|
|
throw new Error(`Invalid leaveDate for profile ${profile.id}: ${profile.leaveDate}`);
|
|
|
|
|
}
|
2026-05-05 16:43:47 +07:00
|
|
|
|
|
|
|
|
// ส่งไปยัง Exprofile
|
2026-05-08 14:37:40 +07:00
|
|
|
await PostRetireToExprofile(
|
2026-05-05 16:43:47 +07:00
|
|
|
null,
|
|
|
|
|
profile.citizenId,
|
|
|
|
|
profile.prefix || "",
|
|
|
|
|
profile.firstName || "",
|
|
|
|
|
profile.lastName || "",
|
|
|
|
|
retireYear.toString(),
|
|
|
|
|
profile.position || "",
|
|
|
|
|
profile.posType?.posTypeName || "",
|
|
|
|
|
profile.posLevel?.posLevelName || "",
|
|
|
|
|
retireDate,
|
|
|
|
|
profile.org || "",
|
2026-05-08 14:47:58 +07:00
|
|
|
profile.leaveReason || "เกษียณอายุราชการ",
|
2026-05-05 16:43:47 +07:00
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|