using BMA.EHR.Application.Common.Interfaces; using BMA.EHR.Domain.Models.Placement; using Microsoft.AspNetCore.Http; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using System.Net.Http.Headers; using Newtonsoft.Json; namespace BMA.EHR.Application.Repositories { /// /// Response model จาก Org API (check-isLeave) /// public class OrgProfileResult { public string citizenId { get; set; } = ""; public string? profileId { get; set; } public bool isLeave { get; set; } public bool isActive { get; set; } } public class PlacementRepository : GenericRepository { #region " Fields " private readonly IApplicationDBContext _dbContext; private readonly IHttpContextAccessor _httpContextAccessor; private readonly IConfiguration _configuration; #endregion #region " Constructor and Destructor " public PlacementRepository(IApplicationDBContext dbContext, IHttpContextAccessor httpContextAccessor, IConfiguration configuration) : base(dbContext, httpContextAccessor) { _dbContext = dbContext; _httpContextAccessor = httpContextAccessor; _configuration = configuration; } #endregion #region " Methods " public async Task> GetCompetitivePlacementAsync() { try { var data = await _dbContext.Set() .Include(p => p.PlacementType) .Where(p => p.PlacementType.Name == "สอบแข่งขัน") .ToListAsync(); return data; } catch { throw; } } public async Task> GetAllPlacementAsync() { try { var data = await _dbContext.Set() .Include(p => p.PlacementType) .ToListAsync(); return data; } catch { throw; } } public async Task> GetQualifyingPlacementAsync() { var data = await _dbContext.Set() .Include(p => p.PlacementType) .Where(p => p.PlacementType.Name != "สอบแข่งขัน") .ToListAsync(); return data; } public async Task> FindByNameAsync(string name) { var data = await _dbContext.Set().Where(x => x.Name == name).ToListAsync(); return data; } /// /// Job อัพเดทสถานะผู้สอบผ่านที่ลาออกไปแล้วแต่ยังไม่ส่งไปออกคำสั่ง /// และอัพเดทบุคคลภายนอกที่เข้ามาอยู่ในระบบแล้ว /// ทำงานทุกวันเวลา 05:00 น. /// public async Task UpdateStatusPlacementProfiles() { Console.WriteLine("[Job:UpdateStatusPlacementProfiles] === STARTED ==="); // 1. Query ทั้ง 2 กรณี: ทุกคนที่ยังไม่ DONE var allCitizenIds = await _dbContext.Set() .Where(p => !string.IsNullOrEmpty(p.CitizenId) && p.PlacementStatus != "DONE" // && p.CitizenId == "2536721883131" ) .Select(p => new { p.CitizenId, p.IsOfficer }) .ToListAsync(); if (!allCitizenIds.Any()) { Console.WriteLine("[Job:UpdateStatusPlacementProfiles] No profiles to process"); return; } var officerCount = allCitizenIds.Count(x => x.IsOfficer == true); var notOfficerCount = allCitizenIds.Count(x => x.IsOfficer == false); Console.WriteLine($"[Job:UpdateStatusPlacementProfiles] พบข้าราชการ {officerCount} คน, บุคคลภายนอก {notOfficerCount} คน"); // 2. ส่ง citizenIds ทั้งหมดไป Org API ครั้งเดียว var citizenIds = allCitizenIds.Select(x => x.CitizenId).Distinct().ToList(); var apiUrl = $"{_configuration["API"]}/org/dotnet/check-isLeave"; List orgResults = new(); using (var client = new HttpClient()) { client.DefaultRequestHeaders.Add("api-key", _configuration["API_KEY"]); var payload = new { citizenIds }; var jsonPayload = JsonConvert.SerializeObject(payload); var content = new StringContent(jsonPayload, System.Text.Encoding.UTF8, "application/json"); try { var response = await client.PostAsync(apiUrl, content); var result = await response.Content.ReadAsStringAsync(); var responseObj = JsonConvert.DeserializeAnonymousType(result, new { status = 0, message = "", result = new List() }); orgResults = responseObj?.result ?? new(); Console.WriteLine($"[Job:UpdateStatusPlacementProfiles] Org API ตอบกลับ {orgResults.Count} รายการ"); } catch (Exception ex) { Console.WriteLine($"[Job:UpdateStatusPlacementProfiles] Call API failed: {ex.Message}"); return; } } if (!orgResults.Any()) { Console.WriteLine("[Job:UpdateStatusPlacementProfiles] ไม่มีรายการต้องอัปเดต"); Console.WriteLine("[Job:UpdateStatusPlacementProfiles] === COMPLETED ==="); return; } // 3. แยกข้อมูลตามเงื่อนไข var leaveCitizenIds = orgResults.Where(x => x.isLeave).Select(x => x.citizenId).ToList(); var inSystemProfiles = orgResults.Where(x => x.isActive && !x.isLeave && !string.IsNullOrEmpty(x.profileId)).ToList(); Console.WriteLine($"[Job:UpdateStatusPlacementProfiles] ลาออก {leaveCitizenIds.Count} รายการ, อยู่ที่ทะเบียนประวัติ {inSystemProfiles.Count} รายการ"); // 4. Split Batch Update (500 รายการ/batch) var batchSize = 500; var totalUpdated = 0; // 4.1 Update คนลาออก → IsOfficer = false if (leaveCitizenIds.Any()) { var totalBatches = (int)Math.Ceiling((double)leaveCitizenIds.Count / batchSize); for (int i = 0; i < totalBatches; i++) { var batch = leaveCitizenIds.Skip(i * batchSize).Take(batchSize).ToList(); var profilesToUpdate = await _dbContext.Set() .Where(p => !string.IsNullOrEmpty(p.CitizenId) && batch.Contains(p.CitizenId) && p.IsOfficer == true) .ToListAsync(); foreach (var profile in profilesToUpdate) { profile.IsOfficer = false; } await _dbContext.SaveChangesAsync(); totalUpdated += profilesToUpdate.Count; Console.WriteLine($"[Job:UpdateStatusPlacementProfiles] [ลาออก] Batch {i + 1}/{totalBatches} → อัปเดต {profilesToUpdate.Count} รายการ"); } } // 4.2 Update คนที่อยู่ในทะเบียนประวัติ → profileId + IsOfficer = true if (inSystemProfiles.Any()) { var totalBatches = (int)Math.Ceiling((double)inSystemProfiles.Count / batchSize); for (int i = 0; i < totalBatches; i++) { var batch = inSystemProfiles.Skip(i * batchSize).Take(batchSize).ToList(); var batchCitizenIds = batch.Select(x => x.citizenId).ToList(); var profilesToUpdate = await _dbContext.Set() .Where(p => !string.IsNullOrEmpty(p.CitizenId) && batchCitizenIds.Contains(p.CitizenId) && p.IsOfficer == false) .ToListAsync(); foreach (var profile in profilesToUpdate) { var orgProfile = batch.FirstOrDefault(x => x.citizenId == profile.CitizenId); if (orgProfile != null) { profile.profileId = orgProfile.profileId; profile.IsOfficer = true; } } await _dbContext.SaveChangesAsync(); totalUpdated += profilesToUpdate.Count; Console.WriteLine($"[Job:UpdateStatusPlacementProfiles] [เข้าระบบ] Batch {i + 1}/{totalBatches} → อัปเดต {profilesToUpdate.Count} รายการ"); } } Console.WriteLine($"[Job:UpdateStatusPlacementProfiles] อัปเดตรวมทั้งหมด {totalUpdated} รายการ"); Console.WriteLine("[Job:UpdateStatusPlacementProfiles] === COMPLETED ==="); } #endregion } }