diff --git a/BMA.EHR.Application/Repositories/UserProfileRepository.cs b/BMA.EHR.Application/Repositories/UserProfileRepository.cs index 01ad610e..0ca46423 100644 --- a/BMA.EHR.Application/Repositories/UserProfileRepository.cs +++ b/BMA.EHR.Application/Repositories/UserProfileRepository.cs @@ -720,7 +720,7 @@ namespace BMA.EHR.Application.Repositories } } - public async Task SearchProfile(string? citizenId, string? firstName, string? lastName, string accessToken, int page, int pageSize, string? role, string? nodeId, int? node) + public async Task SearchProfile(string? citizenId, string? firstName, string? lastName, string accessToken, int page, int pageSize, string? role, string? nodeId, int? node) { try { @@ -738,21 +738,21 @@ namespace BMA.EHR.Application.Repositories pageSize = pageSize, }; - var profiles = new List(); + var profiles = new List(); var total = 0; var apiResult = await PostExternalAPIAsync(apiPath, accessToken, body, apiKey); if (apiResult != null) { - var raw = JsonConvert.DeserializeObject(apiResult); + var raw = JsonConvert.DeserializeObject(apiResult); if (raw != null && raw.Result != null) { - profiles.AddRange(raw.Result.data); - total = raw.Result.total; + profiles.AddRange(raw.Result.Data); + total = raw.Result.Total; } } - return new { data = profiles, total = total }; + return new GetProfileByKeycloakIdRootAddTotalDto { Data = profiles, Total = total }; } catch { @@ -760,7 +760,7 @@ namespace BMA.EHR.Application.Repositories } } - public async Task SearchProfileEmployee(string? citizenId, string? firstName, string? lastName, string accessToken, int page, int pageSize, string? role, string? nodeId, int? node) + public async Task SearchProfileEmployee(string? citizenId, string? firstName, string? lastName, string accessToken, int page, int pageSize, string? role, string? nodeId, int? node) { try { @@ -778,21 +778,21 @@ namespace BMA.EHR.Application.Repositories pageSize = pageSize, }; - var profiles = new List(); + var profiles = new List(); var total = 0; var apiResult = await PostExternalAPIAsync(apiPath, accessToken, body, apiKey); if (apiResult != null) { - var raw = JsonConvert.DeserializeObject(apiResult); + var raw = JsonConvert.DeserializeObject(apiResult); if (raw != null && raw.Result != null) { - profiles.AddRange(raw.Result.data); - total = raw.Result.total; + profiles.AddRange(raw.Result.Data); + total = raw.Result.Total; } } - return new { data = profiles, total = total }; + return new GetProfileByKeycloakIdRootAddTotalDto { Data = profiles, Total = total }; } catch { diff --git a/BMA.EHR.Application/Responses/Profiles/GetListProfileByKeycloakIdRootResultDto.cs b/BMA.EHR.Application/Responses/Profiles/GetListProfileByKeycloakIdRootResultDto.cs index 4dd09565..ce2781cb 100644 --- a/BMA.EHR.Application/Responses/Profiles/GetListProfileByKeycloakIdRootResultDto.cs +++ b/BMA.EHR.Application/Responses/Profiles/GetListProfileByKeycloakIdRootResultDto.cs @@ -8,4 +8,12 @@ public List Result { get; set; } = new(); } + public class GetListProfileByKeycloakIdRootResultAddTotalDto + { + public string Message { get; set; } = string.Empty; + + public int Status { get; set; } = -1; + + public GetProfileByKeycloakIdRootAddTotalDto Result { get; set; } = new(); + } } diff --git a/BMA.EHR.Application/Responses/Profiles/GetProfileByKeycloakIdRootDto.cs b/BMA.EHR.Application/Responses/Profiles/GetProfileByKeycloakIdRootDto.cs index c6d92ec5..1110ca9e 100644 --- a/BMA.EHR.Application/Responses/Profiles/GetProfileByKeycloakIdRootDto.cs +++ b/BMA.EHR.Application/Responses/Profiles/GetProfileByKeycloakIdRootDto.cs @@ -25,6 +25,12 @@ namespace BMA.EHR.Application.Responses.Profiles public DateTime? DateStart { get; set; } public DateTime? DateAppoint { get; set; } + } + public class GetProfileByKeycloakIdRootAddTotalDto + { + public List Data { get; set; } = new(); + + public int Total { get; set; } } } diff --git a/BMA.EHR.Leave/Controllers/LeaveController.cs b/BMA.EHR.Leave/Controllers/LeaveController.cs index e7d99dac..4634f2a5 100644 --- a/BMA.EHR.Leave/Controllers/LeaveController.cs +++ b/BMA.EHR.Leave/Controllers/LeaveController.cs @@ -1839,18 +1839,41 @@ namespace BMA.EHR.Leave.Service.Controllers } var profile = await _userProfileRepository.SearchProfile(req.CitizenId, req.FirstName, req.LastName, AccessToken ?? "", req.Page, req.PageSize, role, nodeId, profileAdmin?.Node); + // Get default round once var getDefaultRound = await _dutyTimeRepository.GetDefaultAsync(); var resultSet = new List(); - foreach (var p in profile.data) + // Create dictionaries to cache results and avoid duplicate queries + var effectiveDateCache = new Dictionary(); + var dutyTimeCache = new Dictionary(); + + foreach (var p in profile.Data) { - var effectiveDate = await _userDutyTimeRepository.GetLastEffectRound(p.Id); + // Use cache for effective date + if (!effectiveDateCache.ContainsKey(p.Id)) + { + effectiveDateCache[p.Id] = await _userDutyTimeRepository.GetLastEffectRound(p.Id); + } + var effectiveDate = effectiveDateCache[p.Id]; + var roundId = effectiveDate != null ? effectiveDate.DutyTimeId : Guid.Empty; - var userRound = await _dutyTimeRepository.GetByIdAsync(roundId); + + // Use cache for duty time + DutyTime? userRound = null; + if (roundId != Guid.Empty) + { + if (!dutyTimeCache.ContainsKey(roundId)) + { + dutyTimeCache[roundId] = await _dutyTimeRepository.GetByIdAsync(roundId); + } + userRound = dutyTimeCache[roundId]; + } var duty = userRound ?? getDefaultRound; + if (duty == null) continue; // Skip if no duty time found + var res = new SearchProfileResultDto { ProfileId = p.Id, @@ -1858,44 +1881,12 @@ namespace BMA.EHR.Leave.Service.Controllers FullName = $"{p.Prefix ?? ""}{p.FirstName ?? ""} {p.LastName ?? ""}", StartTimeMorning = duty.StartTimeMorning, LeaveTimeAfterNoon = duty.EndTimeAfternoon, - EffectiveDate = effectiveDate == null ? null : effectiveDate.EffectiveDate.Value.Date + EffectiveDate = effectiveDate?.EffectiveDate?.Date }; resultSet.Add(res); } - if (!string.IsNullOrWhiteSpace(req.sortBy)) - { - switch (req.sortBy.ToUpper()) - { - case "CITIZENID": - if (req.descending == true) - resultSet = resultSet.OrderByDescending(x => x.CitizenId).ToList(); - else - resultSet = resultSet.OrderBy(x => x.CitizenId).ToList(); - break; - case "FULLNAME": - if (req.descending == true) - resultSet = resultSet.OrderByDescending(x => x.FullName).ToList(); - else - resultSet = resultSet.OrderBy(x => x.FullName).ToList(); - break; - case "STARTTIMEMORNING": - if (req.descending == true) - resultSet = resultSet.OrderByDescending(x => x.StartTimeMorning).ToList(); - else - resultSet = resultSet.OrderBy(x => x.StartTimeMorning).ToList(); - break; - case "EFFECTIVEDATE": - if (req.descending == true) - resultSet = resultSet.OrderByDescending(x => x.EffectiveDate).ToList(); - else - resultSet = resultSet.OrderBy(x => x.EffectiveDate).ToList(); - break; - default: break; - } - } - var pageResult = resultSet.Skip((req.Page - 1) * req.PageSize).Take(req.PageSize).ToList(); - return Success(new { data = resultSet, total = profile.total }); + return Success(new { data = resultSet, total = profile.Total }); } /// @@ -2091,18 +2082,41 @@ namespace BMA.EHR.Leave.Service.Controllers } var profile = await _userProfileRepository.SearchProfileEmployee(req.CitizenId, req.FirstName, req.LastName, AccessToken ?? "", req.Page, req.PageSize, role, nodeId, profileAdmin?.Node); + // Get default round once var getDefaultRound = await _dutyTimeRepository.GetDefaultAsync(); var resultSet = new List(); - foreach (var p in profile.data) + // Create dictionaries to cache results and avoid duplicate queries + var effectiveDateCache = new Dictionary(); + var dutyTimeCache = new Dictionary(); + + foreach (var p in profile.Data) { - var effectiveDate = await _userDutyTimeRepository.GetLastEffectRound(p.Id); + // Use cache for effective date + if (!effectiveDateCache.ContainsKey(p.Id)) + { + effectiveDateCache[p.Id] = await _userDutyTimeRepository.GetLastEffectRound(p.Id); + } + var effectiveDate = effectiveDateCache[p.Id]; + var roundId = effectiveDate != null ? effectiveDate.DutyTimeId : Guid.Empty; - var userRound = await _dutyTimeRepository.GetByIdAsync(roundId); + + // Use cache for duty time + DutyTime? userRound = null; + if (roundId != Guid.Empty) + { + if (!dutyTimeCache.ContainsKey(roundId)) + { + dutyTimeCache[roundId] = await _dutyTimeRepository.GetByIdAsync(roundId); + } + userRound = dutyTimeCache[roundId]; + } var duty = userRound ?? getDefaultRound; + if (duty == null) continue; // Skip if no duty time found + var res = new SearchProfileResultDto { ProfileId = p.Id, @@ -2110,12 +2124,12 @@ namespace BMA.EHR.Leave.Service.Controllers FullName = $"{p.Prefix ?? ""}{p.FirstName ?? ""} {p.LastName ?? ""}", StartTimeMorning = duty.StartTimeMorning, LeaveTimeAfterNoon = duty.EndTimeAfternoon, - EffectiveDate = effectiveDate == null ? null : effectiveDate.EffectiveDate.Value.Date + EffectiveDate = effectiveDate?.EffectiveDate?.Date }; resultSet.Add(res); } - return Success(new { data = resultSet, total = profile.total }); + return Success(new { data = resultSet, total = profile.Total }); } /// diff --git a/BMA.EHR.Leave/Controllers/LeaveRequestController.cs b/BMA.EHR.Leave/Controllers/LeaveRequestController.cs index 5c98d26c..ddd6853a 100644 --- a/BMA.EHR.Leave/Controllers/LeaveRequestController.cs +++ b/BMA.EHR.Leave/Controllers/LeaveRequestController.cs @@ -2481,44 +2481,58 @@ namespace BMA.EHR.Leave.Service.Controllers public async Task> GetUserLeaveSummaryAsync() { var userId = UserId == null ? Guid.Empty : Guid.Parse(UserId); - var leaveTypes = await _leaveTypeRepository.GetAllAsync(); var thisYear = DateTime.Now.Year; var toDay = DateTime.Now.Date; if (toDay >= new DateTime(toDay.Year, 10, 1) && toDay <= new DateTime(toDay.Year, 12, 31)) thisYear = thisYear + 1; + // Execute repository calls sequentially to avoid DbContext threading issues + var leaveTypes = await _leaveTypeRepository.GetAllAsync(); var sendList = await _leaveRequestRepository.GetSumSendLeaveAsync(thisYear); var rejectList = await _leaveRequestRepository.GetSumRejectLeaveAsync(thisYear); var deleteList = await _leaveRequestRepository.GetSumDeleteLeaveAsync(thisYear); - - var result = new List(); var pf = await _userProfileRepository.GetProfileByKeycloakIdAsync(userId, AccessToken); + if (pf == null) { throw new Exception(GlobalMessages.DataNotFound); } + + // Create dictionaries for fast lookup instead of FirstOrDefault searches + var sendDict = sendList.Where(x => x.KeycloakUserId == userId) + .ToDictionary(x => x.LeaveTypeId, x => x.SumLeaveDay); + var rejectDict = rejectList.Where(x => x.KeycloakUserId == userId) + .ToDictionary(x => x.LeaveTypeId, x => x.SumLeaveDay); + var deleteDict = deleteList.Where(x => x.KeycloakUserId == userId) + .ToDictionary(x => x.LeaveTypeId, x => x.SumLeaveDay); + + // Pre-load all leave beginning data sequentially to avoid DbContext threading issues + var leaveBeginningDict = new Dictionary(); foreach (var leaveType in leaveTypes) { - var sendData = sendList.FirstOrDefault(x => x.KeycloakUserId == userId && x.LeaveTypeId == leaveType.Id); - var send = sendData == null ? 0 : sendData.SumLeaveDay; - var leaveData = await _leaveBeginningRepository.GetByYearAndTypeIdForUser(thisYear, leaveType.Id, pf); - var approve = leaveData == null ? 0 : leaveData.LeaveDaysUsed; + leaveBeginningDict[leaveType.Id] = leaveData; + } - var rejectData = rejectList.FirstOrDefault(x => x.KeycloakUserId == userId && x.LeaveTypeId == leaveType.Id); - var reject = rejectData == null ? 0 : rejectData.SumLeaveDay; + var result = new List(); - var deleteData = deleteList.FirstOrDefault(x => x.KeycloakUserId == userId && x.LeaveTypeId == leaveType.Id); - var delete = deleteData == null ? 0 : deleteData.SumLeaveDay; + foreach (var leaveType in leaveTypes) + { + // Use dictionary lookups for better performance + var send = sendDict.GetValueOrDefault(leaveType.Id, 0); + var reject = rejectDict.GetValueOrDefault(leaveType.Id, 0); + var delete = deleteDict.GetValueOrDefault(leaveType.Id, 0); + + leaveBeginningDict.TryGetValue(leaveType.Id, out var leaveData); + var approve = leaveData?.LeaveDaysUsed ?? 0; // fix issue : SIT ระบบบันทึกการลา>> สิทธิ์การลา(โอนสิทธิ์การลา) #974 - var extendLeave = 0.0; var leaveLimit = (double)leaveType.Limit; if (leaveType.Code == "LV-005") { - leaveLimit = leaveData == null ? 0.0 : leaveData.LeaveDays; + leaveLimit = leaveData?.LeaveDays ?? 0.0; extendLeave = leaveLimit - 10; }