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