From 92847e6be2e3644254709ec87b317f23615305d3 Mon Sep 17 00:00:00 2001 From: Suphonchai Phoonsawat Date: Thu, 24 Apr 2025 10:59:31 +0700 Subject: [PATCH] =?UTF-8?q?=E0=B9=81=E0=B8=81=E0=B9=89=E0=B9=84=E0=B8=82?= =?UTF-8?q?=20-=20format=20response=20-=20=E0=B8=AA=E0=B9=88=E0=B8=87=20no?= =?UTF-8?q?ti=20=E0=B9=82=E0=B8=94=E0=B8=A2=E0=B8=A1=E0=B8=B5=E0=B8=81?= =?UTF-8?q?=E0=B8=B2=E0=B8=A3=E0=B9=81=E0=B8=99=E0=B8=9A=E0=B8=A5=E0=B8=B4?= =?UTF-8?q?=E0=B8=87=E0=B8=84=E0=B9=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .dockerignore | 10 +- .../LeaveRequests/LeaveBeginingRepository.cs | 1 + .../LeaveRequests/LeaveRequestRepository.cs | 119 ++++++++++++++++-- .../Controllers/LeaveBeginningController.cs | 38 +++++- BMA.EHR.Leave/Dockerfile | 60 ++++++++- BMA.EHR.Leave/appsettings.json | 4 +- 6 files changed, 211 insertions(+), 21 deletions(-) diff --git a/.dockerignore b/.dockerignore index 3729ff0c..8bf65212 100644 --- a/.dockerignore +++ b/.dockerignore @@ -22,4 +22,12 @@ **/secrets.dev.yaml **/values.dev.yaml LICENSE -README.md \ No newline at end of file +README.md +.git +**/bin/ +**/obj/ +.vscode/ +.dockerignore +.gitignore +README.md +*.md \ No newline at end of file diff --git a/BMA.EHR.Application/Repositories/Leaves/LeaveRequests/LeaveBeginingRepository.cs b/BMA.EHR.Application/Repositories/Leaves/LeaveRequests/LeaveBeginingRepository.cs index 05a33f89..db09f644 100644 --- a/BMA.EHR.Application/Repositories/Leaves/LeaveRequests/LeaveBeginingRepository.cs +++ b/BMA.EHR.Application/Repositories/Leaves/LeaveRequests/LeaveBeginingRepository.cs @@ -57,6 +57,7 @@ namespace BMA.EHR.Application.Repositories.Leaves.LeaveRequests public async Task> GetAllByYearAsync(int year) { return await _dbContext.Set() + .Include(x => x.LeaveType) .Where(x => x.LeaveYear == year) .ToListAsync(); } diff --git a/BMA.EHR.Application/Repositories/Leaves/LeaveRequests/LeaveRequestRepository.cs b/BMA.EHR.Application/Repositories/Leaves/LeaveRequests/LeaveRequestRepository.cs index 8b828b00..565cdf97 100644 --- a/BMA.EHR.Application/Repositories/Leaves/LeaveRequests/LeaveRequestRepository.cs +++ b/BMA.EHR.Application/Repositories/Leaves/LeaveRequests/LeaveRequestRepository.cs @@ -28,6 +28,8 @@ namespace BMA.EHR.Application.Repositories.Leaves.LeaveRequests private readonly IApplicationDBContext _appDbContext; + private readonly string URL = string.Empty; + #endregion #region " Constructor and Destuctor " @@ -47,6 +49,8 @@ namespace BMA.EHR.Application.Repositories.Leaves.LeaveRequests _configuration = configuration; _emailSenderService = emailSenderService; _appDbContext = appDbContext; + + URL = (_configuration["API"]).Replace("/api/v1", ""); } #endregion @@ -506,6 +510,22 @@ namespace BMA.EHR.Application.Repositories.Leaves.LeaveRequests rawData.ApproveStep = "st2"; await UpdateAsync(rawData); + + // TODO: Send notification to 1st Commander + var firstCommander = rawData.Approvers + .Where(x => x.ApproveType!.ToUpper() == "COMMANDER") + .OrderBy(x => x.Seq) + .FirstOrDefault(); + // Send Notification + var noti1 = new Notification + { + Body = $"การขอลาของคุณ {rawData.FirstName} {rawData.LastName} รอรับการอนุมัติจากคุณ", + ReceiverUserId = firstCommander!.ProfileId, + Type = "", + Payload = $"{URL}/leave/detail/{id}", + }; + _appDbContext.Set().Add(noti1); + await _appDbContext.SaveChangesAsync(); } public async Task CommanderApproveLeaveRequest(Guid id, string reason) @@ -527,7 +547,6 @@ namespace BMA.EHR.Application.Repositories.Leaves.LeaveRequests // check commander approve var approvers = rawData.Approvers.Where(x => x.ApproveType!.ToUpper() == "COMMANDER").OrderBy(x => x.Seq).ToList(); - var maxSeq = approvers.Max(x => x.Seq); var approver = approvers.FirstOrDefault(x => x.KeycloakId == userId); if (approver == null) @@ -535,21 +554,34 @@ namespace BMA.EHR.Application.Repositories.Leaves.LeaveRequests throw new Exception("คุณไม่มีสิทธิ์อนุมัติการลาในขั้นตอนนี้"); } + // check prev approver มี action แล้วหรือไม่? + var prevApprover = approvers.FirstOrDefault(x => x.Seq == approver.Seq - 1); + + if (prevApprover != null) + { + if (prevApprover.ApproveStatus == "PENDING") + { + throw new Exception("ไม่สามารถทำการอนุมัติได้ เนื่องจากยังอยู่ระหว่างการพิจารณาโดยผู้บังคับบัญชารายก่อนหน้า"); + } + } + + var maxSeq = approvers.Max(x => x.Seq); + approver.ApproveStatus = "APPROVE"; approver.Comment = reason; if (approver.Seq != maxSeq) { - var nextApprover = approvers.FirstOrDefault(x => x.Seq == approver.Seq + 1); + // Send Noti var noti = new Notification { Body = $"การขอลาของคุณ {rawData.FirstName} {rawData.LastName} รอรับการอนุมัติจากคุณ", ReceiverUserId = nextApprover!.ProfileId, Type = "", - Payload = "", + Payload = $"{URL}/leave/detail/{id}", }; _appDbContext.Set().Add(noti); await _appDbContext.SaveChangesAsync(); @@ -564,6 +596,22 @@ namespace BMA.EHR.Application.Repositories.Leaves.LeaveRequests rawData.ApproveStep = "st3"; await UpdateAsync(rawData); + + // TODO: Send notification to 1st Approver + var firstCommander = rawData.Approvers + .Where(x => x.ApproveType!.ToUpper() == "APPROVER") + .OrderBy(x => x.Seq) + .FirstOrDefault(); + // Send Notification + var noti1 = new Notification + { + Body = $"การขอลาของคุณ {rawData.FirstName} {rawData.LastName} รอรับการอนุมัติจากคุณ", + ReceiverUserId = firstCommander!.ProfileId, + Type = "", + Payload = $"{URL}/leave/detail/{id}", + }; + _appDbContext.Set().Add(noti1); + await _appDbContext.SaveChangesAsync(); } } @@ -587,7 +635,6 @@ namespace BMA.EHR.Application.Repositories.Leaves.LeaveRequests // check commander approve var approvers = rawData.Approvers.Where(x => x.ApproveType!.ToUpper() == "COMMANDER").OrderBy(x => x.Seq).ToList(); - var maxSeq = approvers.Max(x => x.Seq); var approver = approvers.FirstOrDefault(x => x.KeycloakId == userId); if (approver == null) @@ -595,6 +642,20 @@ namespace BMA.EHR.Application.Repositories.Leaves.LeaveRequests throw new Exception("คุณไม่มีสิทธิ์อนุมัติการลาในขั้นตอนนี้"); } + // check prev approver มี action แล้วหรือไม่? + var prevApprover = approvers.FirstOrDefault(x => x.Seq == approver.Seq - 1); + + if (prevApprover != null) + { + if (prevApprover.ApproveStatus == "PENDING") + { + throw new Exception("ไม่สามารถทำการอนุมัติได้ เนื่องจากยังอยู่ระหว่างการพิจารณาโดยผู้บังคับบัญชารายก่อนหน้า"); + } + } + + + var maxSeq = approvers.Max(x => x.Seq); + approver.ApproveStatus = "REJECT"; approver.Comment = reason; @@ -609,7 +670,7 @@ namespace BMA.EHR.Application.Repositories.Leaves.LeaveRequests Body = $"การขอลาของคุณ {rawData.FirstName} {rawData.LastName} รอรับการอนุมัติจากคุณ", ReceiverUserId = nextApprover!.ProfileId, Type = "", - Payload = "", + Payload = $"{URL}/leave/detail/{id}", }; _appDbContext.Set().Add(noti); await _appDbContext.SaveChangesAsync(); @@ -624,6 +685,22 @@ namespace BMA.EHR.Application.Repositories.Leaves.LeaveRequests rawData.ApproveStep = "st3"; await UpdateAsync(rawData); + + // TODO: Send notification to 1st Approver + var firstCommander = rawData.Approvers + .Where(x => x.ApproveType!.ToUpper() == "APPROVER") + .OrderBy(x => x.Seq) + .FirstOrDefault(); + // Send Notification + var noti1 = new Notification + { + Body = $"การขอลาของคุณ {rawData.FirstName} {rawData.LastName} รอรับการอนุมัติจากคุณ", + ReceiverUserId = firstCommander!.ProfileId, + Type = "", + Payload = $"{URL}/leave/detail/{id}", + }; + _appDbContext.Set().Add(noti1); + await _appDbContext.SaveChangesAsync(); } } @@ -646,7 +723,6 @@ namespace BMA.EHR.Application.Repositories.Leaves.LeaveRequests // check commander approve var approvers = rawData.Approvers.Where(x => x.ApproveType!.ToUpper() == "APPROVER").OrderBy(x => x.Seq).ToList(); - var maxSeq = approvers.Max(x => x.Seq); var approver = approvers.FirstOrDefault(x => x.KeycloakId == userId); if (approver == null) @@ -654,6 +730,20 @@ namespace BMA.EHR.Application.Repositories.Leaves.LeaveRequests throw new Exception("คุณไม่มีสิทธิ์อนุมัติการลาในขั้นตอนนี้"); } + // check prev approver มี action แล้วหรือไม่? + var prevApprover = approvers.FirstOrDefault(x => x.Seq == approver.Seq - 1); + + if (prevApprover != null) + { + if (prevApprover.ApproveStatus == "PENDING") + { + throw new Exception("ไม่สามารถทำการอนุมัติได้ เนื่องจากยังอยู่ระหว่างการพิจารณาโดยผู้บังคับบัญชารายก่อนหน้า"); + } + } + + + var maxSeq = approvers.Max(x => x.Seq); + approver.ApproveStatus = "APPROVE"; approver.Comment = reason; @@ -773,7 +863,6 @@ namespace BMA.EHR.Application.Repositories.Leaves.LeaveRequests // check commander approve var approvers = rawData.Approvers.Where(x => x.ApproveType!.ToUpper() == "APPROVER").OrderBy(x => x.Seq).ToList(); - var maxSeq = approvers.Max(x => x.Seq); var approver = approvers.FirstOrDefault(x => x.KeycloakId == userId); if (approver == null) @@ -781,6 +870,20 @@ namespace BMA.EHR.Application.Repositories.Leaves.LeaveRequests throw new Exception("คุณไม่มีสิทธิ์อนุมัติการลาในขั้นตอนนี้"); } + // check prev approver มี action แล้วหรือไม่? + var prevApprover = approvers.FirstOrDefault(x => x.Seq == approver.Seq - 1); + + if (prevApprover != null) + { + if (prevApprover.ApproveStatus == "PENDING") + { + throw new Exception("ไม่สามารถทำการอนุมัติได้ เนื่องจากยังอยู่ระหว่างการพิจารณาโดยผู้บังคับบัญชารายก่อนหน้า"); + } + } + + + var maxSeq = approvers.Max(x => x.Seq); + approver.ApproveStatus = "REJECT"; approver.Comment = reason; @@ -824,7 +927,7 @@ namespace BMA.EHR.Application.Repositories.Leaves.LeaveRequests }; _appDbContext.Set().Add(noti); await _appDbContext.SaveChangesAsync(); - } + } } public async Task> GetSumSendLeaveAsync(int year) diff --git a/BMA.EHR.Leave/Controllers/LeaveBeginningController.cs b/BMA.EHR.Leave/Controllers/LeaveBeginningController.cs index 2a148a0c..ff714ccd 100644 --- a/BMA.EHR.Leave/Controllers/LeaveBeginningController.cs +++ b/BMA.EHR.Leave/Controllers/LeaveBeginningController.cs @@ -8,6 +8,7 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Newtonsoft.Json; using Newtonsoft.Json.Linq; +using OfficeOpenXml.ConditionalFormatting; using Swashbuckle.AspNetCore.Annotations; using System.Security.Claims; @@ -102,15 +103,40 @@ namespace BMA.EHR.Leave.Service.Controllers return Error(jsonData["message"]?.ToString(), StatusCodes.Status403Forbidden); } - var result = await _leaveBeginningRepository.GetAllByYearAsync(req.Year); + var resData = await _leaveBeginningRepository.GetAllByYearAsync(req.Year); if (req.Type != Guid.Empty) - result = result.Where(x => x.LeaveTypeId == req.Type).ToList(); + resData = resData.Where(x => x.LeaveTypeId == req.Type).ToList(); if (req.Keyword != "") - result = result.Where(x => x.FirstName!.Contains(req.Keyword) || x.LastName!.Contains(req.Keyword)).ToList(); + resData = resData.Where(x => x.FirstName!.Contains(req.Keyword) || x.LastName!.Contains(req.Keyword)).ToList(); + + var result = new List(); + + foreach (var item in resData) + { + result.Add(new + { + item.Id, + item.ProfileId, + item.Prefix, + item.FirstName, + item.LastName, + item.LeaveTypeId, + LeaveTypeCode = item.LeaveType?.Code, + LeaveType = item.LeaveType?.Name, + item.LeaveYear, + item.LeaveDays, + item.LeaveDaysUsed, + item.CreatedAt, + item.CreatedFullName, + item.LastUpdatedAt, + item.LastUpdateFullName + }); + } var pageResult = result.Skip((req.Page - 1) * req.PageSize).Take(req.PageSize).ToList(); + return Success(new { data = pageResult, total = result.Count }); } catch (Exception ex) @@ -212,9 +238,9 @@ namespace BMA.EHR.Leave.Service.Controllers var profile = await _userProfileRepository.GetProfileByProfileIdAsync(req.ProfileId, AccessToken); - if(profile == null) + if (profile == null) { - return Error("ไม่พบข้อมูลข้าราชการหรือลูกจ้าง", StatusCodes.Status404NotFound); + return Error("ไม่พบข้อมูลข้าราชการหรือลูกจ้าง", StatusCodes.Status404NotFound); } leaveBeginning.LeaveTypeId = req.LeaveTypeId; @@ -288,7 +314,7 @@ namespace BMA.EHR.Leave.Service.Controllers return Success(); } - catch(Exception ex) + catch (Exception ex) { return Error(ex); } diff --git a/BMA.EHR.Leave/Dockerfile b/BMA.EHR.Leave/Dockerfile index 2e851b2c..4d22d69e 100644 --- a/BMA.EHR.Leave/Dockerfile +++ b/BMA.EHR.Leave/Dockerfile @@ -1,8 +1,43 @@ -#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 +# +#FROM mcr.microsoft.com/dotnet/sdk:7.0 AS build +#WORKDIR /src +# +#COPY ["BMA.EHR.Domain/BMA.EHR.Domain.csproj", "BMA.EHR.Domain/"] +#COPY ["BMA.EHR.Application/BMA.EHR.Application.csproj", "BMA.EHR.Application/"] +#COPY ["BMA.EHR.Infrastructure/BMA.EHR.Infrastructure.csproj", "BMA.EHR.Infrastructure/"] +#COPY ["BMA.EHR.Leave/BMA.EHR.Leave.csproj", "BMA.EHR.Leave/"] +# +#RUN dotnet restore "BMA.EHR.Leave/BMA.EHR.Leave.csproj" +#COPY . . +#WORKDIR "/src/BMA.EHR.Leave" +#RUN dotnet build "BMA.EHR.Leave.csproj" -c Release -o /app/build +# +#FROM build AS publish +#RUN dotnet publish "BMA.EHR.Leave.csproj" -c Release -o /app/publish /p:UseAppHost=false +# +#FROM base AS final +#WORKDIR /app +#COPY --from=publish /app/publish . +#ENTRYPOINT ["dotnet", "BMA.EHR.Leave.dll"] +# +# --------------------------- +# Base image สำหรับ runtime +# --------------------------- FROM mcr.microsoft.com/dotnet/aspnet:7.0 AS base -# ตั้งค่า TimeZone ใน Container +# ตั้งค่า TimeZone ENV TZ=Asia/Bangkok RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone @@ -10,23 +45,40 @@ WORKDIR /app EXPOSE 80 EXPOSE 443 +# --------------------------- +# Build stage +# --------------------------- FROM mcr.microsoft.com/dotnet/sdk:7.0 AS build WORKDIR /src +# 1. Copy เฉพาะ .csproj ไฟล์และ restore เพื่อใช้ cache ได้ COPY ["BMA.EHR.Domain/BMA.EHR.Domain.csproj", "BMA.EHR.Domain/"] COPY ["BMA.EHR.Application/BMA.EHR.Application.csproj", "BMA.EHR.Application/"] COPY ["BMA.EHR.Infrastructure/BMA.EHR.Infrastructure.csproj", "BMA.EHR.Infrastructure/"] COPY ["BMA.EHR.Leave/BMA.EHR.Leave.csproj", "BMA.EHR.Leave/"] RUN dotnet restore "BMA.EHR.Leave/BMA.EHR.Leave.csproj" + +# 2. Copy source code ทั้งหมดหลัง restore เพื่อไม่ให้ cache พังง่าย COPY . . + WORKDIR "/src/BMA.EHR.Leave" -RUN dotnet build "BMA.EHR.Leave.csproj" -c Release -o /app/build +# 3. Build แบบ Release +RUN dotnet build "BMA.EHR.Leave.csproj" -c Release -warnaslevel:0 -o /app/build + +# --------------------------- +# Publish stage +# --------------------------- FROM build AS publish -RUN dotnet publish "BMA.EHR.Leave.csproj" -c Release -o /app/publish /p:UseAppHost=false +RUN dotnet publish "BMA.EHR.Leave.csproj" -c Release -warnaslevel:0 -o /app/publish /p:UseAppHost=false +# --------------------------- +# Final runtime image +# --------------------------- FROM base AS final WORKDIR /app + COPY --from=publish /app/publish . + ENTRYPOINT ["dotnet", "BMA.EHR.Leave.dll"] diff --git a/BMA.EHR.Leave/appsettings.json b/BMA.EHR.Leave/appsettings.json index b5ad52fe..2e0134d0 100644 --- a/BMA.EHR.Leave/appsettings.json +++ b/BMA.EHR.Leave/appsettings.json @@ -17,7 +17,7 @@ "ConnectionStrings": { "DefaultConnection": "server=192.168.1.80;user=root;password=adminVM123;port=3306;database=hrms;Convert Zero Datetime=True;Allow User Variables=true;Pooling=True;", "ExamConnection": "server=192.168.1.80;user=root;password=adminVM123;port=3306;database=hrms_exam;Convert Zero Datetime=True;Allow User Variables=true;Pooling=True;", - "LeaveConnection": "server=192.168.1.80;user=root;password=adminVM123;port=3306;database=hrms_leave;Convert Zero Datetime=True;Allow User Variables=true;Pooling=True;", + "LeaveConnection": "server=192.168.1.80;user=root;password=adminVM123;port=3306;database=hrms_leave;Convert Zero Datetime=True;Allow User Variables=true;Pooling=True;" //"DefaultConnection": "server=172.27.17.68;user=user;password=cDldaqkwESWvuZ37Gr0n;port=3306;database=hrms;Convert Zero Datetime=True;Allow User Variables=true;Pooling=True;", //"ExamConnection": "server=172.27.17.68;user=user;password=cDldaqkwESWvuZ37Gr0n;port=3306;database=hrms_exam;Convert Zero Datetime=True;Allow User Variables=true;Pooling=True;", @@ -25,7 +25,7 @@ }, "Jwt": { "Key": "HP-FnQMUj9msHMSD3T9HtdEnphAKoCJLEl85CIqROFI", - "Issuer": "https://id.frappet.synology.me/realms/hrms", + "Issuer": "https://id.frappet.synology.me/realms/hrms" //"Key": "xY2VR-EFvvNPsMs39u8ooVBWQL6mPwrNJOh3koJFTgU", //"Issuer": "https://hrms-id.bangkok.go.th/realms/hrms" },