From f6bf1ab026bac08a4e1b327650d81eca79965902 Mon Sep 17 00:00:00 2001 From: Suphonchai Phoonsawat Date: Mon, 25 May 2026 14:19:44 +0700 Subject: [PATCH] =?UTF-8?q?=E0=B8=9B=E0=B8=A3=E0=B8=B1=E0=B8=9A=E0=B9=83?= =?UTF-8?q?=E0=B8=AB=E0=B9=89=E0=B9=80=E0=B8=81=E0=B9=87=E0=B8=9A=E0=B8=82?= =?UTF-8?q?=E0=B9=89=E0=B8=AD=E0=B8=A1=E0=B8=B9=E0=B8=A5=E0=B9=80=E0=B8=9E?= =?UTF-8?q?=E0=B8=B4=E0=B9=88=E0=B8=A1=E0=B9=80=E0=B8=95=E0=B8=B4=E0=B8=A1?= =?UTF-8?q?=E0=B9=83=E0=B8=99=20CheckinJobStatus?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CheckInJobStatusRepository.cs | 54 +++++ BMA.EHR.Leave/BMA.EHR.Leave.csproj | 3 + BMA.EHR.Leave/Controllers/LeaveController.cs | 213 ++++++++++++++---- BMA.EHR.Leave/Program.cs | 20 +- BMA.EHR.Leave/wwwroot/blank.jpeg | Bin 0 -> 16428 bytes 5 files changed, 238 insertions(+), 52 deletions(-) create mode 100644 BMA.EHR.Leave/wwwroot/blank.jpeg diff --git a/BMA.EHR.Application/Repositories/Leaves/TimeAttendants/CheckInJobStatusRepository.cs b/BMA.EHR.Application/Repositories/Leaves/TimeAttendants/CheckInJobStatusRepository.cs index 302bdd12..85c575d3 100644 --- a/BMA.EHR.Application/Repositories/Leaves/TimeAttendants/CheckInJobStatusRepository.cs +++ b/BMA.EHR.Application/Repositories/Leaves/TimeAttendants/CheckInJobStatusRepository.cs @@ -114,6 +114,60 @@ namespace BMA.EHR.Application.Repositories.Leaves.TimeAttendants return job!; } + /// + /// ดึงข้อมูลงานที่ค้างอยู่ในสถานะ PENDING หรือ PROCESSING เกินเวลาที่กำหนด (นาที) + /// + public async Task> GetStalePendingOrProcessingJobsAsync(int timeoutMinutes = 30) + { + var cutoffDate = DateTime.Now.AddMinutes(-timeoutMinutes); + var staleJobs = await _dbContext.Set() + .Where(x => (x.Status == "PENDING" || x.Status == "PROCESSING") + && x.CreatedDate < cutoffDate) + .OrderBy(x => x.CreatedDate) + .ToListAsync(); + + return staleJobs; + } + + /// + /// ดึงข้อมูลงานที่ค้างอยู่ในสถานะ PENDING หรือ PROCESSING เกินเวลาที่กำหนด (นาที) ของ user คนใดคนหนึ่ง + /// + public async Task> GetStalePendingOrProcessingJobsByUserAsync(Guid userId, int timeoutMinutes = 30) + { + var cutoffDate = DateTime.Now.AddMinutes(-timeoutMinutes); + var staleJobs = await _dbContext.Set() + .Where(x => x.KeycloakUserId == userId + && (x.Status == "PENDING" || x.Status == "PROCESSING") + && x.CreatedDate < cutoffDate) + .OrderBy(x => x.CreatedDate) + .ToListAsync(); + + return staleJobs; + } + + /// + /// Mark งานที่ค้างเกินเวลาที่กำหนดเป็น FAILED + /// + public async Task MarkStaleJobsAsFailedAsync(int timeoutMinutes = 30) + { + var staleJobs = await GetStalePendingOrProcessingJobsAsync(timeoutMinutes); + + foreach (var job in staleJobs) + { + job.Status = "FAILED"; + job.CompletedDate = DateTime.Now; + job.ErrorMessage = $"งานค้างในสถานะ {job.Status} เกิน {timeoutMinutes} นาที ระบบทำเครื่องหมายเป็น FAILED อัตโนมัติ"; + } + + if (staleJobs.Any()) + { + _dbContext.Set().UpdateRange(staleJobs); + await _dbContext.SaveChangesAsync(); + } + + return staleJobs.Count; + } + /// /// ล้างข้อมูล Job Status ที่เก่าเกิน X วัน /// diff --git a/BMA.EHR.Leave/BMA.EHR.Leave.csproj b/BMA.EHR.Leave/BMA.EHR.Leave.csproj index 28f42590..e7c58efa 100644 --- a/BMA.EHR.Leave/BMA.EHR.Leave.csproj +++ b/BMA.EHR.Leave/BMA.EHR.Leave.csproj @@ -74,6 +74,9 @@ PreserveNewest + + PreserveNewest + diff --git a/BMA.EHR.Leave/Controllers/LeaveController.cs b/BMA.EHR.Leave/Controllers/LeaveController.cs index 3b1ec5b7..78594aef 100644 --- a/BMA.EHR.Leave/Controllers/LeaveController.cs +++ b/BMA.EHR.Leave/Controllers/LeaveController.cs @@ -536,7 +536,18 @@ namespace BMA.EHR.Leave.Service.Controllers // prepare data and convert request body and send to queue var userId = UserId == null ? Guid.Empty : Guid.Parse(UserId); var currentDate = DateTime.Now; - + + // ตรวจสอบและ mark งานเก่าที่ค้างเกิน 30 นาทีเป็น FAILED อัตโนมัติ + var staleJobs = await _checkInJobStatusRepository.GetStalePendingOrProcessingJobsByUserAsync(userId, 30); + if (staleJobs != null && staleJobs.Count > 0) + { + foreach (var staleJob in staleJobs) + { + await _checkInJobStatusRepository.UpdateToFailedAsync(staleJob.TaskId, + $"งานค้างในสถานะ {staleJob.Status} เกิน 30 นาที ระบบทำเครื่องหมายเป็น FAILED อัตโนมัติ"); + } + } + // ตรวจสอบว่ามีงานที่กำลัง pending หรือ processing อยู่หรือไม่ var existingJobs = await _checkInJobStatusRepository.GetPendingOrProcessingJobsAsync(userId); if (existingJobs != null && existingJobs.Count > 0) @@ -544,10 +555,10 @@ namespace BMA.EHR.Leave.Service.Controllers // กรองเฉพาะงานที่เป็นประเภทเดียวกัน (CHECK_IN หรือ CHECK_OUT) var checkType = data.CheckInId == null ? "CHECK_IN" : "CHECK_OUT"; var sameTypeJob = existingJobs.FirstOrDefault(j => j.CheckType == checkType); - + if (sameTypeJob != null) { - + return Error($"มีงาน {checkType} กำลังดำเนินการอยู่", StatusCodes.Status500InternalServerError); // var timeDiff = (currentDate - sameTypeJob.CreatedDate).TotalMinutes; // if (timeDiff < 2) @@ -586,7 +597,7 @@ namespace BMA.EHR.Leave.Service.Controllers LocationName = data.LocationName, Remark = data.Remark, CheckInFileName = data.Img == null ? "no-file" : data.Img.FileName, - CheckInFileBytes = checkFileBytes, + //CheckInFileBytes = checkFileBytes, Token = AccessToken ?? "" }; @@ -613,14 +624,7 @@ namespace BMA.EHR.Leave.Service.Controllers Status = "PENDING", CheckType = data.CheckInId == null ? "CHECK_IN" : "CHECK_OUT", CheckInId = data.CheckInId, - AdditionalData = JsonConvert.SerializeObject(new - { - IsLocation = data.IsLocation, - LocationName = data.LocationName, - POI = data.POI, - KeycloakId = userId, - Token = AccessToken, - }) + AdditionalData = JsonConvert.SerializeObject(checkData) }; await _checkInJobStatusRepository.AddAsync(jobStatus); @@ -727,6 +731,117 @@ namespace BMA.EHR.Leave.Service.Controllers return Success(new { count = result.Count, jobs = result }); } + /// + /// ประมวลผลงาน CheckIn ที่ค้างอยู่ในสถานะ PENDING/PROCESSING เกินเวลาที่กำหนดใหม่อีกครั้ง + /// + /// + /// เมื่อทำรายการสำเร็จ + /// ไม่ได้ Login เข้าระบบ + /// เมื่อเกิดข้อผิดพลาดในการทำงาน + [HttpPost("reprocess-stale-checkin-jobs")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status401Unauthorized)] + [ProducesResponseType(StatusCodes.Status500InternalServerError)] + public async Task> ReprocessStaleCheckInJobsAsync([FromQuery] int timeoutMinutes = 30) + { + try + { + var staleJobs = await _checkInJobStatusRepository.GetStalePendingOrProcessingJobsAsync(timeoutMinutes); + + if (staleJobs == null || staleJobs.Count == 0) + { + return Success(new { message = "ไม่พบงานที่ค้างอยู่", count = 0 }); + } + + var results = new List(); + foreach (var job in staleJobs) + { + try + { + // อ่านข้อมูลเดิมจาก AdditionalData + if (string.IsNullOrEmpty(job.AdditionalData)) + { + await _checkInJobStatusRepository.UpdateToFailedAsync(job.TaskId, + "ไม่พบข้อมูลสำหรับประมวลผลซ้ำ (AdditionalData is null)"); + results.Add(new + { + taskId = job.TaskId, + keycloakUserId = job.KeycloakUserId, + checkType = job.CheckType, + createdDate = job.CreatedDate, + previousStatus = job.Status, + newStatus = "FAILED", + errorMessage = "ไม่พบข้อมูลสำหรับประมวลผลซ้ำ" + }); + continue; + } + + var checkData = JsonConvert.DeserializeObject(job.AdditionalData); + checkData.UserId = job.KeycloakUserId; + checkData.CurrentDate = job.CreatedDate; + if (checkData == null) + { + await _checkInJobStatusRepository.UpdateToFailedAsync(job.TaskId, + "ไม่สามารถอ่านข้อมูลสำหรับประมวลผลซ้ำได้"); + results.Add(new + { + taskId = job.TaskId, + keycloakUserId = job.KeycloakUserId, + checkType = job.CheckType, + createdDate = job.CreatedDate, + previousStatus = job.Status, + newStatus = "FAILED", + errorMessage = "ไม่สามารถอ่านข้อมูลสำหรับประมวลผลซ้ำได้" + }); + continue; + } + + // ตั้ง TaskId ให้ตรงกับ job เดิม + checkData.TaskId = job.TaskId; + + // เรียก ProcessCheckInAsync ด้วยข้อมูลเดิม + var processResult = await ProcessCheckInAsync(checkData); + + results.Add(new + { + taskId = job.TaskId, + keycloakUserId = job.KeycloakUserId, + checkType = job.CheckType, + createdDate = job.CreatedDate, + previousStatus = job.Status, + result = processResult + }); + } + catch (Exception ex) + { + await _checkInJobStatusRepository.UpdateToFailedAsync(job.TaskId, + $"เกิดข้อผิดพลาดในการประมวลผลซ้ำ: {ex.Message}"); + results.Add(new + { + taskId = job.TaskId, + keycloakUserId = job.KeycloakUserId, + checkType = job.CheckType, + createdDate = job.CreatedDate, + previousStatus = job.Status, + newStatus = "FAILED", + errorMessage = ex.Message + }); + } + } + + return Success(new + { + message = $"ประมวลผลซ้ำงาน {staleJobs.Count} รายการเสร็จสิ้น", + count = staleJobs.Count, + jobs = results + }); + } + catch (Exception ex) + { + return Error(ex); + } + } + [HttpGet("check-status")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status401Unauthorized)] @@ -958,16 +1073,18 @@ namespace BMA.EHR.Leave.Service.Controllers var currentDate = data.CurrentDate ?? DateTime.Now; - if (data.CheckInFileName == "no-file") + if (data.CheckInFileName == "no-file") { //throw new Exception(GlobalMessages.NoFileToUpload); await _checkInJobStatusRepository.UpdateToFailedAsync(taskId, GlobalMessages.NoFileToUpload); - await _notificationService.SendNotificationAsync(data.Token, true, $"ลงเวลาไม่สำเร็จ \r\nเนื่องจาก {GlobalMessages.NoFileToUpload}\r\nกรุณาลองใหม่อีกครั้ง"); + await _notificationService.SendNotificationAsync(data.Token, true, + $"ลงเวลาไม่สำเร็จ \r\nเนื่องจาก {GlobalMessages.NoFileToUpload}\r\nกรุณาลองใหม่อีกครั้ง"); // send notification to user var noti1 = new Notification { - Body = $"ประมวลผลการลงเวลาวันที่ {currentDate.ToString("dd-MM-yyyy")} ไม่สำเร็จ \r\nเนื่องจาก {GlobalMessages.NoFileToUpload}", + Body = + $"ประมวลผลการลงเวลาวันที่ {currentDate.ToString("dd-MM-yyyy")} ไม่สำเร็จ \r\nเนื่องจาก {GlobalMessages.NoFileToUpload}", ReceiverUserId = profile.Id, Type = "", Payload = "", @@ -977,46 +1094,54 @@ namespace BMA.EHR.Leave.Service.Controllers return Error(GlobalMessages.NoFileToUpload, StatusCodes.Status400BadRequest); } - + // last check-in record var lastCheckIn = await _userTimeStampRepository.GetLastRecord(userId); var check_status = data.CheckInId == null ? "check-in-picture" : "check-out-picture"; var check_out_status = "check-out-picture"; - var fileName = $"{_bucketName}/{userId}/{currentDate.ToString("dd-MM-yyyy")}/{check_status}/{data.CheckInFileName}"; - var fileNameCheckOut = $"{_bucketName}/{userId}/{currentDate.ToString("dd-MM-yyyy")}/{check_out_status}/{data.CheckInFileName}"; - using (var ms = new MemoryStream(data.CheckInFileBytes ?? new byte[0])) + // ถ้าไม่มี CheckInFileBytes ให้ใช้ภาพ blank.jpeg แทน + var fileBytes = data.CheckInFileBytes; + if (fileBytes == null || fileBytes.Length == 0) + { + var blankPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "wwwroot", "blank.jpeg"); + fileBytes = await System.IO.File.ReadAllBytesAsync(blankPath); + data.CheckInFileName = "blank.jpeg"; + } + + var fileName = + $"{_bucketName}/{userId}/{currentDate.ToString("dd-MM-yyyy")}/{check_status}/{data.CheckInFileName}"; + var fileNameCheckOut = + $"{_bucketName}/{userId}/{currentDate.ToString("dd-MM-yyyy")}/{check_out_status}/{data.CheckInFileName}"; + using (var ms = new MemoryStream(fileBytes)) { try { await _minIOService.UploadFileAsync(fileName, ms); - // if (lastCheckIn != null && lastCheckIn.CheckOut == null) - // { - // // ยังไม่เคย check-out มาก่อน หรือ check-out เป็น null ให้ใช้ชื่อไฟล์แบบ check-out - // await _minIOService.UploadFileAsync(fileNameCheckOut, ms); - // } } catch (Exception ex) { - await _checkInJobStatusRepository.UpdateToFailedAsync(taskId, $"ไม่สามารถอัปโหลดรูปภาพได้: {ex.Message}"); - await _notificationService.SendNotificationAsync(data.Token, true, $"ลงเวลาไม่สำเร็จ \r\nเนื่องจากไม่สามารถอัปโหลดรูปภาพได้ {ex.Message}\r\nกรุณาลองใหม่อีกครั้ง"); + await _checkInJobStatusRepository.UpdateToFailedAsync(taskId, + $"ไม่สามารถอัปโหลดรูปภาพได้: {ex.Message}"); + await _notificationService.SendNotificationAsync(data.Token, true, + $"ลงเวลาไม่สำเร็จ \r\nเนื่องจากไม่สามารถอัปโหลดรูปภาพได้ {ex.Message}\r\nกรุณาลองใหม่อีกครั้ง"); // send notification to user - var noti1 = new Notification + var noti2 = new Notification { - Body = $"ประมวลผลการลงเวลาวันที่ {currentDate.ToString("dd-MM-yyyy")} ไม่สำเร็จ \r\nเนื่องจากไม่สามารถอัปโหลดรูปภาพได้ {ex.Message}", + Body = + $"ประมวลผลการลงเวลาวันที่ {currentDate.ToString("dd-MM-yyyy")} ไม่สำเร็จ \r\nเนื่องจากไม่สามารถอัปโหลดรูปภาพได้ {ex.Message}", ReceiverUserId = profile.Id, Type = "", Payload = "", }; - _appDbContext.Set().Add(noti1); + _appDbContext.Set().Add(noti2); await _appDbContext.SaveChangesAsync(); - - return Error($"ไม่สามารถอัปโหลดรูปภาพได้: {ex.Message}", StatusCodes.Status500InternalServerError); + return Error($"ไม่สามารถอัปโหลดรูปภาพได้: {ex.Message}", + StatusCodes.Status500InternalServerError); } - } if (lastCheckIn != null && lastCheckIn.CheckOut == null) @@ -1026,36 +1151,32 @@ namespace BMA.EHR.Leave.Service.Controllers try { await _minIOService.UploadFileAsync(fileNameCheckOut, ms2); - // if (lastCheckIn != null && lastCheckIn.CheckOut == null) - // { - // // ยังไม่เคย check-out มาก่อน หรือ check-out เป็น null ให้ใช้ชื่อไฟล์แบบ check-out - // await _minIOService.UploadFileAsync(fileNameCheckOut, ms); - // } } catch (Exception ex) { - await _checkInJobStatusRepository.UpdateToFailedAsync(taskId, $"ไม่สามารถอัปโหลดรูปภาพได้: {ex.Message}"); - await _notificationService.SendNotificationAsync(data.Token, true, $"ลงเวลาไม่สำเร็จ \r\nเนื่องจากไม่สามารถอัปโหลดรูปภาพได้ {ex.Message}\r\nกรุณาลองใหม่อีกครั้ง"); + await _checkInJobStatusRepository.UpdateToFailedAsync(taskId, + $"ไม่สามารถอัปโหลดรูปภาพได้: {ex.Message}"); + await _notificationService.SendNotificationAsync(data.Token, true, + $"ลงเวลาไม่สำเร็จ \r\nเนื่องจากไม่สามารถอัปโหลดรูปภาพได้ {ex.Message}\r\nกรุณาลองใหม่อีกครั้ง"); // send notification to user - var noti1 = new Notification + var noti3 = new Notification { - Body = $"ประมวลผลการลงเวลาวันที่ {currentDate.ToString("dd-MM-yyyy")} ไม่สำเร็จ \r\nเนื่องจากไม่สามารถอัปโหลดรูปภาพได้ {ex.Message}", + Body = + $"ประมวลผลการลงเวลาวันที่ {currentDate.ToString("dd-MM-yyyy")} ไม่สำเร็จ \r\nเนื่องจากไม่สามารถอัปโหลดรูปภาพได้ {ex.Message}", ReceiverUserId = profile.Id, Type = "", Payload = "", }; - _appDbContext.Set().Add(noti1); + _appDbContext.Set().Add(noti3); await _appDbContext.SaveChangesAsync(); - - return Error($"ไม่สามารถอัปโหลดรูปภาพได้: {ex.Message}", StatusCodes.Status500InternalServerError); + return Error($"ไม่สามารถอัปโหลดรูปภาพได้: {ex.Message}", + StatusCodes.Status500InternalServerError); } - } } - var defaultRound = await _dutyTimeRepository.GetDefaultAsync(); if (defaultRound == null) { diff --git a/BMA.EHR.Leave/Program.cs b/BMA.EHR.Leave/Program.cs index 9d42133e..3dd83203 100644 --- a/BMA.EHR.Leave/Program.cs +++ b/BMA.EHR.Leave/Program.cs @@ -198,12 +198,20 @@ if (manager != null) // ทำความสะอาดข้อมูล CheckIn Job Status ที่เก่ากว่า 30 วัน - รันทุกวันเวลา 02:00 น. manager.AddOrUpdate("ทำความสะอาดข้อมูล CheckIn Job Status", Job.FromExpression(x => x.CleanupOldJobsAsync(30)), "0 2 * * *", bangkokTimeZone); - manager.AddOrUpdate("ประมวลผลงานที่ค้างอยู่ในสถานะ Pending หรือ Processing", Job.FromExpression(x => x.ProcessPendingJobsAsync()), "0 3 * * *", - new RecurringJobOptions - { - TimeZone = bangkokTimeZone, - QueueName = "leave" // ← กำหนด queue - }); + // ตรวจสอบและ mark งาน CheckIn ที่ค้างเกิน 30 นาทีเป็น FAILED - รันทุก 15 นาที + // manager.AddOrUpdate("ตรวจสอบงาน CheckIn ที่ค้างเกินเวลา", Job.FromExpression(x => x.MarkStaleJobsAsFailedAsync(30)), "*/15 * * * *", + // new RecurringJobOptions + // { + // TimeZone = bangkokTimeZone, + // QueueName = "leave" + // }); + // + // manager.AddOrUpdate("ประมวลผลงานที่ค้างอยู่ในสถานะ Pending หรือ Processing", Job.FromExpression(x => x.ProcessPendingJobsAsync()), "0 3 * * *", + // new RecurringJobOptions + // { + // TimeZone = bangkokTimeZone, + // QueueName = "leave" // ← กำหนด queue + // }); } // apply migrations diff --git a/BMA.EHR.Leave/wwwroot/blank.jpeg b/BMA.EHR.Leave/wwwroot/blank.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..2b1534867e1760d14dc68afb6a70ab3939470ad3 GIT binary patch literal 16428 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 ZG5^idf=6U0Nn