From e028dc5cf5b06ae4b06dc29f4b7fb9d8f8cb2202 Mon Sep 17 00:00:00 2001 From: kittapath Date: Fri, 7 Mar 2025 14:49:27 +0700 Subject: [PATCH 01/10] =?UTF-8?q?report=20=E0=B8=A5=E0=B8=87=E0=B9=80?= =?UTF-8?q?=E0=B8=A7=E0=B8=A5=E0=B8=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Controllers/LeaveReportController.cs | 48 +++++++++---------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/BMA.EHR.Leave/Controllers/LeaveReportController.cs b/BMA.EHR.Leave/Controllers/LeaveReportController.cs index 16e0c2e8..0fa71312 100644 --- a/BMA.EHR.Leave/Controllers/LeaveReportController.cs +++ b/BMA.EHR.Leave/Controllers/LeaveReportController.cs @@ -808,7 +808,7 @@ namespace BMA.EHR.Leave.Service.Controllers var therapyDay = leaveDays.FirstOrDefault(x => x.KeycloakUserId == keycloakUserId && x.LeaveTypeCode == "LV-011"); var therapyDayCount = therapyDay != null ? therapyDay.SumLeaveDay : 0; - var timeStamps = await _processUserTimeStampRepository.GetTimeStampHistoryByRangeForUserAsync(p.Keycloak ?? Guid.Empty,req.StartDate,req.EndDate); + var timeStamps = await _processUserTimeStampRepository.GetTimeStampHistoryByRangeForUserAsync(p.Keycloak ?? Guid.Empty, req.StartDate, req.EndDate); var defaultRound = await _dutyTimeRepository.GetDefaultAsync(); if (defaultRound == null) @@ -822,27 +822,27 @@ namespace BMA.EHR.Leave.Service.Controllers var duty = userRound ?? defaultRound; - /* var processTimeStamps = timeStamps - .Select(d => new - { - d.Id, - CheckInStatus = DateTime.Parse(d.CheckIn.ToString("yyyy-MM-dd HH:mm")) > - DateTime.Parse($"{d.CheckIn.Date.ToString("yyyy-MM-dd")} {duty.StartTimeMorning}") ? - "LATE" : - "NORMAL", - CheckOutStatus = d.CheckOut == null ? "" : - DateTime.Parse(d.CheckOut.Value.ToString("yyyy-MM-dd HH:mm")) < - DateTime.Parse($"{d.CheckIn.Date.ToString("yyyy-MM-dd")} {duty.EndTimeAfternoon}") ? - "LATE" : - DateTime.Parse(d.CheckOut.Value.ToString("yyyy-MM-dd HH:mm")) < - DateTime.Parse($"{d.CheckIn.Date.ToString("yyyy-MM-dd")} {duty.EndTimeMorning}") ? - "ABSENT" : - "NORMAL", - });*/ + /* var processTimeStamps = timeStamps + .Select(d => new + { + d.Id, + CheckInStatus = DateTime.Parse(d.CheckIn.ToString("yyyy-MM-dd HH:mm")) > + DateTime.Parse($"{d.CheckIn.Date.ToString("yyyy-MM-dd")} {duty.StartTimeMorning}") ? + "LATE" : + "NORMAL", + CheckOutStatus = d.CheckOut == null ? "" : + DateTime.Parse(d.CheckOut.Value.ToString("yyyy-MM-dd HH:mm")) < + DateTime.Parse($"{d.CheckIn.Date.ToString("yyyy-MM-dd")} {duty.EndTimeAfternoon}") ? + "LATE" : + DateTime.Parse(d.CheckOut.Value.ToString("yyyy-MM-dd HH:mm")) < + DateTime.Parse($"{d.CheckIn.Date.ToString("yyyy-MM-dd")} {duty.EndTimeMorning}") ? + "ABSENT" : + "NORMAL", + });*/ /*var absentCount = processTimeStamps.Count(x => x.CheckOutStatus == "ABSENT"); var lateCount = processTimeStamps.Count(x => x.CheckInStatus == "LATE");*/ - + var absentCount = timeStamps.Count(d => d.CheckOutStatus == "ABSENT"); // นับจำนวนที่มี CheckOutStatus == "ABSENT" var lateCount = timeStamps.Count(d => @@ -949,8 +949,8 @@ namespace BMA.EHR.Leave.Service.Controllers return Error(jsonData["message"]?.ToString(), StatusCodes.Status403Forbidden); } var profile = new List(); - profile = await _userProfileRepository.GetProfileWithKeycloakAllOfficerRetireFilter(AccessToken, req.node, req.nodeId, jsonData["result"] == "OWNER" || jsonData["result"] == "CHILD", req.isRetirement??true); - + profile = await _userProfileRepository.GetProfileWithKeycloakAllOfficerRetireFilter(AccessToken, req.node, req.nodeId, jsonData["result"] == "OWNER" || jsonData["result"] == "CHILD", req.isRetirement ?? true); + // get leave day var leaveDays = await _leaveRequestRepository.GetSumApproveLeaveByTypeAndRange(req.StartDate, req.EndDate); var leaveTypes = await _leaveTypeRepository.GetAllAsync(); @@ -1154,8 +1154,8 @@ namespace BMA.EHR.Leave.Service.Controllers for (DateTime i = req.StartDate.Date; i <= req.EndDate.Date; i = i.AddDays(1)) { - if (!excludeDates.Contains(i)) - dateList.Add(i); + // if (!excludeDates.Contains(i)) + dateList.Add(i); } var employees = new List(); @@ -1227,7 +1227,7 @@ namespace BMA.EHR.Leave.Service.Controllers if (timeStamps.CheckOut != null) { if (timeStamps.CheckOutStatus == "ABSENT") - remarkStr = "ขาดราชการ" + (!timeStamps.IsLocationCheckOut ? $" (นอกสถานที่:{ timeStamps.CheckOutLocationName })".Trim() : "") ; + remarkStr = "ขาดราชการ" + (!timeStamps.IsLocationCheckOut ? $" (นอกสถานที่:{timeStamps.CheckOutLocationName})".Trim() : ""); else if (timeStamps.CheckInStatus == "ABSENT") remarkStr = "ขาดราชการ" + (!timeStamps.IsLocationCheckIn ? $" (นอกสถานที่:{timeStamps.CheckInLocationName})".Trim() : ""); else if (timeStamps.CheckInStatus == "LATE") From 22f4fce08041ad41ea8d5384f65a993aa0325477 Mon Sep 17 00:00:00 2001 From: Suphonchai Phoonsawat Date: Fri, 7 Mar 2025 15:04:55 +0700 Subject: [PATCH 02/10] change cron timezone --- BMA.EHR.Leave/Program.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BMA.EHR.Leave/Program.cs b/BMA.EHR.Leave/Program.cs index 44bdd418..3415f8f5 100644 --- a/BMA.EHR.Leave/Program.cs +++ b/BMA.EHR.Leave/Program.cs @@ -167,7 +167,7 @@ var app = builder.Build(); var manager = new RecurringJobManager(); if (manager != null) { - manager.AddOrUpdate("ปรับปรุงรอบการลงเวลาทำงาน", Job.FromExpression(x => x.UpdateUserDutyTime()), Cron.Daily(1, 0), TimeZoneInfo.Local); + manager.AddOrUpdate("ปรับปรุงรอบการลงเวลาทำงาน", Job.FromExpression(x => x.UpdateUserDutyTime()), "0 1 * * *", TimeZoneInfo.FindSystemTimeZoneById("Asia/Bangkok")); } // apply migrations From b96f49bd54f947a6a85362ed00fc11aa8c8c78c7 Mon Sep 17 00:00:00 2001 From: kittapath Date: Fri, 7 Mar 2025 22:42:58 +0700 Subject: [PATCH 03/10] =?UTF-8?q?=E0=B9=81=E0=B8=81=E0=B9=89=E0=B8=A3?= =?UTF-8?q?=E0=B8=B2=E0=B8=A2=E0=B8=87=E0=B8=B2=E0=B8=99=E0=B8=A5=E0=B8=B2?= =?UTF-8?q?=E0=B8=87=E0=B8=87=E0=B8=B2=E0=B8=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Extensions/DateTimeExtension.cs | 16 ++++++ .../Controllers/LeaveReportController.cs | 54 ++++++++++++++----- 2 files changed, 56 insertions(+), 14 deletions(-) diff --git a/BMA.EHR.Domain/Extensions/DateTimeExtension.cs b/BMA.EHR.Domain/Extensions/DateTimeExtension.cs index 18f03079..27ebc5a8 100644 --- a/BMA.EHR.Domain/Extensions/DateTimeExtension.cs +++ b/BMA.EHR.Domain/Extensions/DateTimeExtension.cs @@ -408,6 +408,22 @@ namespace BMA.EHR.Domain.Extensions public int days { get; set; } } + // แปลงจาก DayOfWeek เป็นภาษาไทย + public static string GetThaiDayOfWeek(this DateTime date) + { + return date.DayOfWeek switch + { + DayOfWeek.Sunday => "อาทิตย์", + DayOfWeek.Monday => "จันทร์", + DayOfWeek.Tuesday => "อังคาร", + DayOfWeek.Wednesday => "พุธ", + DayOfWeek.Thursday => "พฤหัสบดี", + DayOfWeek.Friday => "ศุกร์", + DayOfWeek.Saturday => "เสาร์", + _ => "ไม่ทราบ" + }; + } + #endregion #endregion diff --git a/BMA.EHR.Leave/Controllers/LeaveReportController.cs b/BMA.EHR.Leave/Controllers/LeaveReportController.cs index 0fa71312..33198b05 100644 --- a/BMA.EHR.Leave/Controllers/LeaveReportController.cs +++ b/BMA.EHR.Leave/Controllers/LeaveReportController.cs @@ -71,6 +71,12 @@ namespace BMA.EHR.Leave.Service.Controllers _httpContextAccessor = httpContextAccessor; _permission = permission; } + private class LoopDate + { + public DateTime date { get; set; } + + public bool isHoliday { get; set; } + } #endregion @@ -1150,12 +1156,25 @@ namespace BMA.EHR.Leave.Service.Controllers var weekend = _holidayRepository.GetWeekEnd(req.StartDate.Date, req.EndDate.Date); var excludeDates = holidays.Union(weekend).ToList(); - var dateList = new List(); - + var dateList = new List(); for (DateTime i = req.StartDate.Date; i <= req.EndDate.Date; i = i.AddDays(1)) { - // if (!excludeDates.Contains(i)) - dateList.Add(i); + if (!excludeDates.Contains(i)) + { + dateList.Add(new LoopDate + { + date = i, + isHoliday = true, + }); + } + else + { + dateList.Add(new LoopDate + { + date = i, + isHoliday = true, + }); + } } var employees = new List(); @@ -1174,7 +1193,7 @@ namespace BMA.EHR.Leave.Service.Controllers { var keycloakUserId = p.Keycloak ?? Guid.Empty; - var timeStamps = await _processUserTimeStampRepository.GetTimestampByDateAsync(keycloakUserId, dd); + var timeStamps = await _processUserTimeStampRepository.GetTimestampByDateAsync(keycloakUserId, dd.date); var fullName = $"{p.Prefix}{p.FirstName} {p.LastName}"; // _userProfileRepository.GetUserFullName(keycloakUserId, AccessToken); @@ -1191,7 +1210,7 @@ namespace BMA.EHR.Leave.Service.Controllers var duty = userRound ?? defaultRound; // check วันลาของแต่ละคน - var leaveReq = await _leaveRequestRepository.GetLeavePeriodAsync(keycloakUserId, dd); + var leaveReq = await _leaveRequestRepository.GetLeavePeriodAsync(keycloakUserId, dd.date); var remarkStr = string.Empty; if (leaveReq != null) @@ -1217,8 +1236,14 @@ namespace BMA.EHR.Leave.Service.Controllers { if (timeStamps == null) { - if (dd <= DateTime.Now.Date) + if (dd.date <= DateTime.Now.Date) + { remarkStr = "ขาดราชการ"; + if (dd.isHoliday == true) + { + remarkStr = "วันหยุด"; + } + } else remarkStr = ""; } else @@ -1266,10 +1291,11 @@ namespace BMA.EHR.Leave.Service.Controllers $"{timeStamps.CheckOut.Value.ToString("HH:mm")} น." : "", remark = remarkStr, - checkInDate = timeStamps == null ? dd.Date.ToThaiFullDate2().ToThaiNumber() : timeStamps.CheckIn.Date.ToThaiFullDate2().ToThaiNumber(), - checkedOutDate = timeStamps == null ? dd.Date.ToThaiFullDate2().ToThaiNumber() : + date = timeStamps == null ? dd.date.Date : timeStamps.CheckIn.Date, + checkInDate = timeStamps == null ? dd.date.Date.ToThaiFullDate2() : timeStamps.CheckIn.Date.ToThaiFullDate2(), + checkedOutDate = timeStamps == null ? dd.date.Date.ToThaiFullDate2() : timeStamps.CheckOut != null ? - timeStamps.CheckOut.Value.ToThaiFullDate2().ToThaiNumber() : + timeStamps.CheckOut.Value.ToThaiFullDate2() : "", }; @@ -1307,10 +1333,10 @@ namespace BMA.EHR.Leave.Service.Controllers } } - var enddate = req.EndDate.Date == req.StartDate.Date ? "" : $" - {req.EndDate.Date.ToThaiShortDate().ToThaiNumber()}"; + var enddate = req.EndDate.Date == req.StartDate.Date ? "" : $" - {req.EndDate.Date.ToThaiShortDate()}"; var item = new { - dateTimeStamp = $"ณ วันที่ {req.StartDate.Date.ToThaiShortDate().ToThaiNumber()}{enddate}", + dateTimeStamp = $"ณ วัน{req.StartDate.Date.GetThaiDayOfWeek()} ที่ {req.StartDate.Date.ToThaiShortDate()}{enddate}", organizationName = profile?.FirstOrDefault()?.Oc ?? "", officerTotal = profile.Count, workTotal = workTotal, @@ -1320,9 +1346,9 @@ namespace BMA.EHR.Leave.Service.Controllers wfhTotal = wfhTotal, seminarTotal = seminarTotal, studyTotal = studyTotal, - employees = employees + employees = employees.OrderBy(x => x.date).ToList() }; - + //วันที่ออก var result = new { template = "TimeStamp", From 3b95c4d2d1ae75185765eee92b2f66f3594ba92d Mon Sep 17 00:00:00 2001 From: kittapath Date: Fri, 7 Mar 2025 23:30:43 +0700 Subject: [PATCH 04/10] report leave sort --- .github/workflows/release_leave.yaml | 72 +++++++++---------- .../Controllers/LeaveReportController.cs | 34 +++++++-- 2 files changed, 64 insertions(+), 42 deletions(-) diff --git a/.github/workflows/release_leave.yaml b/.github/workflows/release_leave.yaml index b017222d..a354557f 100644 --- a/.github/workflows/release_leave.yaml +++ b/.github/workflows/release_leave.yaml @@ -68,40 +68,40 @@ jobs: docker compose pull docker compose up -d echo "${{ steps.gen_ver.outputs.image_ver }}"> success - - name: Notify Discord Success - if: success() - run: | - curl -H "Content-Type: application/json" \ - -X POST \ - -d '{ - "embeds": [{ - "title": "✅ Deployment Success!", - "description": "**Details:**\n- Image: `${{env.IMAGE_NAME}}`\n- Version: `${{ steps.gen_ver.outputs.image_ver }}`\n- Deployed by: `${{github.actor}}`", - "color": 3066993, - "footer": { - "text": "Release Notification", - "icon_url": "https://example.com/success-icon.png" - }, - "timestamp": "'$(date -u +%Y-%m-%dT%H:%M:%SZ)'" - }] - }' \ - ${{ secrets.DISCORD_WEBHOOK }} + # - name: Notify Discord Success + # if: success() + # run: | + # curl -H "Content-Type: application/json" \ + # -X POST \ + # -d '{ + # "embeds": [{ + # "title": "✅ Deployment Success!", + # "description": "**Details:**\n- Image: `${{env.IMAGE_NAME}}`\n- Version: `${{ steps.gen_ver.outputs.image_ver }}`\n- Deployed by: `${{github.actor}}`", + # "color": 3066993, + # "footer": { + # "text": "Release Notification", + # "icon_url": "https://example.com/success-icon.png" + # }, + # "timestamp": "'$(date -u +%Y-%m-%dT%H:%M:%SZ)'" + # }] + # }' \ + # ${{ secrets.DISCORD_WEBHOOK }} - - name: Notify Discord Failure - if: failure() - run: | - curl -H "Content-Type: application/json" \ - -X POST \ - -d '{ - "embeds": [{ - "title": "❌ Deployment Failed!", - "description": "**Details:**\n- Image: `${{env.IMAGE_NAME}}`\n- Version: `${{ steps.gen_ver.outputs.image_ver }}`\n- Attempted by: `${{github.actor}}`", - "color": 15158332, - "footer": { - "text": "Release Notification", - "icon_url": "https://example.com/failure-icon.png" - }, - "timestamp": "'$(date -u +%Y-%m-%dT%H:%M:%SZ)'" - }] - }' \ - ${{ secrets.DISCORD_WEBHOOK }} + # - name: Notify Discord Failure + # if: failure() + # run: | + # curl -H "Content-Type: application/json" \ + # -X POST \ + # -d '{ + # "embeds": [{ + # "title": "❌ Deployment Failed!", + # "description": "**Details:**\n- Image: `${{env.IMAGE_NAME}}`\n- Version: `${{ steps.gen_ver.outputs.image_ver }}`\n- Attempted by: `${{github.actor}}`", + # "color": 15158332, + # "footer": { + # "text": "Release Notification", + # "icon_url": "https://example.com/failure-icon.png" + # }, + # "timestamp": "'$(date -u +%Y-%m-%dT%H:%M:%SZ)'" + # }] + # }' \ + # ${{ secrets.DISCORD_WEBHOOK }} diff --git a/BMA.EHR.Leave/Controllers/LeaveReportController.cs b/BMA.EHR.Leave/Controllers/LeaveReportController.cs index 33198b05..509917c9 100644 --- a/BMA.EHR.Leave/Controllers/LeaveReportController.cs +++ b/BMA.EHR.Leave/Controllers/LeaveReportController.cs @@ -77,6 +77,22 @@ namespace BMA.EHR.Leave.Service.Controllers public bool isHoliday { get; set; } } + private class DateResultReport + { + public int no { get; set; } + + public string fullName { get; set; } + public string dutyTimeName { get; set; } + public string checkInLocation { get; set; } + public string checkInTime { get; set; } + public string checkOutLocation { get; set; } + public string checkOutTime { get; set; } + public string remark { get; set; } + public string checkInDate { get; set; } + public string checkedOutDate { get; set; } + public DateTime? checkInTimeRaw { get; set; } + public DateTime? checkOutTimeRaw { get; set; } + } #endregion @@ -1159,7 +1175,7 @@ namespace BMA.EHR.Leave.Service.Controllers var dateList = new List(); for (DateTime i = req.StartDate.Date; i <= req.EndDate.Date; i = i.AddDays(1)) { - if (!excludeDates.Contains(i)) + if (excludeDates.Contains(i)) { dateList.Add(new LoopDate { @@ -1172,12 +1188,12 @@ namespace BMA.EHR.Leave.Service.Controllers dateList.Add(new LoopDate { date = i, - isHoliday = true, + isHoliday = false, }); } } - var employees = new List(); + var employees = new List(); var count = 1; var restTotal = 0; @@ -1278,7 +1294,7 @@ namespace BMA.EHR.Leave.Service.Controllers } } - var emp = new + var emp = new DateResultReport { no = count, fullName = fullName, @@ -1291,12 +1307,13 @@ namespace BMA.EHR.Leave.Service.Controllers $"{timeStamps.CheckOut.Value.ToString("HH:mm")} น." : "", remark = remarkStr, - date = timeStamps == null ? dd.date.Date : timeStamps.CheckIn.Date, checkInDate = timeStamps == null ? dd.date.Date.ToThaiFullDate2() : timeStamps.CheckIn.Date.ToThaiFullDate2(), checkedOutDate = timeStamps == null ? dd.date.Date.ToThaiFullDate2() : timeStamps.CheckOut != null ? timeStamps.CheckOut.Value.ToThaiFullDate2() : "", + checkInTimeRaw = timeStamps?.CheckIn, + checkOutTimeRaw = timeStamps?.CheckOut, }; if (timeStamps != null) @@ -1332,6 +1349,11 @@ namespace BMA.EHR.Leave.Service.Controllers count++; } } + employees = employees.OrderBy(x => x.checkInTimeRaw ?? DateTime.MaxValue).ThenBy(x => x.checkOutTimeRaw ?? DateTime.MaxValue).ToList(); + for (int i = 0; i < employees.Count; i++) + { + employees[i].no = i + 1; + } var enddate = req.EndDate.Date == req.StartDate.Date ? "" : $" - {req.EndDate.Date.ToThaiShortDate()}"; var item = new @@ -1346,7 +1368,7 @@ namespace BMA.EHR.Leave.Service.Controllers wfhTotal = wfhTotal, seminarTotal = seminarTotal, studyTotal = studyTotal, - employees = employees.OrderBy(x => x.date).ToList() + employees = employees }; //วันที่ออก var result = new From 8980c4a2ee163b6b70e8f655f320b9ec067169fc Mon Sep 17 00:00:00 2001 From: kittapath Date: Sat, 8 Mar 2025 00:35:47 +0700 Subject: [PATCH 05/10] on noti leave --- .github/workflows/release_leave.yaml | 72 ++++++++++++++-------------- 1 file changed, 36 insertions(+), 36 deletions(-) diff --git a/.github/workflows/release_leave.yaml b/.github/workflows/release_leave.yaml index a354557f..b017222d 100644 --- a/.github/workflows/release_leave.yaml +++ b/.github/workflows/release_leave.yaml @@ -68,40 +68,40 @@ jobs: docker compose pull docker compose up -d echo "${{ steps.gen_ver.outputs.image_ver }}"> success - # - name: Notify Discord Success - # if: success() - # run: | - # curl -H "Content-Type: application/json" \ - # -X POST \ - # -d '{ - # "embeds": [{ - # "title": "✅ Deployment Success!", - # "description": "**Details:**\n- Image: `${{env.IMAGE_NAME}}`\n- Version: `${{ steps.gen_ver.outputs.image_ver }}`\n- Deployed by: `${{github.actor}}`", - # "color": 3066993, - # "footer": { - # "text": "Release Notification", - # "icon_url": "https://example.com/success-icon.png" - # }, - # "timestamp": "'$(date -u +%Y-%m-%dT%H:%M:%SZ)'" - # }] - # }' \ - # ${{ secrets.DISCORD_WEBHOOK }} + - name: Notify Discord Success + if: success() + run: | + curl -H "Content-Type: application/json" \ + -X POST \ + -d '{ + "embeds": [{ + "title": "✅ Deployment Success!", + "description": "**Details:**\n- Image: `${{env.IMAGE_NAME}}`\n- Version: `${{ steps.gen_ver.outputs.image_ver }}`\n- Deployed by: `${{github.actor}}`", + "color": 3066993, + "footer": { + "text": "Release Notification", + "icon_url": "https://example.com/success-icon.png" + }, + "timestamp": "'$(date -u +%Y-%m-%dT%H:%M:%SZ)'" + }] + }' \ + ${{ secrets.DISCORD_WEBHOOK }} - # - name: Notify Discord Failure - # if: failure() - # run: | - # curl -H "Content-Type: application/json" \ - # -X POST \ - # -d '{ - # "embeds": [{ - # "title": "❌ Deployment Failed!", - # "description": "**Details:**\n- Image: `${{env.IMAGE_NAME}}`\n- Version: `${{ steps.gen_ver.outputs.image_ver }}`\n- Attempted by: `${{github.actor}}`", - # "color": 15158332, - # "footer": { - # "text": "Release Notification", - # "icon_url": "https://example.com/failure-icon.png" - # }, - # "timestamp": "'$(date -u +%Y-%m-%dT%H:%M:%SZ)'" - # }] - # }' \ - # ${{ secrets.DISCORD_WEBHOOK }} + - name: Notify Discord Failure + if: failure() + run: | + curl -H "Content-Type: application/json" \ + -X POST \ + -d '{ + "embeds": [{ + "title": "❌ Deployment Failed!", + "description": "**Details:**\n- Image: `${{env.IMAGE_NAME}}`\n- Version: `${{ steps.gen_ver.outputs.image_ver }}`\n- Attempted by: `${{github.actor}}`", + "color": 15158332, + "footer": { + "text": "Release Notification", + "icon_url": "https://example.com/failure-icon.png" + }, + "timestamp": "'$(date -u +%Y-%m-%dT%H:%M:%SZ)'" + }] + }' \ + ${{ secrets.DISCORD_WEBHOOK }} From 772c9482aa3202435806eaabd1fd4c30fa5da2b8 Mon Sep 17 00:00:00 2001 From: Suphonchai Phoonsawat Date: Sat, 8 Mar 2025 10:53:24 +0700 Subject: [PATCH 06/10] add timezone in docker container and force before hangfire start --- BMA.EHR.Leave/Dockerfile | 7 +- BMA.EHR.Leave/Program.cs | 270 ++++++++++++++++++++------------------- 2 files changed, 143 insertions(+), 134 deletions(-) diff --git a/BMA.EHR.Leave/Dockerfile b/BMA.EHR.Leave/Dockerfile index 3c211ab3..2e851b2c 100644 --- a/BMA.EHR.Leave/Dockerfile +++ b/BMA.EHR.Leave/Dockerfile @@ -1,6 +1,11 @@ -#See https://aka.ms/containerfastmode to understand how Visual Studio uses this Dockerfile to build your images for faster debugging. +#See https://aka.ms/containerfastmode to understand how Visual Studio uses this Dockerfile to build your images for faster debugging. FROM mcr.microsoft.com/dotnet/aspnet:7.0 AS base + +# ตั้งค่า TimeZone ใน Container +ENV TZ=Asia/Bangkok +RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone + WORKDIR /app EXPOSE 80 EXPOSE 443 diff --git a/BMA.EHR.Leave/Program.cs b/BMA.EHR.Leave/Program.cs index 3415f8f5..82ae3aea 100644 --- a/BMA.EHR.Leave/Program.cs +++ b/BMA.EHR.Leave/Program.cs @@ -24,163 +24,167 @@ using BMA.EHR.Application.Repositories.Leaves.TimeAttendants; using BMA.EHR.Leave.Service.Extensions; var builder = WebApplication.CreateBuilder(args); +// ตั้ง TimeZone เป็น Asia/Bangkok ในโค้ด +var bangkokTimeZone = TimeZoneInfo.FindSystemTimeZoneById("Asia/Bangkok"); +TimeZoneInfo.ClearCachedData(); + + +var issuer = builder.Configuration["Jwt:Issuer"]; +var key = builder.Configuration["Jwt:Key"]; + + +IdentityModelEventSource.ShowPII = true; + +builder.Services.AddHttpContextAccessor(); + +builder.Services.AddApiVersioning(opt => { - var issuer = builder.Configuration["Jwt:Issuer"]; - var key = builder.Configuration["Jwt:Key"]; + opt.DefaultApiVersion = new ApiVersion(1, 0); + opt.AssumeDefaultVersionWhenUnspecified = true; + opt.ReportApiVersions = true; + opt.ApiVersionReader = ApiVersionReader.Combine(new UrlSegmentApiVersionReader(), + new HeaderApiVersionReader("x-api-version"), + new MediaTypeApiVersionReader("x-api-version")); +}); +builder.Services.AddVersionedApiExplorer(setup => +{ + setup.GroupNameFormat = "'v'VVV"; + setup.SubstituteApiVersionInUrl = true; +}); - IdentityModelEventSource.ShowPII = true; +builder.Services.AddEndpointsApiExplorer(); - builder.Services.AddHttpContextAccessor(); - - builder.Services.AddApiVersioning(opt => +// Authorization +builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer(opt => +{ + opt.SaveToken = true; + opt.RequireHttpsMetadata = false; //false for dev + opt.Authority = issuer; + opt.TokenValidationParameters = new() { - opt.DefaultApiVersion = new ApiVersion(1, 0); - opt.AssumeDefaultVersionWhenUnspecified = true; - opt.ReportApiVersions = true; - opt.ApiVersionReader = ApiVersionReader.Combine(new UrlSegmentApiVersionReader(), - new HeaderApiVersionReader("x-api-version"), - new MediaTypeApiVersionReader("x-api-version")); - }); + ValidateIssuer = true, + ValidateAudience = false, + ValidateLifetime = true, + ValidateIssuerSigningKey = true, + ValidIssuer = issuer, + IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(key)) + }; +}); +builder.Services.AddAuthorization(); - builder.Services.AddVersionedApiExplorer(setup => - { - setup.GroupNameFormat = "'v'VVV"; - setup.SubstituteApiVersionInUrl = true; - }); +// use serilog +ConfigureLogs(); +builder.Host.UseSerilog(); - builder.Services.AddEndpointsApiExplorer(); - - // Authorization - builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer(opt => - { - opt.SaveToken = true; - opt.RequireHttpsMetadata = false; //false for dev - opt.Authority = issuer; - opt.TokenValidationParameters = new() - { - ValidateIssuer = true, - ValidateAudience = false, - ValidateLifetime = true, - ValidateIssuerSigningKey = true, - ValidIssuer = issuer, - IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(key)) - }; - }); - builder.Services.AddAuthorization(); - - // use serilog - ConfigureLogs(); - builder.Host.UseSerilog(); - - // Add config CORS - builder.Services.AddCors(options => options.AddDefaultPolicy(builder => - { - builder - .AllowAnyOrigin() - .AllowAnyMethod() - .AllowAnyHeader() - .SetIsOriginAllowedToAllowWildcardSubdomains(); - })); +// Add config CORS +builder.Services.AddCors(options => options.AddDefaultPolicy(builder => +{ + builder + .AllowAnyOrigin() + .AllowAnyMethod() + .AllowAnyHeader() + .SetIsOriginAllowedToAllowWildcardSubdomains(); +})); - // Add services to the container. - builder.Services.AddApplication(); - builder.Services.AddLeaveApplication(); - builder.Services.AddPersistence(builder.Configuration); - builder.Services.AddLeavePersistence(builder.Configuration); +// Add services to the container. +builder.Services.AddApplication(); +builder.Services.AddLeaveApplication(); +builder.Services.AddPersistence(builder.Configuration); +builder.Services.AddLeavePersistence(builder.Configuration); - builder.Services.AddHttpClient(); +builder.Services.AddHttpClient(); - builder.Services.AddControllers(options => - { - options.SuppressAsyncSuffixInActionNames = false; - }) - .AddNewtonsoftJson(x => x.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore); +builder.Services.AddControllers(options => +{ + options.SuppressAsyncSuffixInActionNames = false; +}) +.AddNewtonsoftJson(x => x.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore); - builder.Services.AddSwaggerGen(); - builder.Services.ConfigureOptions(); +builder.Services.AddSwaggerGen(); +builder.Services.ConfigureOptions(); - builder.Services.AddHealthChecks(); +builder.Services.AddHealthChecks(); - builder.Services.AddRabbitMqConnectionPooling(builder.Configuration); +builder.Services.AddRabbitMqConnectionPooling(builder.Configuration); - // Add Hangfire services. - var defaultConnection = builder.Configuration.GetConnectionString("DefaultConnection"); +// Add Hangfire services. +var defaultConnection = builder.Configuration.GetConnectionString("DefaultConnection"); + +builder.Services.AddHangfire(configuration => configuration + .SetDataCompatibilityLevel(CompatibilityLevel.Version_170) + .UseSimpleAssemblyNameTypeSerializer() + .UseRecommendedSerializerSettings() + .UseStorage( + new MySqlStorage( + defaultConnection, + new MySqlStorageOptions + { + TransactionIsolationLevel = IsolationLevel.ReadCommitted, + QueuePollInterval = TimeSpan.FromSeconds(15), + JobExpirationCheckInterval = TimeSpan.FromHours(1), + CountersAggregateInterval = TimeSpan.FromMinutes(5), + PrepareSchemaIfNecessary = true, + DashboardJobListLimit = 50000, + TransactionTimeout = TimeSpan.FromMinutes(1), + TablesPrefix = "Hangfire" + }))); +builder.Services.AddHangfireServer(); - builder.Services.AddHangfire(configuration => configuration - .SetDataCompatibilityLevel(CompatibilityLevel.Version_170) - .UseSimpleAssemblyNameTypeSerializer() - .UseRecommendedSerializerSettings() - .UseStorage( - new MySqlStorage( - defaultConnection, - new MySqlStorageOptions - { - TransactionIsolationLevel = IsolationLevel.ReadCommitted, - QueuePollInterval = TimeSpan.FromSeconds(15), - JobExpirationCheckInterval = TimeSpan.FromHours(1), - CountersAggregateInterval = TimeSpan.FromMinutes(5), - PrepareSchemaIfNecessary = true, - DashboardJobListLimit = 50000, - TransactionTimeout = TimeSpan.FromMinutes(1), - TablesPrefix = "Hangfire" - }))); - builder.Services.AddHangfireServer(); -} var app = builder.Build(); + +var apiVersionDescriptionProvider = app.Services.GetRequiredService(); + +if (app.Environment.IsDevelopment()) { - var apiVersionDescriptionProvider = app.Services.GetRequiredService(); - - if (app.Environment.IsDevelopment()) + app.UseSwagger(); + app.UseSwaggerUI(options => { - app.UseSwagger(); - app.UseSwaggerUI(options => + foreach (var description in apiVersionDescriptionProvider.ApiVersionDescriptions) { - foreach (var description in apiVersionDescriptionProvider.ApiVersionDescriptions) - { - options.SwaggerEndpoint($"/swagger/{description.GroupName}/swagger.json", - description.GroupName.ToUpperInvariant()); - } - }); - } - - app.MapHealthChecks("/health"); - - - app.UseHttpsRedirection(); - app.UseCors(); - app.UseAuthentication(); - app.UseAuthorization(); - app.UseDefaultFiles(); - app.UseStaticFiles(); - app.MapControllers(); - app.UseMiddleware(); - - - app.UseHangfireDashboard("/hangfire", new DashboardOptions() - { - Authorization = new[] { new CustomAuthorizeFilter() } + options.SwaggerEndpoint($"/swagger/{description.GroupName}/swagger.json", + description.GroupName.ToUpperInvariant()); + } }); - - var manager = new RecurringJobManager(); - if (manager != null) - { - manager.AddOrUpdate("ปรับปรุงรอบการลงเวลาทำงาน", Job.FromExpression(x => x.UpdateUserDutyTime()), "0 1 * * *", TimeZoneInfo.FindSystemTimeZoneById("Asia/Bangkok")); - } - - // apply migrations - await using var scope = app.Services.CreateAsyncScope(); - await using var db = scope.ServiceProvider.GetRequiredService(); - await db.Database.MigrateAsync(); - - // seed default data - await LeaveSeeder.SeedLeaveType(app); - - app.Run(); } +app.MapHealthChecks("/health"); + + +app.UseHttpsRedirection(); +app.UseCors(); +app.UseAuthentication(); +app.UseAuthorization(); +app.UseDefaultFiles(); +app.UseStaticFiles(); +app.MapControllers(); +app.UseMiddleware(); + + +app.UseHangfireDashboard("/hangfire", new DashboardOptions() +{ + Authorization = new[] { new CustomAuthorizeFilter() } +}); + +var manager = new RecurringJobManager(); +if (manager != null) +{ + manager.AddOrUpdate("ปรับปรุงรอบการลงเวลาทำงาน", Job.FromExpression(x => x.UpdateUserDutyTime()), "0 1 * * *", bangkokTimeZone); +} + +// apply migrations +await using var scope = app.Services.CreateAsyncScope(); +await using var db = scope.ServiceProvider.GetRequiredService(); +await db.Database.MigrateAsync(); + +// seed default data +await LeaveSeeder.SeedLeaveType(app); + +app.Run(); + + void ConfigureLogs() { var environment = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT"); From bf61ae65de2445d7230b20e4e6e867f3b8ca2325 Mon Sep 17 00:00:00 2001 From: Suphonchai Phoonsawat Date: Sat, 8 Mar 2025 12:07:53 +0700 Subject: [PATCH 07/10] =?UTF-8?q?=E0=B8=97=E0=B8=94=E0=B8=AA=E0=B8=AD?= =?UTF-8?q?=E0=B8=9A=E0=B8=A3=E0=B8=B1=E0=B8=99=E0=B8=95=E0=B8=AD=E0=B8=99?= =?UTF-8?q?=E0=B8=9A=E0=B9=88=E0=B8=B2=E0=B8=A2=E0=B9=82=E0=B8=A1=E0=B8=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- BMA.EHR.Leave/Program.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BMA.EHR.Leave/Program.cs b/BMA.EHR.Leave/Program.cs index 82ae3aea..b3331847 100644 --- a/BMA.EHR.Leave/Program.cs +++ b/BMA.EHR.Leave/Program.cs @@ -171,7 +171,7 @@ app.UseHangfireDashboard("/hangfire", new DashboardOptions() var manager = new RecurringJobManager(); if (manager != null) { - manager.AddOrUpdate("ปรับปรุงรอบการลงเวลาทำงาน", Job.FromExpression(x => x.UpdateUserDutyTime()), "0 1 * * *", bangkokTimeZone); + manager.AddOrUpdate("ปรับปรุงรอบการลงเวลาทำงาน", Job.FromExpression(x => x.UpdateUserDutyTime()), "0 13 * * *", bangkokTimeZone); } // apply migrations From db8f425877662e0cc04c72c91fd16d498d6bd5cc Mon Sep 17 00:00:00 2001 From: Suphonchai Phoonsawat Date: Sat, 8 Mar 2025 13:04:58 +0700 Subject: [PATCH 08/10] =?UTF-8?q?=E0=B9=81=E0=B8=81=E0=B9=89=20cron=20?= =?UTF-8?q?=E0=B8=81=E0=B8=A5=E0=B8=B1=E0=B8=9A=20=E0=B8=95=E0=B8=B5?= =?UTF-8?q?=E0=B8=AB=E0=B8=99=E0=B8=B6=E0=B9=88=E0=B8=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- BMA.EHR.Leave/Program.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BMA.EHR.Leave/Program.cs b/BMA.EHR.Leave/Program.cs index b3331847..82ae3aea 100644 --- a/BMA.EHR.Leave/Program.cs +++ b/BMA.EHR.Leave/Program.cs @@ -171,7 +171,7 @@ app.UseHangfireDashboard("/hangfire", new DashboardOptions() var manager = new RecurringJobManager(); if (manager != null) { - manager.AddOrUpdate("ปรับปรุงรอบการลงเวลาทำงาน", Job.FromExpression(x => x.UpdateUserDutyTime()), "0 13 * * *", bangkokTimeZone); + manager.AddOrUpdate("ปรับปรุงรอบการลงเวลาทำงาน", Job.FromExpression(x => x.UpdateUserDutyTime()), "0 1 * * *", bangkokTimeZone); } // apply migrations From 950b5c4604c37d898ec827fe353db4c84907ba80 Mon Sep 17 00:00:00 2001 From: Suphonchai Phoonsawat Date: Sun, 9 Mar 2025 09:38:13 +0700 Subject: [PATCH 09/10] fix #1239 --- BMA.EHR.Leave/Controllers/LeaveReportController.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/BMA.EHR.Leave/Controllers/LeaveReportController.cs b/BMA.EHR.Leave/Controllers/LeaveReportController.cs index 509917c9..7cc2fc36 100644 --- a/BMA.EHR.Leave/Controllers/LeaveReportController.cs +++ b/BMA.EHR.Leave/Controllers/LeaveReportController.cs @@ -1277,7 +1277,8 @@ namespace BMA.EHR.Leave.Service.Controllers lateTotal += 1; } else - remarkStr = ""; + remarkStr = !timeStamps.IsLocationCheckIn ? $" นอกสถานที่:{timeStamps.CheckInLocationName}".Trim() : ""; + //remarkStr = ""; } else { @@ -1289,7 +1290,8 @@ namespace BMA.EHR.Leave.Service.Controllers lateTotal += 1; } else - remarkStr = ""; + //remarkStr = ""; + remarkStr = !timeStamps.IsLocationCheckIn ? $" นอกสถานที่:{timeStamps.CheckInLocationName}".Trim() : ""; } } } From 6f7b9bf3e6e760f7840010959f20b8015b309f92 Mon Sep 17 00:00:00 2001 From: Suphonchai Phoonsawat Date: Mon, 10 Mar 2025 09:37:34 +0700 Subject: [PATCH 10/10] fix start time --- .../Leaves/TimeAttendants/UserDutyTimeRepository.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/BMA.EHR.Application/Repositories/Leaves/TimeAttendants/UserDutyTimeRepository.cs b/BMA.EHR.Application/Repositories/Leaves/TimeAttendants/UserDutyTimeRepository.cs index 48bdd0da..3eefefe0 100644 --- a/BMA.EHR.Application/Repositories/Leaves/TimeAttendants/UserDutyTimeRepository.cs +++ b/BMA.EHR.Application/Repositories/Leaves/TimeAttendants/UserDutyTimeRepository.cs @@ -62,7 +62,7 @@ namespace BMA.EHR.Application.Repositories.Leaves.TimeAttendants { var data = _dbContext.Set() .Where(u => !u.IsProcess) - .Where(u => u.EffectiveDate.Value.Date <= DateTime.Now.Date) + .Where(u => u.EffectiveDate.Value.Date <= DateTime.Now.AddHours(7).Date) .ToList(); foreach (var d in data) @@ -106,6 +106,7 @@ namespace BMA.EHR.Application.Repositories.Leaves.TimeAttendants var data = await _dbContext.Set() .Where(x => x.ProfileId == profileId) .Where(x => x.IsProcess) + .Where(x => x.EffectiveDate.Value.Date <= DateTime.Now.Date) .OrderByDescending(x => x.EffectiveDate) .FirstOrDefaultAsync();