From 804a4cf85fb9c6b32ee0e436fc9db71dd92ae0fe Mon Sep 17 00:00:00 2001 From: waruneeauy Date: Tue, 23 Dec 2025 20:21:25 +0700 Subject: [PATCH 001/183] add: build leave --- .onedev/build-leave.yml | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 .onedev/build-leave.yml diff --git a/.onedev/build-leave.yml b/.onedev/build-leave.yml new file mode 100644 index 00000000..c89f0c51 --- /dev/null +++ b/.onedev/build-leave.yml @@ -0,0 +1,31 @@ +version: 37 +jobs: + - name: CI for Leave PROD + steps: + - !CheckoutStep + name: checkout code + cloneCredential: !DefaultCredential {} + withLfs: false + withSubmodules: false + condition: ALL_PREVIOUS_STEPS_WERE_SUCCESSFUL + + - !SetBuildVersionStep + name: set build version + buildVersion: "@tag@" + condition: ALL_PREVIOUS_STEPS_WERE_SUCCESSFUL + + - !BuildImageStep + name: build docker image + dockerfile: ./BMA.EHR.Leave/Dockerfile + output: !RegistryOutput + tags: "hrms-git.bangkok.go.th/bma-hrms/hrms-api-leave:@build_version@ hrms-git.bangkok.go.th/bma-hrms/hrms-api-leave:latest" + condition: ALL_PREVIOUS_STEPS_WERE_SUCCESSFUL + + triggers: + - !TagCreateTrigger + tags: leave-prod-* + branches: main + retryCondition: never + maxRetries: 3 + retryDelay: 30 + timeout: 14400 From 8bb31b4e73708195a5d40602502d336ccf7481d7 Mon Sep 17 00:00:00 2001 From: waruneeauy Date: Tue, 23 Dec 2025 20:30:37 +0700 Subject: [PATCH 002/183] updated deploy --- .onedev/build-leave.yml | 117 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 112 insertions(+), 5 deletions(-) diff --git a/.onedev/build-leave.yml b/.onedev/build-leave.yml index c89f0c51..21b0b4a6 100644 --- a/.onedev/build-leave.yml +++ b/.onedev/build-leave.yml @@ -1,5 +1,73 @@ -version: 37 +version: 38 jobs: + - name: CI for Leave UAT + steps: + - !CheckoutStep + name: checkout code + cloneCredential: !DefaultCredential {} + withLfs: false + withSubmodules: false + condition: ALL_PREVIOUS_STEPS_WERE_SUCCESSFUL + - !GenerateChecksumStep + name: generate project checksum + files: "**/*.csproj" + targetFile: checksum + condition: ALL_PREVIOUS_STEPS_WERE_SUCCESSFUL + - !SetupCacheStep + name: set up nuget cache + key: nuget_packages_@file:checksum@ + loadKeys: + - nuget_packages + paths: + - /root/.nuget/packages + uploadStrategy: UPLOAD_IF_NOT_HIT + condition: ALL_PREVIOUS_STEPS_WERE_SUCCESSFUL + - !CommandStep + name: test and analyze + runInContainer: true + image: mcr.microsoft.com/dotnet/sdk + interpreter: !DefaultInterpreter + commands: | + set -e + dotnet tool install -g roslynator.dotnet.cli + dotnet test -l trx --collect:"XPlat Code Coverage" + #/root/.dotnet/tools/roslynator analyze -o roslynator-analysis.xml + useTTY: true + condition: NEVER + - !PublishTRXReportStep + name: publish unit test report + reportName: Unit Test + filePatterns: "**/*.trx" + condition: NEVER + - !PublishCoberturaReportStep + name: publish code coverage report + reportName: Code Coverage + filePatterns: "**/coverage.cobertura.xml" + condition: NEVER + - !PublishRoslynatorReportStep + name: publish code problem report + reportName: Code Problems + filePatterns: roslynator-analysis.xml + failThreshold: HIGH + condition: NEVER + - !SetBuildVersionStep + name: set build version + buildVersion: "@tag@" + condition: ALL_PREVIOUS_STEPS_WERE_SUCCESSFUL + - !BuildImageStep + name: build docker image + dockerfile: ./BMA.EHR.Leave/Dockerfile + output: !RegistryOutput + tags: "@server@/bma-hrms/hrms-api-leave:@build_version@ @server@/bma-hrms/hrms-api-leave:latest" + condition: ALL_PREVIOUS_STEPS_WERE_SUCCESSFUL + triggers: + - !TagCreateTrigger + tags: leave-uat-* + branches: main + retryCondition: never + maxRetries: 3 + retryDelay: 30 + timeout: 14400 - name: CI for Leave PROD steps: - !CheckoutStep @@ -8,19 +76,58 @@ jobs: withLfs: false withSubmodules: false condition: ALL_PREVIOUS_STEPS_WERE_SUCCESSFUL - + - !GenerateChecksumStep + name: generate project checksum + files: "**/*.csproj" + targetFile: checksum + condition: ALL_PREVIOUS_STEPS_WERE_SUCCESSFUL + - !SetupCacheStep + name: set up nuget cache + key: nuget_packages_@file:checksum@ + loadKeys: + - nuget_packages + paths: + - /root/.nuget/packages + uploadStrategy: UPLOAD_IF_NOT_HIT + condition: ALL_PREVIOUS_STEPS_WERE_SUCCESSFUL + - !CommandStep + name: test and analyze + runInContainer: true + image: mcr.microsoft.com/dotnet/sdk + interpreter: !DefaultInterpreter + commands: | + set -e + dotnet tool install -g roslynator.dotnet.cli + dotnet test -l trx --collect:"XPlat Code Coverage" + #/root/.dotnet/tools/roslynator analyze -o roslynator-analysis.xml + useTTY: true + condition: NEVER + - !PublishTRXReportStep + name: publish unit test report + reportName: Unit Test + filePatterns: "**/*.trx" + condition: NEVER + - !PublishCoberturaReportStep + name: publish code coverage report + reportName: Code Coverage + filePatterns: "**/coverage.cobertura.xml" + condition: NEVER + - !PublishRoslynatorReportStep + name: publish code problem report + reportName: Code Problems + filePatterns: roslynator-analysis.xml + failThreshold: HIGH + condition: NEVER - !SetBuildVersionStep name: set build version buildVersion: "@tag@" condition: ALL_PREVIOUS_STEPS_WERE_SUCCESSFUL - - !BuildImageStep name: build docker image dockerfile: ./BMA.EHR.Leave/Dockerfile output: !RegistryOutput - tags: "hrms-git.bangkok.go.th/bma-hrms/hrms-api-leave:@build_version@ hrms-git.bangkok.go.th/bma-hrms/hrms-api-leave:latest" + tags: "@server@/bma-hrms/hrms-api-leave:@build_version@ @server@/bma-hrms/hrms-api-leave:latest" condition: ALL_PREVIOUS_STEPS_WERE_SUCCESSFUL - triggers: - !TagCreateTrigger tags: leave-prod-* From bde1aa21c94ff9d3901fc204242e05922bae88b5 Mon Sep 17 00:00:00 2001 From: waruneeauy Date: Tue, 23 Dec 2025 21:09:15 +0700 Subject: [PATCH 003/183] test build leave --- .onedev/build-leave.yml => .onedev-build-leave.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) rename .onedev/build-leave.yml => .onedev-build-leave.yml (98%) diff --git a/.onedev/build-leave.yml b/.onedev-build-leave.yml similarity index 98% rename from .onedev/build-leave.yml rename to .onedev-build-leave.yml index 21b0b4a6..6b94263e 100644 --- a/.onedev/build-leave.yml +++ b/.onedev-build-leave.yml @@ -1,6 +1,6 @@ version: 38 jobs: - - name: CI for Leave UAT + - name: CI for UAT steps: - !CheckoutStep name: checkout code @@ -68,7 +68,7 @@ jobs: maxRetries: 3 retryDelay: 30 timeout: 14400 - - name: CI for Leave PROD + - name: CI for PROD steps: - !CheckoutStep name: checkout code From 2b1d6852c9a6ffbec4cf9fdfa2dcb2bfe613f323 Mon Sep 17 00:00:00 2001 From: waruneeauy Date: Tue, 23 Dec 2025 21:18:04 +0700 Subject: [PATCH 004/183] add build placement --- .onedev-build-placement.yml | 138 ++++++++++++++++++++++++++++++++++++ 1 file changed, 138 insertions(+) create mode 100644 .onedev-build-placement.yml diff --git a/.onedev-build-placement.yml b/.onedev-build-placement.yml new file mode 100644 index 00000000..dbadf72a --- /dev/null +++ b/.onedev-build-placement.yml @@ -0,0 +1,138 @@ +version: 38 +jobs: + - name: CI for UAT Placement + steps: + - !CheckoutStep + name: checkout code + cloneCredential: !DefaultCredential {} + withLfs: false + withSubmodules: false + condition: ALL_PREVIOUS_STEPS_WERE_SUCCESSFUL + - !GenerateChecksumStep + name: generate project checksum + files: "**/*.csproj" + targetFile: checksum + condition: ALL_PREVIOUS_STEPS_WERE_SUCCESSFUL + - !SetupCacheStep + name: set up nuget cache + key: nuget_packages_@file:checksum@ + loadKeys: + - nuget_packages + paths: + - /root/.nuget/packages + uploadStrategy: UPLOAD_IF_NOT_HIT + condition: ALL_PREVIOUS_STEPS_WERE_SUCCESSFUL + - !CommandStep + name: test and analyze + runInContainer: true + image: mcr.microsoft.com/dotnet/sdk + interpreter: !DefaultInterpreter + commands: | + set -e + dotnet tool install -g roslynator.dotnet.cli + dotnet test -l trx --collect:"XPlat Code Coverage" + #/root/.dotnet/tools/roslynator analyze -o roslynator-analysis.xml + useTTY: true + condition: NEVER + - !PublishTRXReportStep + name: publish unit test report + reportName: Unit Test + filePatterns: "**/*.trx" + condition: NEVER + - !PublishCoberturaReportStep + name: publish code coverage report + reportName: Code Coverage + filePatterns: "**/coverage.cobertura.xml" + condition: NEVER + - !PublishRoslynatorReportStep + name: publish code problem report + reportName: Code Problems + filePatterns: roslynator-analysis.xml + failThreshold: HIGH + condition: NEVER + - !SetBuildVersionStep + name: set build version + buildVersion: "@tag@" + condition: ALL_PREVIOUS_STEPS_WERE_SUCCESSFUL + - !BuildImageStep + name: build docker image + dockerfile: ./BMA.EHR.Placement.Service/Dockerfile + output: !RegistryOutput + tags: "@server@/bma-hrms/hrms-api-placement:@build_version@ @server@/bma-hrms/hrms-api-placement:latest" + condition: ALL_PREVIOUS_STEPS_WERE_SUCCESSFUL + triggers: + - !TagCreateTrigger + tags: placement-uat-* + branches: main + retryCondition: never + maxRetries: 3 + retryDelay: 30 + timeout: 14400 + - name: CI for PROD Placement + steps: + - !CheckoutStep + name: checkout code + cloneCredential: !DefaultCredential {} + withLfs: false + withSubmodules: false + condition: ALL_PREVIOUS_STEPS_WERE_SUCCESSFUL + - !GenerateChecksumStep + name: generate project checksum + files: "**/*.csproj" + targetFile: checksum + condition: ALL_PREVIOUS_STEPS_WERE_SUCCESSFUL + - !SetupCacheStep + name: set up nuget cache + key: nuget_packages_@file:checksum@ + loadKeys: + - nuget_packages + paths: + - /root/.nuget/packages + uploadStrategy: UPLOAD_IF_NOT_HIT + condition: ALL_PREVIOUS_STEPS_WERE_SUCCESSFUL + - !CommandStep + name: test and analyze + runInContainer: true + image: mcr.microsoft.com/dotnet/sdk + interpreter: !DefaultInterpreter + commands: | + set -e + dotnet tool install -g roslynator.dotnet.cli + dotnet test -l trx --collect:"XPlat Code Coverage" + #/root/.dotnet/tools/roslynator analyze -o roslynator-analysis.xml + useTTY: true + condition: NEVER + - !PublishTRXReportStep + name: publish unit test report + reportName: Unit Test + filePatterns: "**/*.trx" + condition: NEVER + - !PublishCoberturaReportStep + name: publish code coverage report + reportName: Code Coverage + filePatterns: "**/coverage.cobertura.xml" + condition: NEVER + - !PublishRoslynatorReportStep + name: publish code problem report + reportName: Code Problems + filePatterns: roslynator-analysis.xml + failThreshold: HIGH + condition: NEVER + - !SetBuildVersionStep + name: set build version + buildVersion: "@tag@" + condition: ALL_PREVIOUS_STEPS_WERE_SUCCESSFUL + - !BuildImageStep + name: build docker image + dockerfile: ./BMA.EHR.Placement.Service/Dockerfile + output: !RegistryOutput + tags: "@server@/bma-hrms/hrms-api-placement:@build_version@ @server@/bma-hrms/hrms-api-placement:latest" + condition: ALL_PREVIOUS_STEPS_WERE_SUCCESSFUL + triggers: + - !TagCreateTrigger + tags: placement-prod-* + branches: main + retryCondition: never + maxRetries: 3 + retryDelay: 30 + timeout: 14400 From d10c86e0cbe2e0d9949d52b64d7d06aae79fbedd Mon Sep 17 00:00:00 2001 From: waruneeauy Date: Tue, 23 Dec 2025 21:50:04 +0700 Subject: [PATCH 005/183] add: build docker hub --- .../workflows/dockerhub-release-leave.yaml | 90 ++++++++++++ .onedev-build-leave.yml | 138 ------------------ .onedev-build-placement.yml | 138 ------------------ 3 files changed, 90 insertions(+), 276 deletions(-) create mode 100644 .github/workflows/dockerhub-release-leave.yaml delete mode 100644 .onedev-build-leave.yml delete mode 100644 .onedev-build-placement.yml diff --git a/.github/workflows/dockerhub-release-leave.yaml b/.github/workflows/dockerhub-release-leave.yaml new file mode 100644 index 00000000..e78ce73d --- /dev/null +++ b/.github/workflows/dockerhub-release-leave.yaml @@ -0,0 +1,90 @@ +name: DockerHub Release - Leave Service +run-name: DockerHub Release - Leave Service by ${{ github.actor }} +on: + push: + tags: + - "leave-[0-9]+.[0-9]+.[0-9]+" + branches: + - main + - develop + workflow_dispatch: + inputs: + IMAGE_VER: + description: 'Image version (e.g., latest, v1.0.0)' + required: false + default: 'latest' + +env: + DOCKERHUB_REGISTRY: docker.io + IMAGE_NAME: hrms-api-leave + +jobs: + release-to-dockerhub: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set output tags + id: vars + run: echo "tag=${GITHUB_REF#refs/*/}" >> $GITHUB_OUTPUT + + - name: Generate version + id: gen_ver + run: | + if [[ $GITHUB_REF == 'refs/tags/'* ]]; then + IMAGE_VER=${{ steps.vars.outputs.tag }} + elif [[ $GITHUB_REF == 'refs/heads/'* ]]; then + BRANCH_NAME=${GITHUB_REF#refs/heads/} + IMAGE_VER="${BRANCH_NAME}-latest" + else + IMAGE_VER=${{ github.event.inputs.IMAGE_VER }} + fi + if [[ $IMAGE_VER == '' ]]; then + IMAGE_VER='test-vBeta' + fi + echo "image_ver=${IMAGE_VER}" >> $GITHUB_OUTPUT + echo "Generated version: ${IMAGE_VER}" + + - name: Display version + run: | + echo "Git Ref: $GITHUB_REF" + echo "Image Version: ${{ steps.gen_ver.outputs.image_ver }}" + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Login to Docker Hub + uses: docker/login-action@v3 + with: + registry: ${{env.DOCKERHUB_REGISTRY}} + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Extract metadata for Docker + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ secrets.DOCKERHUB_USERNAME }}/${{ env.IMAGE_NAME }} + tags: | + type=ref,event=tag + type=ref,event=branch + type=raw,value=${{ steps.gen_ver.outputs.image_ver }} + type=raw,value=latest,enable={{is_default_branch}} + + - name: Build and push Docker image + uses: docker/build-push-action@v5 + with: + context: . + file: BMA.EHR.Leave/Dockerfile + platforms: linux/amd64 + push: true + tags: | + ${{ secrets.DOCKERHUB_USERNAME }}/${{ env.IMAGE_NAME }}:${{ steps.gen_ver.outputs.image_ver }} + ${{ secrets.DOCKERHUB_USERNAME }}/${{ env.IMAGE_NAME }}:latest + labels: ${{ steps.meta.outputs.labels }} + cache-from: type=gha + cache-to: type=gha,mode=max + + - name: Image digest + run: echo "Image pushed with digest ${{ steps.build.outputs.digest }}" diff --git a/.onedev-build-leave.yml b/.onedev-build-leave.yml deleted file mode 100644 index 6b94263e..00000000 --- a/.onedev-build-leave.yml +++ /dev/null @@ -1,138 +0,0 @@ -version: 38 -jobs: - - name: CI for UAT - steps: - - !CheckoutStep - name: checkout code - cloneCredential: !DefaultCredential {} - withLfs: false - withSubmodules: false - condition: ALL_PREVIOUS_STEPS_WERE_SUCCESSFUL - - !GenerateChecksumStep - name: generate project checksum - files: "**/*.csproj" - targetFile: checksum - condition: ALL_PREVIOUS_STEPS_WERE_SUCCESSFUL - - !SetupCacheStep - name: set up nuget cache - key: nuget_packages_@file:checksum@ - loadKeys: - - nuget_packages - paths: - - /root/.nuget/packages - uploadStrategy: UPLOAD_IF_NOT_HIT - condition: ALL_PREVIOUS_STEPS_WERE_SUCCESSFUL - - !CommandStep - name: test and analyze - runInContainer: true - image: mcr.microsoft.com/dotnet/sdk - interpreter: !DefaultInterpreter - commands: | - set -e - dotnet tool install -g roslynator.dotnet.cli - dotnet test -l trx --collect:"XPlat Code Coverage" - #/root/.dotnet/tools/roslynator analyze -o roslynator-analysis.xml - useTTY: true - condition: NEVER - - !PublishTRXReportStep - name: publish unit test report - reportName: Unit Test - filePatterns: "**/*.trx" - condition: NEVER - - !PublishCoberturaReportStep - name: publish code coverage report - reportName: Code Coverage - filePatterns: "**/coverage.cobertura.xml" - condition: NEVER - - !PublishRoslynatorReportStep - name: publish code problem report - reportName: Code Problems - filePatterns: roslynator-analysis.xml - failThreshold: HIGH - condition: NEVER - - !SetBuildVersionStep - name: set build version - buildVersion: "@tag@" - condition: ALL_PREVIOUS_STEPS_WERE_SUCCESSFUL - - !BuildImageStep - name: build docker image - dockerfile: ./BMA.EHR.Leave/Dockerfile - output: !RegistryOutput - tags: "@server@/bma-hrms/hrms-api-leave:@build_version@ @server@/bma-hrms/hrms-api-leave:latest" - condition: ALL_PREVIOUS_STEPS_WERE_SUCCESSFUL - triggers: - - !TagCreateTrigger - tags: leave-uat-* - branches: main - retryCondition: never - maxRetries: 3 - retryDelay: 30 - timeout: 14400 - - name: CI for PROD - steps: - - !CheckoutStep - name: checkout code - cloneCredential: !DefaultCredential {} - withLfs: false - withSubmodules: false - condition: ALL_PREVIOUS_STEPS_WERE_SUCCESSFUL - - !GenerateChecksumStep - name: generate project checksum - files: "**/*.csproj" - targetFile: checksum - condition: ALL_PREVIOUS_STEPS_WERE_SUCCESSFUL - - !SetupCacheStep - name: set up nuget cache - key: nuget_packages_@file:checksum@ - loadKeys: - - nuget_packages - paths: - - /root/.nuget/packages - uploadStrategy: UPLOAD_IF_NOT_HIT - condition: ALL_PREVIOUS_STEPS_WERE_SUCCESSFUL - - !CommandStep - name: test and analyze - runInContainer: true - image: mcr.microsoft.com/dotnet/sdk - interpreter: !DefaultInterpreter - commands: | - set -e - dotnet tool install -g roslynator.dotnet.cli - dotnet test -l trx --collect:"XPlat Code Coverage" - #/root/.dotnet/tools/roslynator analyze -o roslynator-analysis.xml - useTTY: true - condition: NEVER - - !PublishTRXReportStep - name: publish unit test report - reportName: Unit Test - filePatterns: "**/*.trx" - condition: NEVER - - !PublishCoberturaReportStep - name: publish code coverage report - reportName: Code Coverage - filePatterns: "**/coverage.cobertura.xml" - condition: NEVER - - !PublishRoslynatorReportStep - name: publish code problem report - reportName: Code Problems - filePatterns: roslynator-analysis.xml - failThreshold: HIGH - condition: NEVER - - !SetBuildVersionStep - name: set build version - buildVersion: "@tag@" - condition: ALL_PREVIOUS_STEPS_WERE_SUCCESSFUL - - !BuildImageStep - name: build docker image - dockerfile: ./BMA.EHR.Leave/Dockerfile - output: !RegistryOutput - tags: "@server@/bma-hrms/hrms-api-leave:@build_version@ @server@/bma-hrms/hrms-api-leave:latest" - condition: ALL_PREVIOUS_STEPS_WERE_SUCCESSFUL - triggers: - - !TagCreateTrigger - tags: leave-prod-* - branches: main - retryCondition: never - maxRetries: 3 - retryDelay: 30 - timeout: 14400 diff --git a/.onedev-build-placement.yml b/.onedev-build-placement.yml deleted file mode 100644 index dbadf72a..00000000 --- a/.onedev-build-placement.yml +++ /dev/null @@ -1,138 +0,0 @@ -version: 38 -jobs: - - name: CI for UAT Placement - steps: - - !CheckoutStep - name: checkout code - cloneCredential: !DefaultCredential {} - withLfs: false - withSubmodules: false - condition: ALL_PREVIOUS_STEPS_WERE_SUCCESSFUL - - !GenerateChecksumStep - name: generate project checksum - files: "**/*.csproj" - targetFile: checksum - condition: ALL_PREVIOUS_STEPS_WERE_SUCCESSFUL - - !SetupCacheStep - name: set up nuget cache - key: nuget_packages_@file:checksum@ - loadKeys: - - nuget_packages - paths: - - /root/.nuget/packages - uploadStrategy: UPLOAD_IF_NOT_HIT - condition: ALL_PREVIOUS_STEPS_WERE_SUCCESSFUL - - !CommandStep - name: test and analyze - runInContainer: true - image: mcr.microsoft.com/dotnet/sdk - interpreter: !DefaultInterpreter - commands: | - set -e - dotnet tool install -g roslynator.dotnet.cli - dotnet test -l trx --collect:"XPlat Code Coverage" - #/root/.dotnet/tools/roslynator analyze -o roslynator-analysis.xml - useTTY: true - condition: NEVER - - !PublishTRXReportStep - name: publish unit test report - reportName: Unit Test - filePatterns: "**/*.trx" - condition: NEVER - - !PublishCoberturaReportStep - name: publish code coverage report - reportName: Code Coverage - filePatterns: "**/coverage.cobertura.xml" - condition: NEVER - - !PublishRoslynatorReportStep - name: publish code problem report - reportName: Code Problems - filePatterns: roslynator-analysis.xml - failThreshold: HIGH - condition: NEVER - - !SetBuildVersionStep - name: set build version - buildVersion: "@tag@" - condition: ALL_PREVIOUS_STEPS_WERE_SUCCESSFUL - - !BuildImageStep - name: build docker image - dockerfile: ./BMA.EHR.Placement.Service/Dockerfile - output: !RegistryOutput - tags: "@server@/bma-hrms/hrms-api-placement:@build_version@ @server@/bma-hrms/hrms-api-placement:latest" - condition: ALL_PREVIOUS_STEPS_WERE_SUCCESSFUL - triggers: - - !TagCreateTrigger - tags: placement-uat-* - branches: main - retryCondition: never - maxRetries: 3 - retryDelay: 30 - timeout: 14400 - - name: CI for PROD Placement - steps: - - !CheckoutStep - name: checkout code - cloneCredential: !DefaultCredential {} - withLfs: false - withSubmodules: false - condition: ALL_PREVIOUS_STEPS_WERE_SUCCESSFUL - - !GenerateChecksumStep - name: generate project checksum - files: "**/*.csproj" - targetFile: checksum - condition: ALL_PREVIOUS_STEPS_WERE_SUCCESSFUL - - !SetupCacheStep - name: set up nuget cache - key: nuget_packages_@file:checksum@ - loadKeys: - - nuget_packages - paths: - - /root/.nuget/packages - uploadStrategy: UPLOAD_IF_NOT_HIT - condition: ALL_PREVIOUS_STEPS_WERE_SUCCESSFUL - - !CommandStep - name: test and analyze - runInContainer: true - image: mcr.microsoft.com/dotnet/sdk - interpreter: !DefaultInterpreter - commands: | - set -e - dotnet tool install -g roslynator.dotnet.cli - dotnet test -l trx --collect:"XPlat Code Coverage" - #/root/.dotnet/tools/roslynator analyze -o roslynator-analysis.xml - useTTY: true - condition: NEVER - - !PublishTRXReportStep - name: publish unit test report - reportName: Unit Test - filePatterns: "**/*.trx" - condition: NEVER - - !PublishCoberturaReportStep - name: publish code coverage report - reportName: Code Coverage - filePatterns: "**/coverage.cobertura.xml" - condition: NEVER - - !PublishRoslynatorReportStep - name: publish code problem report - reportName: Code Problems - filePatterns: roslynator-analysis.xml - failThreshold: HIGH - condition: NEVER - - !SetBuildVersionStep - name: set build version - buildVersion: "@tag@" - condition: ALL_PREVIOUS_STEPS_WERE_SUCCESSFUL - - !BuildImageStep - name: build docker image - dockerfile: ./BMA.EHR.Placement.Service/Dockerfile - output: !RegistryOutput - tags: "@server@/bma-hrms/hrms-api-placement:@build_version@ @server@/bma-hrms/hrms-api-placement:latest" - condition: ALL_PREVIOUS_STEPS_WERE_SUCCESSFUL - triggers: - - !TagCreateTrigger - tags: placement-prod-* - branches: main - retryCondition: never - maxRetries: 3 - retryDelay: 30 - timeout: 14400 From c87e467a6ef3bc24e1f5efd3662d9e1ad17dcab1 Mon Sep 17 00:00:00 2001 From: waruneeauy Date: Tue, 23 Dec 2025 23:32:02 +0700 Subject: [PATCH 006/183] change build to docker hub --- .../workflows/dockerhub-release-checkin.yaml | 72 +++++++ .../workflows/dockerhub-release-command.yaml | 72 +++++++ .../dockerhub-release-discipline.yaml | 72 +++++++ .../workflows/dockerhub-release-insignia.yaml | 72 +++++++ .../workflows/dockerhub-release-leave.yaml | 10 +- .../dockerhub-release-placement.yaml | 72 +++++++ .../workflows/dockerhub-release-report.yaml | 72 +++++++ .../dockerhub-release-retirement.yaml | 72 +++++++ .github/workflows/release_Retirement.yaml | 6 +- .../workflows/release_checkin_consumer.yaml | 6 +- .github/workflows/release_command.yaml | 6 +- .github/workflows/release_discipline.yaml | 6 +- .github/workflows/release_insignia.yaml | 6 +- .github/workflows/release_leave.yaml | 6 +- .github/workflows/release_placement.yaml | 6 +- .github/workflows/release_report.yaml | 196 +++++++++--------- 16 files changed, 628 insertions(+), 124 deletions(-) create mode 100644 .github/workflows/dockerhub-release-checkin.yaml create mode 100644 .github/workflows/dockerhub-release-command.yaml create mode 100644 .github/workflows/dockerhub-release-discipline.yaml create mode 100644 .github/workflows/dockerhub-release-insignia.yaml create mode 100644 .github/workflows/dockerhub-release-placement.yaml create mode 100644 .github/workflows/dockerhub-release-report.yaml create mode 100644 .github/workflows/dockerhub-release-retirement.yaml diff --git a/.github/workflows/dockerhub-release-checkin.yaml b/.github/workflows/dockerhub-release-checkin.yaml new file mode 100644 index 00000000..9e7c4e23 --- /dev/null +++ b/.github/workflows/dockerhub-release-checkin.yaml @@ -0,0 +1,72 @@ +name: DockerHub Release - CheckIn Consumer +run-name: DockerHub Release - CheckIn Consumer by ${{ github.actor }} +on: + push: + tags: + - "checkin-[0-9]+.[0-9]+.[0-9]+" + workflow_dispatch: + inputs: + IMAGE_VER: + description: "Image version (e.g., latest, v1.0.0)" + required: false + default: "latest" + +env: + DOCKERHUB_REGISTRY: docker.io + IMAGE_NAME: hrms-api-checkin + +jobs: + release-to-dockerhub: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set output tags + id: vars + run: echo "tag=${GITHUB_REF#refs/*/}" >> $GITHUB_OUTPUT + + - name: Generate version + id: gen_ver + run: | + if [[ $GITHUB_REF == 'refs/tags/'* ]]; then + IMAGE_VER=${{ steps.vars.outputs.tag }} + else + IMAGE_VER=${{ github.event.inputs.IMAGE_VER }} + fi + if [[ $IMAGE_VER == '' ]]; then + IMAGE_VER='test-vBeta' + fi + echo "image_ver=${IMAGE_VER}" >> $GITHUB_OUTPUT + echo "Generated version: ${IMAGE_VER}" + + - name: Display version + run: | + echo "Git Ref: $GITHUB_REF" + echo "Image Version: ${{ steps.gen_ver.outputs.image_ver }}" + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Login to Docker Hub + uses: docker/login-action@v3 + with: + registry: ${{env.DOCKERHUB_REGISTRY}} + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Build and push Docker image + uses: docker/build-push-action@v5 + with: + context: . + file: BMA.EHR.CheckInConsumer/Dockerfile + platforms: linux/amd64 + push: true + tags: | + ${{ secrets.DOCKERHUB_USERNAME }}/${{ env.IMAGE_NAME }}:${{ steps.gen_ver.outputs.image_ver }} + ${{ secrets.DOCKERHUB_USERNAME }}/${{ env.IMAGE_NAME }}:latest + labels: | + org.opencontainers.image.title=BMA EHR CheckIn Consumer + org.opencontainers.image.description=HRMS CheckIn Consumer Service + cache-from: type=gha + cache-to: type=gha,mode=max diff --git a/.github/workflows/dockerhub-release-command.yaml b/.github/workflows/dockerhub-release-command.yaml new file mode 100644 index 00000000..33d6b330 --- /dev/null +++ b/.github/workflows/dockerhub-release-command.yaml @@ -0,0 +1,72 @@ +name: DockerHub Release - Command Service +run-name: DockerHub Release - Command Service by ${{ github.actor }} +on: + push: + tags: + - "command-[0-9]+.[0-9]+.[0-9]+" + workflow_dispatch: + inputs: + IMAGE_VER: + description: "Image version (e.g., latest, v1.0.0)" + required: false + default: "latest" + +env: + DOCKERHUB_REGISTRY: docker.io + IMAGE_NAME: hrms-api-command + +jobs: + release-to-dockerhub: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set output tags + id: vars + run: echo "tag=${GITHUB_REF#refs/*/}" >> $GITHUB_OUTPUT + + - name: Generate version + id: gen_ver + run: | + if [[ $GITHUB_REF == 'refs/tags/'* ]]; then + IMAGE_VER=${{ steps.vars.outputs.tag }} + else + IMAGE_VER=${{ github.event.inputs.IMAGE_VER }} + fi + if [[ $IMAGE_VER == '' ]]; then + IMAGE_VER='test-vBeta' + fi + echo "image_ver=${IMAGE_VER}" >> $GITHUB_OUTPUT + echo "Generated version: ${IMAGE_VER}" + + - name: Display version + run: | + echo "Git Ref: $GITHUB_REF" + echo "Image Version: ${{ steps.gen_ver.outputs.image_ver }}" + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Login to Docker Hub + uses: docker/login-action@v3 + with: + registry: ${{env.DOCKERHUB_REGISTRY}} + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Build and push Docker image + uses: docker/build-push-action@v5 + with: + context: . + file: BMA.EHR.Command.Service/Dockerfile + platforms: linux/amd64 + push: true + tags: | + ${{ secrets.DOCKERHUB_USERNAME }}/${{ env.IMAGE_NAME }}:${{ steps.gen_ver.outputs.image_ver }} + ${{ secrets.DOCKERHUB_USERNAME }}/${{ env.IMAGE_NAME }}:latest + labels: | + org.opencontainers.image.title=BMA EHR Command Service + org.opencontainers.image.description=HRMS Command API Service + cache-from: type=gha + cache-to: type=gha,mode=max diff --git a/.github/workflows/dockerhub-release-discipline.yaml b/.github/workflows/dockerhub-release-discipline.yaml new file mode 100644 index 00000000..96096a54 --- /dev/null +++ b/.github/workflows/dockerhub-release-discipline.yaml @@ -0,0 +1,72 @@ +name: DockerHub Release - Discipline Service +run-name: DockerHub Release - Discipline Service by ${{ github.actor }} +on: + push: + tags: + - "discipline-[0-9]+.[0-9]+.[0-9]+" + workflow_dispatch: + inputs: + IMAGE_VER: + description: "Image version (e.g., latest, v1.0.0)" + required: false + default: "latest" + +env: + DOCKERHUB_REGISTRY: docker.io + IMAGE_NAME: hrms-api-discipline + +jobs: + release-to-dockerhub: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set output tags + id: vars + run: echo "tag=${GITHUB_REF#refs/*/}" >> $GITHUB_OUTPUT + + - name: Generate version + id: gen_ver + run: | + if [[ $GITHUB_REF == 'refs/tags/'* ]]; then + IMAGE_VER=${{ steps.vars.outputs.tag }} + else + IMAGE_VER=${{ github.event.inputs.IMAGE_VER }} + fi + if [[ $IMAGE_VER == '' ]]; then + IMAGE_VER='test-vBeta' + fi + echo "image_ver=${IMAGE_VER}" >> $GITHUB_OUTPUT + echo "Generated version: ${IMAGE_VER}" + + - name: Display version + run: | + echo "Git Ref: $GITHUB_REF" + echo "Image Version: ${{ steps.gen_ver.outputs.image_ver }}" + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Login to Docker Hub + uses: docker/login-action@v3 + with: + registry: ${{env.DOCKERHUB_REGISTRY}} + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Build and push Docker image + uses: docker/build-push-action@v5 + with: + context: . + file: BMA.EHR.Discipline.Service/Dockerfile + platforms: linux/amd64 + push: true + tags: | + ${{ secrets.DOCKERHUB_USERNAME }}/${{ env.IMAGE_NAME }}:${{ steps.gen_ver.outputs.image_ver }} + ${{ secrets.DOCKERHUB_USERNAME }}/${{ env.IMAGE_NAME }}:latest + labels: | + org.opencontainers.image.title=BMA EHR Discipline Service + org.opencontainers.image.description=HRMS Discipline API Service + cache-from: type=gha + cache-to: type=gha,mode=max diff --git a/.github/workflows/dockerhub-release-insignia.yaml b/.github/workflows/dockerhub-release-insignia.yaml new file mode 100644 index 00000000..81dae954 --- /dev/null +++ b/.github/workflows/dockerhub-release-insignia.yaml @@ -0,0 +1,72 @@ +name: DockerHub Release - Insignia Service +run-name: DockerHub Release - Insignia Service by ${{ github.actor }} +on: + push: + tags: + - "insignia-[0-9]+.[0-9]+.[0-9]+" + workflow_dispatch: + inputs: + IMAGE_VER: + description: "Image version (e.g., latest, v1.0.0)" + required: false + default: "latest" + +env: + DOCKERHUB_REGISTRY: docker.io + IMAGE_NAME: hrms-api-insignia + +jobs: + release-to-dockerhub: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set output tags + id: vars + run: echo "tag=${GITHUB_REF#refs/*/}" >> $GITHUB_OUTPUT + + - name: Generate version + id: gen_ver + run: | + if [[ $GITHUB_REF == 'refs/tags/'* ]]; then + IMAGE_VER=${{ steps.vars.outputs.tag }} + else + IMAGE_VER=${{ github.event.inputs.IMAGE_VER }} + fi + if [[ $IMAGE_VER == '' ]]; then + IMAGE_VER='test-vBeta' + fi + echo "image_ver=${IMAGE_VER}" >> $GITHUB_OUTPUT + echo "Generated version: ${IMAGE_VER}" + + - name: Display version + run: | + echo "Git Ref: $GITHUB_REF" + echo "Image Version: ${{ steps.gen_ver.outputs.image_ver }}" + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Login to Docker Hub + uses: docker/login-action@v3 + with: + registry: ${{env.DOCKERHUB_REGISTRY}} + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Build and push Docker image + uses: docker/build-push-action@v5 + with: + context: . + file: BMA.EHR.Insignia/Dockerfile + platforms: linux/amd64 + push: true + tags: | + ${{ secrets.DOCKERHUB_USERNAME }}/${{ env.IMAGE_NAME }}:${{ steps.gen_ver.outputs.image_ver }} + ${{ secrets.DOCKERHUB_USERNAME }}/${{ env.IMAGE_NAME }}:latest + labels: | + org.opencontainers.image.title=BMA EHR Insignia Service + org.opencontainers.image.description=HRMS Insignia API Service + cache-from: type=gha + cache-to: type=gha,mode=max diff --git a/.github/workflows/dockerhub-release-leave.yaml b/.github/workflows/dockerhub-release-leave.yaml index e78ce73d..b5a3afe2 100644 --- a/.github/workflows/dockerhub-release-leave.yaml +++ b/.github/workflows/dockerhub-release-leave.yaml @@ -4,15 +4,15 @@ on: push: tags: - "leave-[0-9]+.[0-9]+.[0-9]+" - branches: - - main - - develop + # branches: + # - main + # - develop workflow_dispatch: inputs: IMAGE_VER: - description: 'Image version (e.g., latest, v1.0.0)' + description: "Image version (e.g., latest, v1.0.0)" required: false - default: 'latest' + default: "latest" env: DOCKERHUB_REGISTRY: docker.io diff --git a/.github/workflows/dockerhub-release-placement.yaml b/.github/workflows/dockerhub-release-placement.yaml new file mode 100644 index 00000000..74fa4471 --- /dev/null +++ b/.github/workflows/dockerhub-release-placement.yaml @@ -0,0 +1,72 @@ +name: DockerHub Release - Placement Service +run-name: DockerHub Release - Placement Service by ${{ github.actor }} +on: + push: + tags: + - "placement-[0-9]+.[0-9]+.[0-9]+" + workflow_dispatch: + inputs: + IMAGE_VER: + description: "Image version (e.g., latest, v1.0.0)" + required: false + default: "latest" + +env: + DOCKERHUB_REGISTRY: docker.io + IMAGE_NAME: hrms-api-placement + +jobs: + release-to-dockerhub: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set output tags + id: vars + run: echo "tag=${GITHUB_REF#refs/*/}" >> $GITHUB_OUTPUT + + - name: Generate version + id: gen_ver + run: | + if [[ $GITHUB_REF == 'refs/tags/'* ]]; then + IMAGE_VER=${{ steps.vars.outputs.tag }} + else + IMAGE_VER=${{ github.event.inputs.IMAGE_VER }} + fi + if [[ $IMAGE_VER == '' ]]; then + IMAGE_VER='test-vBeta' + fi + echo "image_ver=${IMAGE_VER}" >> $GITHUB_OUTPUT + echo "Generated version: ${IMAGE_VER}" + + - name: Display version + run: | + echo "Git Ref: $GITHUB_REF" + echo "Image Version: ${{ steps.gen_ver.outputs.image_ver }}" + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Login to Docker Hub + uses: docker/login-action@v3 + with: + registry: ${{env.DOCKERHUB_REGISTRY}} + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Build and push Docker image + uses: docker/build-push-action@v5 + with: + context: . + file: BMA.EHR.Placement.Service/Dockerfile + platforms: linux/amd64 + push: true + tags: | + ${{ secrets.DOCKERHUB_USERNAME }}/${{ env.IMAGE_NAME }}:${{ steps.gen_ver.outputs.image_ver }} + ${{ secrets.DOCKERHUB_USERNAME }}/${{ env.IMAGE_NAME }}:latest + labels: | + org.opencontainers.image.title=BMA EHR Placement Service + org.opencontainers.image.description=HRMS Placement API Service + cache-from: type=gha + cache-to: type=gha,mode=max diff --git a/.github/workflows/dockerhub-release-report.yaml b/.github/workflows/dockerhub-release-report.yaml new file mode 100644 index 00000000..132b218b --- /dev/null +++ b/.github/workflows/dockerhub-release-report.yaml @@ -0,0 +1,72 @@ +name: DockerHub Release - Report Service +run-name: DockerHub Release - Report Service by ${{ github.actor }} +on: + push: + tags: + - "report-[0-9]+.[0-9]+.[0-9]+" + workflow_dispatch: + inputs: + IMAGE_VER: + description: "Image version (e.g., latest, v1.0.0)" + required: false + default: "latest" + +env: + DOCKERHUB_REGISTRY: docker.io + IMAGE_NAME: hrms-api-report + +jobs: + release-to-dockerhub: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set output tags + id: vars + run: echo "tag=${GITHUB_REF#refs/*/}" >> $GITHUB_OUTPUT + + - name: Generate version + id: gen_ver + run: | + if [[ $GITHUB_REF == 'refs/tags/'* ]]; then + IMAGE_VER=${{ steps.vars.outputs.tag }} + else + IMAGE_VER=${{ github.event.inputs.IMAGE_VER }} + fi + if [[ $IMAGE_VER == '' ]]; then + IMAGE_VER='test-vBeta' + fi + echo "image_ver=${IMAGE_VER}" >> $GITHUB_OUTPUT + echo "Generated version: ${IMAGE_VER}" + + - name: Display version + run: | + echo "Git Ref: $GITHUB_REF" + echo "Image Version: ${{ steps.gen_ver.outputs.image_ver }}" + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Login to Docker Hub + uses: docker/login-action@v3 + with: + registry: ${{env.DOCKERHUB_REGISTRY}} + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Build and push Docker image + uses: docker/build-push-action@v5 + with: + context: . + file: BMA.EHR.Report.Service/Dockerfile + platforms: linux/amd64 + push: true + tags: | + ${{ secrets.DOCKERHUB_USERNAME }}/${{ env.IMAGE_NAME }}:${{ steps.gen_ver.outputs.image_ver }} + ${{ secrets.DOCKERHUB_USERNAME }}/${{ env.IMAGE_NAME }}:latest + labels: | + org.opencontainers.image.title=BMA EHR Report Service + org.opencontainers.image.description=HRMS Report API Service + cache-from: type=gha + cache-to: type=gha,mode=max diff --git a/.github/workflows/dockerhub-release-retirement.yaml b/.github/workflows/dockerhub-release-retirement.yaml new file mode 100644 index 00000000..38e107e6 --- /dev/null +++ b/.github/workflows/dockerhub-release-retirement.yaml @@ -0,0 +1,72 @@ +name: DockerHub Release - Retirement Service +run-name: DockerHub Release - Retirement Service by ${{ github.actor }} +on: + push: + tags: + - "retirement-[0-9]+.[0-9]+.[0-9]+" + workflow_dispatch: + inputs: + IMAGE_VER: + description: "Image version (e.g., latest, v1.0.0)" + required: false + default: "latest" + +env: + DOCKERHUB_REGISTRY: docker.io + IMAGE_NAME: hrms-api-retirement + +jobs: + release-to-dockerhub: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set output tags + id: vars + run: echo "tag=${GITHUB_REF#refs/*/}" >> $GITHUB_OUTPUT + + - name: Generate version + id: gen_ver + run: | + if [[ $GITHUB_REF == 'refs/tags/'* ]]; then + IMAGE_VER=${{ steps.vars.outputs.tag }} + else + IMAGE_VER=${{ github.event.inputs.IMAGE_VER }} + fi + if [[ $IMAGE_VER == '' ]]; then + IMAGE_VER='test-vBeta' + fi + echo "image_ver=${IMAGE_VER}" >> $GITHUB_OUTPUT + echo "Generated version: ${IMAGE_VER}" + + - name: Display version + run: | + echo "Git Ref: $GITHUB_REF" + echo "Image Version: ${{ steps.gen_ver.outputs.image_ver }}" + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Login to Docker Hub + uses: docker/login-action@v3 + with: + registry: ${{env.DOCKERHUB_REGISTRY}} + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Build and push Docker image + uses: docker/build-push-action@v5 + with: + context: . + file: BMA.EHR.Retirement.Service/Dockerfile + platforms: linux/amd64 + push: true + tags: | + ${{ secrets.DOCKERHUB_USERNAME }}/${{ env.IMAGE_NAME }}:${{ steps.gen_ver.outputs.image_ver }} + ${{ secrets.DOCKERHUB_USERNAME }}/${{ env.IMAGE_NAME }}:latest + labels: | + org.opencontainers.image.title=BMA EHR Retirement Service + org.opencontainers.image.description=HRMS Retirement API Service + cache-from: type=gha + cache-to: type=gha,mode=max diff --git a/.github/workflows/release_Retirement.yaml b/.github/workflows/release_Retirement.yaml index 6cdceae0..3fd5bedf 100644 --- a/.github/workflows/release_Retirement.yaml +++ b/.github/workflows/release_Retirement.yaml @@ -1,9 +1,9 @@ name: release-dev run-name: release-dev ${{ github.actor }} on: - push: - tags: - - "retirement-[0-9]+.[0-9]+.[0-9]+" + # push: + # tags: + # - "retirement-[0-9]+.[0-9]+.[0-9]+" workflow_dispatch: env: REGISTRY: docker.frappet.com diff --git a/.github/workflows/release_checkin_consumer.yaml b/.github/workflows/release_checkin_consumer.yaml index 31b015e6..f9081264 100644 --- a/.github/workflows/release_checkin_consumer.yaml +++ b/.github/workflows/release_checkin_consumer.yaml @@ -1,9 +1,9 @@ name: release-dev run-name: release-dev ${{ github.actor }} on: - push: - tags: - - "consumer-[0-9]+.[0-9]+.[0-9]+" + # push: + # tags: + # - "consumer-[0-9]+.[0-9]+.[0-9]+" workflow_dispatch: env: REGISTRY: docker.frappet.com diff --git a/.github/workflows/release_command.yaml b/.github/workflows/release_command.yaml index 462ed688..dba3012e 100644 --- a/.github/workflows/release_command.yaml +++ b/.github/workflows/release_command.yaml @@ -1,9 +1,9 @@ name: release-dev run-name: release-dev ${{ github.actor }} on: - push: - tags: - - "command-[0-9]+.[0-9]+.[0-9]+" + # push: + # tags: + # - "command-[0-9]+.[0-9]+.[0-9]+" workflow_dispatch: env: REGISTRY: docker.frappet.com diff --git a/.github/workflows/release_discipline.yaml b/.github/workflows/release_discipline.yaml index 0e8737f4..f4cb42da 100644 --- a/.github/workflows/release_discipline.yaml +++ b/.github/workflows/release_discipline.yaml @@ -1,9 +1,9 @@ name: release-dev run-name: release-dev ${{ github.actor }} on: - push: - tags: - - "discipline-[0-9]+.[0-9]+.[0-9]+" + # push: + # tags: + # - "discipline-[0-9]+.[0-9]+.[0-9]+" workflow_dispatch: env: REGISTRY: docker.frappet.com diff --git a/.github/workflows/release_insignia.yaml b/.github/workflows/release_insignia.yaml index 124d1e9c..af497b37 100644 --- a/.github/workflows/release_insignia.yaml +++ b/.github/workflows/release_insignia.yaml @@ -1,9 +1,9 @@ name: release-dev run-name: release-dev ${{ github.actor }} on: - push: - tags: - - "insignia-[0-9]+.[0-9]+.[0-9]+" + # push: + # tags: + # - "insignia-[0-9]+.[0-9]+.[0-9]+" workflow_dispatch: env: REGISTRY: docker.frappet.com diff --git a/.github/workflows/release_leave.yaml b/.github/workflows/release_leave.yaml index 4b361f5f..9b5e0014 100644 --- a/.github/workflows/release_leave.yaml +++ b/.github/workflows/release_leave.yaml @@ -1,9 +1,9 @@ name: release-dev run-name: release-dev ${{ github.actor }} on: - push: - tags: - - "leave-[0-9]+.[0-9]+.[0-9]+" + # push: + # tags: + # - "leave-[0-9]+.[0-9]+.[0-9]+" workflow_dispatch: env: REGISTRY: docker.frappet.com diff --git a/.github/workflows/release_placement.yaml b/.github/workflows/release_placement.yaml index d9c2f266..377cbaf4 100644 --- a/.github/workflows/release_placement.yaml +++ b/.github/workflows/release_placement.yaml @@ -1,9 +1,9 @@ name: release-dev run-name: release-dev ${{ github.actor }} on: - push: - tags: - - "placement-[0-9]+.[0-9]+.[0-9]+" + # push: + # tags: + # - "placement-[0-9]+.[0-9]+.[0-9]+" workflow_dispatch: env: REGISTRY: docker.frappet.com diff --git a/.github/workflows/release_report.yaml b/.github/workflows/release_report.yaml index 9ba5a7c4..e529729b 100644 --- a/.github/workflows/release_report.yaml +++ b/.github/workflows/release_report.yaml @@ -1,107 +1,107 @@ name: release-dev run-name: release-dev ${{ github.actor }} on: - push: - tags: - - "reportv2-[0-9]+.[0-9]+.[0-9]+" + # push: + # tags: + # - "reportv2-[0-9]+.[0-9]+.[0-9]+" workflow_dispatch: env: - REGISTRY: docker.frappet.com - IMAGE_NAME: ehr/bma-ehr-report-v2-service - DEPLOY_HOST: frappet.com - DEPLOY_PORT: 10102 - # COMPOSE_PATH: /home/frappet/docker/bma-ehr - COMPOSE_PATH: /home/frappet/docker/bma/bma-ehr-report-v2 - TOKEN_LINE: uxuK5hDzS2DsoC5piJBrWRLiz8GgY7iMZZldOWsDDF0 + REGISTRY: docker.frappet.com + IMAGE_NAME: ehr/bma-ehr-report-v2-service + DEPLOY_HOST: frappet.com + DEPLOY_PORT: 10102 + # COMPOSE_PATH: /home/frappet/docker/bma-ehr + COMPOSE_PATH: /home/frappet/docker/bma/bma-ehr-report-v2 + TOKEN_LINE: uxuK5hDzS2DsoC5piJBrWRLiz8GgY7iMZZldOWsDDF0 jobs: - # act workflow_dispatch -W .github/workflows/release_report.yaml --input IMAGE_VER=latest -s DOCKER_USER=admin -s DOCKER_PASS=FPTadmin2357 -s SSH_PASSWORD=FPTadmin2357 - release-dev: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - name: Set output tags - id: vars - run: echo "tag=${GITHUB_REF#refs/*/}" >> $GITHUB_OUTPUT - - name: Gen Version - id: gen_ver - run: | - if [[ $GITHUB_REF == 'refs/tags/'* ]]; then - IMAGE_VER=${{ steps.vars.outputs.tag }} - else - IMAGE_VER=${{ github.event.inputs.IMAGE_VER }} - fi - if [[ $IMAGE_VER == '' ]]; then - IMAGE_VER='test-vBeta' - fi - echo '::set-output name=image_ver::'$IMAGE_VER - - name: Check Version - run: | - echo $GITHUB_REF - echo ${{ steps.gen_ver.outputs.image_ver }} - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v2 - - name: Login in to registry - uses: docker/login-action@v2 - with: - registry: ${{env.REGISTRY}} - username: ${{secrets.DOCKER_USER}} - password: ${{secrets.DOCKER_PASS}} - - name: Build and load local docker image - uses: docker/build-push-action@v3 - with: - context: . - platforms: linux/amd64 - file: BMA.EHR.Report.Service/Dockerfile - push: true - tags: ${{env.REGISTRY}}/${{env.IMAGE_NAME}}:${{ steps.gen_ver.outputs.image_ver }},${{env.REGISTRY}}/${{env.IMAGE_NAME}}:latest + # act workflow_dispatch -W .github/workflows/release_report.yaml --input IMAGE_VER=latest -s DOCKER_USER=admin -s DOCKER_PASS=FPTadmin2357 -s SSH_PASSWORD=FPTadmin2357 + release-dev: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Set output tags + id: vars + run: echo "tag=${GITHUB_REF#refs/*/}" >> $GITHUB_OUTPUT + - name: Gen Version + id: gen_ver + run: | + if [[ $GITHUB_REF == 'refs/tags/'* ]]; then + IMAGE_VER=${{ steps.vars.outputs.tag }} + else + IMAGE_VER=${{ github.event.inputs.IMAGE_VER }} + fi + if [[ $IMAGE_VER == '' ]]; then + IMAGE_VER='test-vBeta' + fi + echo '::set-output name=image_ver::'$IMAGE_VER + - name: Check Version + run: | + echo $GITHUB_REF + echo ${{ steps.gen_ver.outputs.image_ver }} + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v2 + - name: Login in to registry + uses: docker/login-action@v2 + with: + registry: ${{env.REGISTRY}} + username: ${{secrets.DOCKER_USER}} + password: ${{secrets.DOCKER_PASS}} + - name: Build and load local docker image + uses: docker/build-push-action@v3 + with: + context: . + platforms: linux/amd64 + file: BMA.EHR.Report.Service/Dockerfile + push: true + tags: ${{env.REGISTRY}}/${{env.IMAGE_NAME}}:${{ steps.gen_ver.outputs.image_ver }},${{env.REGISTRY}}/${{env.IMAGE_NAME}}:latest - - name: Reload docker compose - uses: appleboy/ssh-action@v0.1.8 - with: - host: ${{env.DEPLOY_HOST}} - username: frappet - password: ${{ secrets.SSH_PASSWORD }} - port: ${{env.DEPLOY_PORT}} - script: | - cd "${{env.COMPOSE_PATH}}" - 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: Reload docker compose + uses: appleboy/ssh-action@v0.1.8 + with: + host: ${{env.DEPLOY_HOST}} + username: frappet + password: ${{ secrets.SSH_PASSWORD }} + port: ${{env.DEPLOY_PORT}} + script: | + cd "${{env.COMPOSE_PATH}}" + 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 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 7caf1cc8d639008afa21ad93b4535e7571741c6e Mon Sep 17 00:00:00 2001 From: Suphonchai Phoonsawat Date: Fri, 26 Dec 2025 09:51:51 +0700 Subject: [PATCH 007/183] add date range parameters to GetProfileByAdminRole and GetEmployeeByAdminRole methods --- .../Repositories/UserProfileRepository.cs | 12 ++++++++---- BMA.EHR.Leave/Controllers/LeaveReportController.cs | 12 ++++++------ 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/BMA.EHR.Application/Repositories/UserProfileRepository.cs b/BMA.EHR.Application/Repositories/UserProfileRepository.cs index 15e06ca9..344e6116 100644 --- a/BMA.EHR.Application/Repositories/UserProfileRepository.cs +++ b/BMA.EHR.Application/Repositories/UserProfileRepository.cs @@ -520,7 +520,7 @@ namespace BMA.EHR.Application.Repositories } } - public async Task> GetProfileByAdminRole(string? accessToken, int? node, string? nodeId, string role, string? revisionId, int? reqNode, string? reqNodeId) + public async Task> GetProfileByAdminRole(string? accessToken, int? node, string? nodeId, string role, string? revisionId, int? reqNode, string? reqNodeId,DateTime? startDate, DateTime? endDate) { try { @@ -533,7 +533,9 @@ namespace BMA.EHR.Application.Repositories role = role, revisionId = revisionId, reqNode = reqNode, - reqNodeId = reqNodeId + reqNodeId = reqNodeId, + startDate = startDate, + endDate = endDate }; var profiles = new List(); @@ -686,7 +688,7 @@ namespace BMA.EHR.Application.Repositories } } - public async Task> GetEmployeeByAdminRole(string? accessToken, int? node, string? nodeId, string role, string? revisionId, int? reqNode, string? reqNodeId) + public async Task> GetEmployeeByAdminRole(string? accessToken, int? node, string? nodeId, string role, string? revisionId, int? reqNode, string? reqNodeId,DateTime? startDate, DateTime? endDate) { try { @@ -699,7 +701,9 @@ namespace BMA.EHR.Application.Repositories role = role, revisionId = revisionId, reqNode = reqNode, - reqNodeId = reqNodeId + reqNodeId = reqNodeId, + startDate = startDate, + endDate = endDate }; var profiles = new List(); diff --git a/BMA.EHR.Leave/Controllers/LeaveReportController.cs b/BMA.EHR.Leave/Controllers/LeaveReportController.cs index 2c001072..bb9538f6 100644 --- a/BMA.EHR.Leave/Controllers/LeaveReportController.cs +++ b/BMA.EHR.Leave/Controllers/LeaveReportController.cs @@ -1029,11 +1029,11 @@ namespace BMA.EHR.Leave.Service.Controllers if (type.Trim().ToUpper() == "OFFICER") { - profile = await _userProfileRepository.GetProfileByAdminRole(AccessToken, profileAdmin?.Node, nodeId, role, req.revisionId, req.node, req.nodeId); + profile = await _userProfileRepository.GetProfileByAdminRole(AccessToken, profileAdmin?.Node, nodeId, role, req.revisionId, req.node, req.nodeId, req.StartDate.Date, req.EndDate.Date); } else { - profile = await _userProfileRepository.GetEmployeeByAdminRole(AccessToken, profileAdmin?.Node, nodeId, role, req.revisionId, req.node, req.nodeId); + profile = await _userProfileRepository.GetEmployeeByAdminRole(AccessToken, profileAdmin?.Node, nodeId, role, req.revisionId, req.node, req.nodeId, req.StartDate.Date, req.EndDate.Date); } // get leave day var leaveDays = await _leaveRequestRepository.GetSumApproveLeaveByTypeAndRange(req.StartDate, req.EndDate); @@ -1658,11 +1658,11 @@ namespace BMA.EHR.Leave.Service.Controllers if (type.Trim().ToUpper() == "OFFICER") { - profile = await _userProfileRepository.GetProfileByAdminRole(AccessToken, profileAdmin?.Node, nodeId, role, req.revisionId, req.node, req.nodeId); + profile = await _userProfileRepository.GetProfileByAdminRole(AccessToken, profileAdmin?.Node, nodeId, role, req.revisionId, req.node, req.nodeId, req.StartDate.Date, req.EndDate.Date); } else { - profile = await _userProfileRepository.GetEmployeeByAdminRole(AccessToken, profileAdmin?.Node, nodeId, role, req.revisionId, req.node, req.nodeId); + profile = await _userProfileRepository.GetEmployeeByAdminRole(AccessToken, profileAdmin?.Node, nodeId, role, req.revisionId, req.node, req.nodeId, req.StartDate.Date, req.EndDate.Date); } var date = req.StartDate.Date; @@ -1999,11 +1999,11 @@ namespace BMA.EHR.Leave.Service.Controllers if (type.Trim().ToUpper() == "OFFICER") { - profile = await _userProfileRepository.GetProfileByAdminRole(AccessToken, profileAdmin?.Node, nodeId, role, req.revisionId, req.node, req.nodeId); + profile = await _userProfileRepository.GetProfileByAdminRole(AccessToken, profileAdmin?.Node, nodeId, role, req.revisionId, req.node, req.nodeId, req.StartDate.Date, req.EndDate.Date); } else { - profile = await _userProfileRepository.GetEmployeeByAdminRole(AccessToken, profileAdmin?.Node, nodeId, role, req.revisionId, req.node, req.nodeId); + profile = await _userProfileRepository.GetEmployeeByAdminRole(AccessToken, profileAdmin?.Node, nodeId, role, req.revisionId, req.node, req.nodeId, req.StartDate.Date, req.EndDate.Date); } // Child กรองตามที่ fe ส่งมาอีกชั้น if ((role == "ROOT" || role == "OWNER" || role == "CHILD" || role == "PARENT" || role == "BROTHER") /*&& req.node > profileAdmin?.Node*/) From 8973e1b78f51aba0e2d08984eb692c83bf5c11a4 Mon Sep 17 00:00:00 2001 From: kittapath <> Date: Tue, 30 Dec 2025 23:55:03 +0700 Subject: [PATCH 008/183] leave v2 --- .../Repositories/UserProfileRepository.cs | 43 +++++++++++++++++-- .../Controllers/LeaveReportController.cs | 12 +++--- 2 files changed, 46 insertions(+), 9 deletions(-) diff --git a/BMA.EHR.Application/Repositories/UserProfileRepository.cs b/BMA.EHR.Application/Repositories/UserProfileRepository.cs index 344e6116..150aeded 100644 --- a/BMA.EHR.Application/Repositories/UserProfileRepository.cs +++ b/BMA.EHR.Application/Repositories/UserProfileRepository.cs @@ -157,7 +157,7 @@ namespace BMA.EHR.Application.Repositories return null; } - catch(Exception ex) + catch (Exception ex) { throw; } @@ -520,7 +520,7 @@ namespace BMA.EHR.Application.Repositories } } - public async Task> GetProfileByAdminRole(string? accessToken, int? node, string? nodeId, string role, string? revisionId, int? reqNode, string? reqNodeId,DateTime? startDate, DateTime? endDate) + public async Task> GetProfileByAdminRole(string? accessToken, int? node, string? nodeId, string role, string? revisionId, int? reqNode, string? reqNodeId, DateTime? startDate, DateTime? endDate) { try { @@ -556,6 +556,43 @@ namespace BMA.EHR.Application.Repositories } } + public async Task> GetProfileByAdminRolev2(string? accessToken, int? node, string? nodeId, string role, string? revisionId, int? reqNode, string? reqNodeId, DateTime? startDate, DateTime? endDate) + { + try + { + var apiPath = $"{_configuration["API"]}/org/dotnet/officer-by-admin-rolev2"; + var apiKey = _configuration["API_KEY"]; + var body = new + { + node = node, + nodeId = nodeId, + role = role, + // revisionId = revisionId, + reqNode = reqNode, + reqNodeId = reqNodeId, + // startDate = startDate, + // endDate = endDate + date = endDate + }; + + var profiles = new List(); + + var apiResult = await PostExternalAPIAsync(apiPath, accessToken, body, apiKey); + if (apiResult != null) + { + var raw = JsonConvert.DeserializeObject(apiResult); + if (raw != null) + return raw.Result; + } + + return null; + } + catch + { + throw; + } + } + public async Task> GetProfileWithKeycloakAllOfficerRetireFilterAndRevision(string? accessToken, int? node, string? nodeId, bool isAll, bool? isRetirement, string? revisionId) { try @@ -688,7 +725,7 @@ namespace BMA.EHR.Application.Repositories } } - public async Task> GetEmployeeByAdminRole(string? accessToken, int? node, string? nodeId, string role, string? revisionId, int? reqNode, string? reqNodeId,DateTime? startDate, DateTime? endDate) + public async Task> GetEmployeeByAdminRole(string? accessToken, int? node, string? nodeId, string role, string? revisionId, int? reqNode, string? reqNodeId, DateTime? startDate, DateTime? endDate) { try { diff --git a/BMA.EHR.Leave/Controllers/LeaveReportController.cs b/BMA.EHR.Leave/Controllers/LeaveReportController.cs index bb9538f6..3b4e81fd 100644 --- a/BMA.EHR.Leave/Controllers/LeaveReportController.cs +++ b/BMA.EHR.Leave/Controllers/LeaveReportController.cs @@ -238,7 +238,7 @@ namespace BMA.EHR.Leave.Service.Controllers LeaveTotal = data.LeaveTotal.ToString().ToThaiNumber(), //data.LeaveStartDate.DiffDay(data.LeaveEndDate).ToString().ToThaiNumber(), leaveAddress = data.LeaveAddress.ToThaiNumber(), leaveNumber = data.LeaveNumber.ToThaiNumber(), - + approve = approveResult, leaveStatus = data.LeaveStatus != null && data.LeaveStatus!.ToUpper() == "APPROVE" ? "🗹 อนุญาต ☐ ไม่อนุญาต" @@ -1658,7 +1658,7 @@ namespace BMA.EHR.Leave.Service.Controllers if (type.Trim().ToUpper() == "OFFICER") { - profile = await _userProfileRepository.GetProfileByAdminRole(AccessToken, profileAdmin?.Node, nodeId, role, req.revisionId, req.node, req.nodeId, req.StartDate.Date, req.EndDate.Date); + profile = await _userProfileRepository.GetProfileByAdminRolev2(AccessToken, profileAdmin?.Node, nodeId, role, req.revisionId, req.node, req.nodeId, req.StartDate.Date, req.EndDate.Date); } else { @@ -1778,13 +1778,13 @@ namespace BMA.EHR.Leave.Service.Controllers var leaveRangeEnd = leaveReq.LeaveRangeEnd == null ? "" : leaveReq.LeaveRangeEnd.ToUpper(); - if(leaveRange != leaveRangeEnd) + if (leaveRange != leaveRangeEnd) { if (leaveRangeEnd == "MORNING") remarkStr += " - ครึ่งวันเช้า"; else if (leaveRangeEnd == "AFTERNOON") remarkStr += " - ครึ่งวันบ่าย"; - } + } break; default: remarkStr += leaveReq.Type.Name; @@ -2114,13 +2114,13 @@ namespace BMA.EHR.Leave.Service.Controllers // remarkStr += "ครึ่งวันบ่าย"; var leaveRangeEnd = leaveReq.LeaveRangeEnd == null ? "" : leaveReq.LeaveRangeEnd.ToUpper(); - if(leaveRange != leaveRangeEnd) + if (leaveRange != leaveRangeEnd) { if (leaveRangeEnd == "MORNING") remarkStr += " - ครึ่งวันเช้า"; else if (leaveRangeEnd == "AFTERNOON") remarkStr += " - ครึ่งวันบ่าย"; - } + } break; default: remarkStr += leaveReq.Type.Name; From 72b9c6e31eb5c3409fe77b7d6c40b1ff97e09690 Mon Sep 17 00:00:00 2001 From: waruneeauy Date: Wed, 31 Dec 2025 08:02:16 +0700 Subject: [PATCH 009/183] remove build forgejo, move to dev branch --- .forgejo/workflows/build-checkin.yml | 83 -------------------- .forgejo/workflows/build-command_backup.yml | 61 --------------- .forgejo/workflows/build-discipline.yml | 83 -------------------- .forgejo/workflows/build-insignia.yml | 83 -------------------- .forgejo/workflows/build-leave.yml | 83 -------------------- .forgejo/workflows/build-placement.yml | 83 -------------------- .forgejo/workflows/build-report_backup.yml | 84 --------------------- .forgejo/workflows/build-retirement.yml | 83 -------------------- .forgejo/workflows/build.yml | 49 ------------ .forgejo/workflows/deploy.yml | 29 ------- 10 files changed, 721 deletions(-) delete mode 100644 .forgejo/workflows/build-checkin.yml delete mode 100644 .forgejo/workflows/build-command_backup.yml delete mode 100644 .forgejo/workflows/build-discipline.yml delete mode 100644 .forgejo/workflows/build-insignia.yml delete mode 100644 .forgejo/workflows/build-leave.yml delete mode 100644 .forgejo/workflows/build-placement.yml delete mode 100644 .forgejo/workflows/build-report_backup.yml delete mode 100644 .forgejo/workflows/build-retirement.yml delete mode 100644 .forgejo/workflows/build.yml delete mode 100644 .forgejo/workflows/deploy.yml diff --git a/.forgejo/workflows/build-checkin.yml b/.forgejo/workflows/build-checkin.yml deleted file mode 100644 index 3971e9df..00000000 --- a/.forgejo/workflows/build-checkin.yml +++ /dev/null @@ -1,83 +0,0 @@ -name: Build & Deploy Checkin Service - -on: - push: - tags: - - "checkin-dev[0-9]+.[0-9]+.[0-9]+" - - "checkin-dev[0-9]+.[0-9]+.[0-9]+*" - workflow_dispatch: - -env: - REGISTRY: ${{ vars.CONTAINER_REGISTRY }} - REGISTRY_USERNAME: ${{ vars.CONTAINER_REGISTRY_USERNAME }} - REGISTRY_PASSWORD: ${{ secrets.CONTAINER_REGISTRY_PASSWORD }} - CONTAINER_IMAGE_NAME: ${{ vars.CONTAINER_REGISTRY }}/${{ vars.CONTAINER_IMAGE_OWNER }} - IMAGE_VERSION: latest - SERVICE_NAME: hrms-api-checkin - DISCORD_WEBHOOK: ${{ vars.DISCORD_WEBHOOK }} - -jobs: - build: - runs-on: ubuntu-latest - steps: - - name: Checkout repository - uses: actions/checkout@v4 - - - name: Tag Version - shell: bash - run: | - if [[ "${{ github.event_name }}" == "push" ]]; then - VERSION=$(echo "${{ github.ref_name }}" | sed 's/checkin-dev//g') - echo "IMAGE_VERSION=${VERSION}" >> $GITHUB_ENV - else - echo "IMAGE_VERSION=${{ env.IMAGE_VERSION }}-${{ github.run_number }}" >> $GITHUB_ENV - fi - - name: Login in to registry - uses: docker/login-action@v3 - with: - registry: ${{ env.REGISTRY }} - username: ${{ env.REGISTRY_USERNAME }} - password: ${{ env.REGISTRY_PASSWORD }} - - - name: Build and push docker image - uses: docker/build-push-action@v5 - with: - platforms: linux/amd64 - context: . - file: ./BMA.EHR.CheckInConsumer/Dockerfile - tags: ${{ env.CONTAINER_IMAGE_NAME }}/${{ env.SERVICE_NAME }}:latest,${{ env.CONTAINER_IMAGE_NAME }}/${{ env.SERVICE_NAME }}:${{ env.IMAGE_VERSION }} - push: true - - - name: Remote Deploy - uses: appleboy/ssh-action@v1.2.1 - with: - host: ${{ vars.SSH_DEPLOY_HOST }} - port: ${{ vars.SSH_DEPLOY_PORT }} - username: ${{ secrets.SSH_DEPLOY_USER }} - password: ${{ secrets.SSH_DEPLOY_PASSWORD }} - script: | - cd ~/repo - ./replace-env.sh API_CHECKIN "${{ env.IMAGE_VERSION }}" - ./deploy.sh ${{ env.SERVICE_NAME }} - - - name: Discord Notification - if: always() - run: | - STATUS="${{ job.status == 'success' && '✅ Success' || '❌ Failed' }}" - COLOR="${{ job.status == 'success' && '3066993' || '15158332' }}" - TIMESTAMP=$(date -u +%Y-%m-%dT%H:%M:%SZ) - curl -H "Content-Type: application/json" \ - -X POST \ - -d "{ - \"embeds\": [{ - \"title\": \"$STATUS\", - \"description\": \"**Build & Deploy**\\n- Image: \`${{ env.CONTAINER_IMAGE_NAME }}/${{ env.SERVICE_NAME }}\`\\n- Version: \`${{ env.IMAGE_VERSION }}\`\\n- By: \`${{ gitea.actor }}\`\", - \"color\": $COLOR, - \"footer\": { - \"text\": \"Release Notification\", - \"icon_url\": \"https://example.com/success-icon.png\" - }, - \"timestamp\": \"$TIMESTAMP\" - }] - }" \ - ${{ env.DISCORD_WEBHOOK }} diff --git a/.forgejo/workflows/build-command_backup.yml b/.forgejo/workflows/build-command_backup.yml deleted file mode 100644 index 0884c70b..00000000 --- a/.forgejo/workflows/build-command_backup.yml +++ /dev/null @@ -1,61 +0,0 @@ -# name: Build - -# on: -# push: -# tags: -# - "command-dev[0-9]+.[0-9]+.[0-9]+" -# - "command-dev[0-9]+.[0-9]+.[0-9]+*" -# workflow_dispatch: - -# env: -# REGISTRY: ${{ vars.CONTAINER_REGISTRY }} -# REGISTRY_USERNAME: ${{ vars.CONTAINER_REGISTRY_USERNAME }} -# REGISTRY_PASSWORD: ${{ secrets.CONTAINER_REGISTRY_PASSWORD }} -# CONTAINER_IMAGE_NAME: ${{ vars.CONTAINER_REGISTRY }}/${{ vars.CONTAINER_IMAGE_OWNER }} -# IMAGE_VERSION: build -# DISCORD_WEBHOOK: ${{ vars.DISCORD_WEBHOOK }} - -# jobs: -# build: -# runs-on: ubuntu-latest -# steps: -# - name: Checkout repository -# uses: actions/checkout@v4 -# - name: Set up Docker Buildx -# uses: docker/setup-buildx-action@v2 -# with: -# config-inline: | -# [registry."${{ env.REGISTRY }}"] -# ca=["/etc/ssl/certs/ca-certificates.crt"] -# - name: Tag Version -# run: | -# if [[ "${{ github.event_name }}" == "push" ]]; then -# echo "IMAGE_VERSION=${{ github.ref_name }}" | sed 's/command-dev//g' >> $GITHUB_ENV -# else -# echo "IMAGE_VERSION=${{ env.IMAGE_VERSION }}-${{ github.run_number }}" >> $GITHUB_ENV -# fi -# - name: Login in to registry -# uses: docker/login-action@v2 -# with: -# registry: ${{ env.REGISTRY }} -# username: ${{ env.REGISTRY_USERNAME }} -# password: ${{ env.REGISTRY_PASSWORD }} -# - name: Build and push docker image -# uses: docker/build-push-action@v3 -# with: -# platforms: linux/amd64 -# context: . -# file: ./BMA.EHR.Command.Service/Dockerfile -# tags: ${{ env.CONTAINER_IMAGE_NAME }}/hrms-api-command:latest,${{ env.CONTAINER_IMAGE_NAME }}/hrms-api-command:${{ env.IMAGE_VERSION }} -# push: true -# - name: Remote Deploy -# uses: appleboy/ssh-action@v1.2.1 -# with: -# host: ${{ vars.SSH_DEPLOY_HOST }} -# port: ${{ vars.SSH_DEPLOY_PORT }} -# username: ${{ secrets.SSH_DEPLOY_USER }} -# password: ${{ secrets.SSH_DEPLOY_PASSWORD }} -# script: | -# cd ~/repo -# ./replace-env.sh API_COMMAND "${{ env.IMAGE_VERSION }}" -# ./deploy.sh hrms-api-command diff --git a/.forgejo/workflows/build-discipline.yml b/.forgejo/workflows/build-discipline.yml deleted file mode 100644 index 767d775b..00000000 --- a/.forgejo/workflows/build-discipline.yml +++ /dev/null @@ -1,83 +0,0 @@ -name: Build & Deploy Discipline Service - -on: - push: - tags: - - "discipline-dev[0-9]+.[0-9]+.[0-9]+" - - "discipline-dev[0-9]+.[0-9]+.[0-9]+*" - workflow_dispatch: - -env: - REGISTRY: ${{ vars.CONTAINER_REGISTRY }} - REGISTRY_USERNAME: ${{ vars.CONTAINER_REGISTRY_USERNAME }} - REGISTRY_PASSWORD: ${{ secrets.CONTAINER_REGISTRY_PASSWORD }} - CONTAINER_IMAGE_NAME: ${{ vars.CONTAINER_REGISTRY }}/${{ vars.CONTAINER_IMAGE_OWNER }} - IMAGE_VERSION: latest - SERVICE_NAME: hrms-api-discipline - DISCORD_WEBHOOK: ${{ vars.DISCORD_WEBHOOK }} - -jobs: - build: - runs-on: ubuntu-latest - steps: - - name: Checkout repository - uses: actions/checkout@v4 - - - name: Tag Version - shell: bash - run: | - if [[ "${{ github.event_name }}" == "push" ]]; then - VERSION=$(echo "${{ github.ref_name }}" | sed 's/discipline-dev//g') - echo "IMAGE_VERSION=${VERSION}" >> $GITHUB_ENV - else - echo "IMAGE_VERSION=${{ env.IMAGE_VERSION }}-${{ github.run_number }}" >> $GITHUB_ENV - fi - - name: Login in to registry - uses: docker/login-action@v3 - with: - registry: ${{ env.REGISTRY }} - username: ${{ env.REGISTRY_USERNAME }} - password: ${{ env.REGISTRY_PASSWORD }} - - - name: Build and push docker image - uses: docker/build-push-action@v5 - with: - platforms: linux/amd64 - context: . - file: ./BMA.EHR.Discipline.Service/Dockerfile - tags: ${{ env.CONTAINER_IMAGE_NAME }}/${{ env.SERVICE_NAME }}:latest,${{ env.CONTAINER_IMAGE_NAME }}/${{ env.SERVICE_NAME }}:${{ env.IMAGE_VERSION }} - push: true - - - name: Remote Deploy - uses: appleboy/ssh-action@v1.2.1 - with: - host: ${{ vars.SSH_DEPLOY_HOST }} - port: ${{ vars.SSH_DEPLOY_PORT }} - username: ${{ secrets.SSH_DEPLOY_USER }} - password: ${{ secrets.SSH_DEPLOY_PASSWORD }} - script: | - cd ~/repo - ./replace-env.sh API_DISCIPLINE "${{ env.IMAGE_VERSION }}" - ./deploy.sh ${{ env.SERVICE_NAME }} - - - name: Discord Notification - if: always() - run: | - STATUS="${{ job.status == 'success' && '✅ Success' || '❌ Failed' }}" - COLOR="${{ job.status == 'success' && '3066993' || '15158332' }}" - TIMESTAMP=$(date -u +%Y-%m-%dT%H:%M:%SZ) - curl -H "Content-Type: application/json" \ - -X POST \ - -d "{ - \"embeds\": [{ - \"title\": \"$STATUS\", - \"description\": \"**Build & Deploy**\\n- Image: \`${{ env.CONTAINER_IMAGE_NAME }}/${{ env.SERVICE_NAME }}\`\\n- Version: \`${{ env.IMAGE_VERSION }}\`\\n- By: \`${{ gitea.actor }}\`\", - \"color\": $COLOR, - \"footer\": { - \"text\": \"Release Notification\", - \"icon_url\": \"https://example.com/success-icon.png\" - }, - \"timestamp\": \"$TIMESTAMP\" - }] - }" \ - ${{ env.DISCORD_WEBHOOK }} diff --git a/.forgejo/workflows/build-insignia.yml b/.forgejo/workflows/build-insignia.yml deleted file mode 100644 index 7ddcbdbd..00000000 --- a/.forgejo/workflows/build-insignia.yml +++ /dev/null @@ -1,83 +0,0 @@ -name: Build & Deploy Insignia Service - -on: - push: - tags: - - "insignia-dev[0-9]+.[0-9]+.[0-9]+" - - "insignia-dev[0-9]+.[0-9]+.[0-9]+*" - workflow_dispatch: - -env: - REGISTRY: ${{ vars.CONTAINER_REGISTRY }} - REGISTRY_USERNAME: ${{ vars.CONTAINER_REGISTRY_USERNAME }} - REGISTRY_PASSWORD: ${{ secrets.CONTAINER_REGISTRY_PASSWORD }} - CONTAINER_IMAGE_NAME: ${{ vars.CONTAINER_REGISTRY }}/${{ vars.CONTAINER_IMAGE_OWNER }} - IMAGE_VERSION: latest - SERVICE_NAME: hrms-api-insignia - DISCORD_WEBHOOK: ${{ vars.DISCORD_WEBHOOK }} - -jobs: - build: - runs-on: ubuntu-latest - steps: - - name: Checkout repository - uses: actions/checkout@v4 - - - name: Tag Version - shell: bash - run: | - if [[ "${{ github.event_name }}" == "push" ]]; then - VERSION=$(echo "${{ github.ref_name }}" | sed 's/insignia-dev//g') - echo "IMAGE_VERSION=${VERSION}" >> $GITHUB_ENV - else - echo "IMAGE_VERSION=${{ env.IMAGE_VERSION }}-${{ github.run_number }}" >> $GITHUB_ENV - fi - - name: Login in to registry - uses: docker/login-action@v3 - with: - registry: ${{ env.REGISTRY }} - username: ${{ env.REGISTRY_USERNAME }} - password: ${{ env.REGISTRY_PASSWORD }} - - - name: Build and push docker image - uses: docker/build-push-action@v5 - with: - platforms: linux/amd64 - context: . - file: ./BMA.EHR.Insignia/Dockerfile - tags: ${{ env.CONTAINER_IMAGE_NAME }}/${{ env.SERVICE_NAME }}:latest,${{ env.CONTAINER_IMAGE_NAME }}/${{ env.SERVICE_NAME }}:${{ env.IMAGE_VERSION }} - push: true - - - name: Remote Deploy - uses: appleboy/ssh-action@v1.2.1 - with: - host: ${{ vars.SSH_DEPLOY_HOST }} - port: ${{ vars.SSH_DEPLOY_PORT }} - username: ${{ secrets.SSH_DEPLOY_USER }} - password: ${{ secrets.SSH_DEPLOY_PASSWORD }} - script: | - cd ~/repo - ./replace-env.sh API_INSIGNIA "${{ env.IMAGE_VERSION }}" - ./deploy.sh ${{ env.SERVICE_NAME }} - - - name: Discord Notification - if: always() - run: | - STATUS="${{ job.status == 'success' && '✅ Success' || '❌ Failed' }}" - COLOR="${{ job.status == 'success' && '3066993' || '15158332' }}" - TIMESTAMP=$(date -u +%Y-%m-%dT%H:%M:%SZ) - curl -H "Content-Type: application/json" \ - -X POST \ - -d "{ - \"embeds\": [{ - \"title\": \"$STATUS\", - \"description\": \"**Build & Deploy**\\n- Image: \`${{ env.CONTAINER_IMAGE_NAME }}/${{ env.SERVICE_NAME }}\`\\n- Version: \`${{ env.IMAGE_VERSION }}\`\\n- By: \`${{ gitea.actor }}\`\", - \"color\": $COLOR, - \"footer\": { - \"text\": \"Release Notification\", - \"icon_url\": \"https://example.com/success-icon.png\" - }, - \"timestamp\": \"$TIMESTAMP\" - }] - }" \ - ${{ env.DISCORD_WEBHOOK }} diff --git a/.forgejo/workflows/build-leave.yml b/.forgejo/workflows/build-leave.yml deleted file mode 100644 index b1c9c168..00000000 --- a/.forgejo/workflows/build-leave.yml +++ /dev/null @@ -1,83 +0,0 @@ -name: Build & Deploy Leave Service - -on: - push: - tags: - - "leave-dev[0-9]+.[0-9]+.[0-9]+" - - "leave-dev[0-9]+.[0-9]+.[0-9]+*" - workflow_dispatch: - -env: - REGISTRY: ${{ vars.CONTAINER_REGISTRY }} - REGISTRY_USERNAME: ${{ vars.CONTAINER_REGISTRY_USERNAME }} - REGISTRY_PASSWORD: ${{ secrets.CONTAINER_REGISTRY_PASSWORD }} - CONTAINER_IMAGE_NAME: ${{ vars.CONTAINER_REGISTRY }}/${{ vars.CONTAINER_IMAGE_OWNER }} - IMAGE_VERSION: latest - SERVICE_NAME: hrms-api-leave - DISCORD_WEBHOOK: ${{ vars.DISCORD_WEBHOOK }} - -jobs: - build: - runs-on: ubuntu-latest - steps: - - name: Checkout repository - uses: actions/checkout@v4 - - - name: Tag Version - shell: bash - run: | - if [[ "${{ github.event_name }}" == "push" ]]; then - VERSION=$(echo "${{ github.ref_name }}" | sed 's/leave-dev//g') - echo "IMAGE_VERSION=${VERSION}" >> $GITHUB_ENV - else - echo "IMAGE_VERSION=${{ env.IMAGE_VERSION }}-${{ github.run_number }}" >> $GITHUB_ENV - fi - - name: Login in to registry - uses: docker/login-action@v3 - with: - registry: ${{ env.REGISTRY }} - username: ${{ env.REGISTRY_USERNAME }} - password: ${{ env.REGISTRY_PASSWORD }} - - - name: Build and push docker image - uses: docker/build-push-action@v5 - with: - platforms: linux/amd64 - context: . - file: ./BMA.EHR.Leave/Dockerfile - tags: ${{ env.CONTAINER_IMAGE_NAME }}/${{ env.SERVICE_NAME }}:latest,${{ env.CONTAINER_IMAGE_NAME }}/${{ env.SERVICE_NAME }}:${{ env.IMAGE_VERSION }} - push: true - - - name: Remote Deploy - uses: appleboy/ssh-action@v1.2.1 - with: - host: ${{ vars.SSH_DEPLOY_HOST }} - port: ${{ vars.SSH_DEPLOY_PORT }} - username: ${{ secrets.SSH_DEPLOY_USER }} - password: ${{ secrets.SSH_DEPLOY_PASSWORD }} - script: | - cd ~/repo - ./replace-env.sh API_LEAVE "${{ env.IMAGE_VERSION }}" - ./deploy.sh ${{ env.SERVICE_NAME }} - - - name: Discord Notification - if: always() - run: | - STATUS="${{ job.status == 'success' && '✅ Success' || '❌ Failed' }}" - COLOR="${{ job.status == 'success' && '3066993' || '15158332' }}" - TIMESTAMP=$(date -u +%Y-%m-%dT%H:%M:%SZ) - curl -H "Content-Type: application/json" \ - -X POST \ - -d "{ - \"embeds\": [{ - \"title\": \"$STATUS\", - \"description\": \"**Build & Deploy**\\n- Image: \`${{ env.CONTAINER_IMAGE_NAME }}/${{ env.SERVICE_NAME }}\`\\n- Version: \`${{ env.IMAGE_VERSION }}\`\\n- By: \`${{ gitea.actor }}\`\", - \"color\": $COLOR, - \"footer\": { - \"text\": \"Release Notification\", - \"icon_url\": \"https://example.com/success-icon.png\" - }, - \"timestamp\": \"$TIMESTAMP\" - }] - }" \ - ${{ env.DISCORD_WEBHOOK }} diff --git a/.forgejo/workflows/build-placement.yml b/.forgejo/workflows/build-placement.yml deleted file mode 100644 index 351b1e59..00000000 --- a/.forgejo/workflows/build-placement.yml +++ /dev/null @@ -1,83 +0,0 @@ -name: Build & Deploy Placement Service - -on: - push: - tags: - - "placement-dev[0-9]+.[0-9]+.[0-9]+" - - "placement-dev[0-9]+.[0-9]+.[0-9]+*" - workflow_dispatch: - -env: - REGISTRY: ${{ vars.CONTAINER_REGISTRY }} - REGISTRY_USERNAME: ${{ vars.CONTAINER_REGISTRY_USERNAME }} - REGISTRY_PASSWORD: ${{ secrets.CONTAINER_REGISTRY_PASSWORD }} - CONTAINER_IMAGE_NAME: ${{ vars.CONTAINER_REGISTRY }}/${{ vars.CONTAINER_IMAGE_OWNER }} - IMAGE_VERSION: latest - SERVICE_NAME: hrms-api-placement - DISCORD_WEBHOOK: ${{ vars.DISCORD_WEBHOOK }} - -jobs: - build: - runs-on: ubuntu-latest - steps: - - name: Checkout repository - uses: actions/checkout@v4 - - - name: Tag Version - shell: bash - run: | - if [[ "${{ github.event_name }}" == "push" ]]; then - VERSION=$(echo "${{ github.ref_name }}" | sed 's/placement-dev//g') - echo "IMAGE_VERSION=${VERSION}" >> $GITHUB_ENV - else - echo "IMAGE_VERSION=${{ env.IMAGE_VERSION }}-${{ github.run_number }}" >> $GITHUB_ENV - fi - - name: Login in to registry - uses: docker/login-action@v3 - with: - registry: ${{ env.REGISTRY }} - username: ${{ env.REGISTRY_USERNAME }} - password: ${{ env.REGISTRY_PASSWORD }} - - - name: Build and push docker image - uses: docker/build-push-action@v5 - with: - platforms: linux/amd64 - context: . - file: ./BMA.EHR.Placement.Service/Dockerfile - tags: ${{ env.CONTAINER_IMAGE_NAME }}/${{ env.SERVICE_NAME }}:latest,${{ env.CONTAINER_IMAGE_NAME }}/${{ env.SERVICE_NAME }}:${{ env.IMAGE_VERSION }} - push: true - - - name: Remote Deploy - uses: appleboy/ssh-action@v1.2.1 - with: - host: ${{ vars.SSH_DEPLOY_HOST }} - port: ${{ vars.SSH_DEPLOY_PORT }} - username: ${{ secrets.SSH_DEPLOY_USER }} - password: ${{ secrets.SSH_DEPLOY_PASSWORD }} - script: | - cd ~/repo - ./replace-env.sh API_PLACEMENT "${{ env.IMAGE_VERSION }}" - ./deploy.sh ${{ env.SERVICE_NAME }} - - - name: Discord Notification - if: always() - run: | - STATUS="${{ job.status == 'success' && '✅ Success' || '❌ Failed' }}" - COLOR="${{ job.status == 'success' && '3066993' || '15158332' }}" - TIMESTAMP=$(date -u +%Y-%m-%dT%H:%M:%SZ) - curl -H "Content-Type: application/json" \ - -X POST \ - -d "{ - \"embeds\": [{ - \"title\": \"$STATUS\", - \"description\": \"**Build & Deploy**\\n- Image: \`${{ env.CONTAINER_IMAGE_NAME }}/${{ env.SERVICE_NAME }}\`\\n- Version: \`${{ env.IMAGE_VERSION }}\`\\n- By: \`${{ gitea.actor }}\`\", - \"color\": $COLOR, - \"footer\": { - \"text\": \"Release Notification\", - \"icon_url\": \"https://example.com/success-icon.png\" - }, - \"timestamp\": \"$TIMESTAMP\" - }] - }" \ - ${{ env.DISCORD_WEBHOOK }} diff --git a/.forgejo/workflows/build-report_backup.yml b/.forgejo/workflows/build-report_backup.yml deleted file mode 100644 index 664de47b..00000000 --- a/.forgejo/workflows/build-report_backup.yml +++ /dev/null @@ -1,84 +0,0 @@ -# name: Build - -# on: -# push: -# tags: -# - "report-dev[0-9]+.[0-9]+.[0-9]+" -# - "report-dev[0-9]+.[0-9]+.[0-9]+*" -# workflow_dispatch: - -# env: -# REGISTRY: ${{ vars.CONTAINER_REGISTRY }} -# REGISTRY_USERNAME: ${{ vars.CONTAINER_REGISTRY_USERNAME }} -# REGISTRY_PASSWORD: ${{ secrets.CONTAINER_REGISTRY_PASSWORD }} -# CONTAINER_IMAGE_NAME: ${{ vars.CONTAINER_REGISTRY }}/${{ vars.CONTAINER_IMAGE_OWNER }} -# SERVICE_NAME: hrms-api-report -# IMAGE_VERSION: build -# DISCORD_WEBHOOK: ${{ vars.DISCORD_WEBHOOK }} - -# jobs: -# build: -# runs-on: ubuntu-latest -# steps: -# - name: Checkout repository -# uses: actions/checkout@v4 -# - name: Set up Docker Buildx -# uses: docker/setup-buildx-action@v2 -# with: -# config-inline: | -# [registry."${{ env.REGISTRY }}"] -# ca=["/etc/ssl/certs/ca-certificates.crt"] -# - name: Tag Version -# run: | -# if [[ "${{ github.event_name }}" == "push" ]]; then -# echo "IMAGE_VERSION=${{ github.ref_name }}" | sed 's/report-dev//g' >> $GITHUB_ENV -# else -# echo "IMAGE_VERSION=${{ env.IMAGE_VERSION }}-${{ github.run_number }}" >> $GITHUB_ENV -# fi -# - name: Login in to registry -# uses: docker/login-action@v2 -# with: -# registry: ${{ env.REGISTRY }} -# username: ${{ env.REGISTRY_USERNAME }} -# password: ${{ env.REGISTRY_PASSWORD }} -# - name: Build and push docker image -# uses: docker/build-push-action@v3 -# with: -# platforms: linux/amd64 -# context: . -# file: ./BMA.EHR.Report.Service/Dockerfile -# tags: ${{ env.CONTAINER_IMAGE_NAME }}/${{ env.SERVICE_NAME }}:latest,${{ env.CONTAINER_IMAGE_NAME }}/${{ env.SERVICE_NAME }}:${{ env.IMAGE_VERSION }} -# push: true -# - name: Remote Deploy -# uses: appleboy/ssh-action@v1.2.1 -# with: -# host: ${{ vars.SSH_DEPLOY_HOST }} -# port: ${{ vars.SSH_DEPLOY_PORT }} -# username: ${{ secrets.SSH_DEPLOY_USER }} -# password: ${{ secrets.SSH_DEPLOY_PASSWORD }} -# script: | -# cd ~/repo -# ./replace-env.sh API_REPORT "${{ env.IMAGE_VERSION }}" -# ./deploy.sh ${{ env.SERVICE_NAME }} - -# - name: Discord Notification -# if: always() -# run: | -# STATUS="${{ job.status == 'success' && '✅ Success' || '❌ Failed' }}" -# COLOR="${{ job.status == 'success' && '3066993' || '15158332' }}" -# TIMESTAMP=$(date -u +%Y-%m-%dT%H:%M:%SZ) -# curl -H "Content-Type: application/json" \ -# -X POST \ -# -d "{ -# \"embeds\": [{ -# \"title\": \"$STATUS\", -# \"description\": \"**Build & Deploy**\\n- Image: \`${{ env.CONTAINER_IMAGE_NAME }}/${{ env.SERVICE_NAME }}\`\\n- Version: \`${{ env.IMAGE_VERSION }}\`\\n- By: \`${{ github.actor }}\`\", -# \"color\": $COLOR, -# \"footer\": { -# \"text\": \"Release Notification\", -# \"icon_url\": \"https://example.com/success-icon.png\" -# }, -# \"timestamp\": \"$TIMESTAMP\" -# }] -# }" \ -# ${{ env.DISCORD_WEBHOOK }} diff --git a/.forgejo/workflows/build-retirement.yml b/.forgejo/workflows/build-retirement.yml deleted file mode 100644 index 257f68f2..00000000 --- a/.forgejo/workflows/build-retirement.yml +++ /dev/null @@ -1,83 +0,0 @@ -name: Build & Deploy Retirement Service - -on: - push: - tags: - - "retirement-dev[0-9]+.[0-9]+.[0-9]+" - - "retirement-dev[0-9]+.[0-9]+.[0-9]+*" - workflow_dispatch: - -env: - REGISTRY: ${{ vars.CONTAINER_REGISTRY }} - REGISTRY_USERNAME: ${{ vars.CONTAINER_REGISTRY_USERNAME }} - REGISTRY_PASSWORD: ${{ secrets.CONTAINER_REGISTRY_PASSWORD }} - CONTAINER_IMAGE_NAME: ${{ vars.CONTAINER_REGISTRY }}/${{ vars.CONTAINER_IMAGE_OWNER }} - IMAGE_VERSION: latest - SERVICE_NAME: hrms-api-retirement - DISCORD_WEBHOOK: ${{ vars.DISCORD_WEBHOOK }} - -jobs: - build: - runs-on: ubuntu-latest - steps: - - name: Checkout repository - uses: actions/checkout@v4 - - - name: Tag Version - shell: bash - run: | - if [[ "${{ github.event_name }}" == "push" ]]; then - VERSION=$(echo "${{ github.ref_name }}" | sed 's/retirement-dev//g') - echo "IMAGE_VERSION=${VERSION}" >> $GITHUB_ENV - else - echo "IMAGE_VERSION=${{ env.IMAGE_VERSION }}-${{ github.run_number }}" >> $GITHUB_ENV - fi - - name: Login in to registry - uses: docker/login-action@v3 - with: - registry: ${{ env.REGISTRY }} - username: ${{ env.REGISTRY_USERNAME }} - password: ${{ env.REGISTRY_PASSWORD }} - - - name: Build and push docker image - uses: docker/build-push-action@v5 - with: - platforms: linux/amd64 - context: . - file: ./BMA.EHR.Retirement.Service/Dockerfile - tags: ${{ env.CONTAINER_IMAGE_NAME }}/${{ env.SERVICE_NAME }}:latest,${{ env.CONTAINER_IMAGE_NAME }}/${{ env.SERVICE_NAME }}:${{ env.IMAGE_VERSION }} - push: true - - - name: Remote Deploy - uses: appleboy/ssh-action@v1.2.1 - with: - host: ${{ vars.SSH_DEPLOY_HOST }} - port: ${{ vars.SSH_DEPLOY_PORT }} - username: ${{ secrets.SSH_DEPLOY_USER }} - password: ${{ secrets.SSH_DEPLOY_PASSWORD }} - script: | - cd ~/repo - ./replace-env.sh API_RETIREMENT "${{ env.IMAGE_VERSION }}" - ./deploy.sh ${{ env.SERVICE_NAME }} - - - name: Discord Notification - if: always() - run: | - STATUS="${{ job.status == 'success' && '✅ Success' || '❌ Failed' }}" - COLOR="${{ job.status == 'success' && '3066993' || '15158332' }}" - TIMESTAMP=$(date -u +%Y-%m-%dT%H:%M:%SZ) - curl -H "Content-Type: application/json" \ - -X POST \ - -d "{ - \"embeds\": [{ - \"title\": \"$STATUS\", - \"description\": \"**Build & Deploy**\\n- Image: \`${{ env.CONTAINER_IMAGE_NAME }}/${{ env.SERVICE_NAME }}\`\\n- Version: \`${{ env.IMAGE_VERSION }}\`\\n- By: \`${{ gitea.actor }}\`\", - \"color\": $COLOR, - \"footer\": { - \"text\": \"Release Notification\", - \"icon_url\": \"https://example.com/success-icon.png\" - }, - \"timestamp\": \"$TIMESTAMP\" - }] - }" \ - ${{ env.DISCORD_WEBHOOK }} diff --git a/.forgejo/workflows/build.yml b/.forgejo/workflows/build.yml deleted file mode 100644 index a4f2d2e5..00000000 --- a/.forgejo/workflows/build.yml +++ /dev/null @@ -1,49 +0,0 @@ -name: Build - -on: - push: - tags: - - "leave[0-9]+.[0-9]+.[0-9]+" - - "leave[0-9]+.[0-9]+.[0-9]+*" - workflow_dispatch: - -env: - REGISTRY: ${{ vars.CONTAINER_REGISTRY }} - REGISTRY_USERNAME: ${{ vars.CONTAINER_REGISTRY_USERNAME }} - REGISTRY_PASSWORD: ${{ secrets.CONTAINER_REGISTRY_PASSWORD }} - CONTAINER_IMAGE_NAME: ${{ vars.CONTAINER_REGISTRY }}/${{ vars.CONTAINER_IMAGE_OWNER }} - IMAGE_VERSION: build - -jobs: - build: - runs-on: ubuntu-latest - steps: - - name: Checkout repository - uses: actions/checkout@v4 - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v2 - with: - config-inline: | - [registry."${{ env.REGISTRY }}"] - ca=["/etc/ssl/certs/ca-certificates.crt"] - - name: Tag Version - run: | - if [[ "${{ github.event_name }}" == "push" ]]; then - echo "IMAGE_VERSION=${{ github.ref_name }}" | sed 's/leave//g' >> $GITHUB_ENV - else - echo "IMAGE_VERSION=${{ env.IMAGE_VERSION }}-${{ github.run_number }}" >> $GITHUB_ENV - fi - - name: Login in to registry - uses: docker/login-action@v2 - with: - registry: ${{ env.REGISTRY }} - username: ${{ env.REGISTRY_USERNAME }} - password: ${{ env.REGISTRY_PASSWORD }} - - name: Build and push docker image - uses: docker/build-push-action@v3 - with: - platforms: linux/amd64 - context: . - file: ./BMA.EHR.Leave/Dockerfile - tags: ${{ env.CONTAINER_IMAGE_NAME }}/hrms-leave:latest,${{ env.CONTAINER_IMAGE_NAME }}/hrms-leave:${{ env.IMAGE_VERSION }} - push: true diff --git a/.forgejo/workflows/deploy.yml b/.forgejo/workflows/deploy.yml deleted file mode 100644 index 7a18d4cc..00000000 --- a/.forgejo/workflows/deploy.yml +++ /dev/null @@ -1,29 +0,0 @@ -name: Build - -on: - workflow_dispatch: - inputs: - version: - description: "Version to deploy" - type: string - required: false - default: "latest" - -env: - IMAGE_VERSION: build - -jobs: - deploy: - runs-on: ubuntu-latest - steps: - - name: Remote Deploy - uses: appleboy/ssh-action@v1.2.1 - with: - host: ${{ vars.SSH_DEPLOY_HOST }} - port: ${{ vars.SSH_DEPLOY_PORT }} - username: ${{ secrets.SSH_DEPLOY_USER }} - password: ${{ secrets.SSH_DEPLOY_PASSWORD }} - script: | - cd ~/repo - ./replace-env.sh API_LEAVE "${{ inputs.version }}" - ./deploy.sh hrms-api-leave From 0b0cc53e0748a1b1b2297c072adeddae53228831 Mon Sep 17 00:00:00 2001 From: kittapath <> Date: Wed, 31 Dec 2025 10:07:01 +0700 Subject: [PATCH 010/183] change to v2 --- BMA.EHR.Leave/Controllers/LeaveReportController.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BMA.EHR.Leave/Controllers/LeaveReportController.cs b/BMA.EHR.Leave/Controllers/LeaveReportController.cs index 3b4e81fd..3f9b9cf1 100644 --- a/BMA.EHR.Leave/Controllers/LeaveReportController.cs +++ b/BMA.EHR.Leave/Controllers/LeaveReportController.cs @@ -1999,7 +1999,7 @@ namespace BMA.EHR.Leave.Service.Controllers if (type.Trim().ToUpper() == "OFFICER") { - profile = await _userProfileRepository.GetProfileByAdminRole(AccessToken, profileAdmin?.Node, nodeId, role, req.revisionId, req.node, req.nodeId, req.StartDate.Date, req.EndDate.Date); + profile = await _userProfileRepository.GetProfileByAdminRolev2(AccessToken, profileAdmin?.Node, nodeId, role, req.revisionId, req.node, req.nodeId, req.StartDate.Date, req.EndDate.Date); } else { From cb074e3e25b7e706caed45b1d8fbe1621bf78c08 Mon Sep 17 00:00:00 2001 From: kittapath <> Date: Fri, 2 Jan 2026 21:46:39 +0700 Subject: [PATCH 011/183] find org v2 --- BMA.EHR.Application/Repositories/UserProfileRepository.cs | 5 +++-- BMA.EHR.Leave/Controllers/LeaveReportController.cs | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/BMA.EHR.Application/Repositories/UserProfileRepository.cs b/BMA.EHR.Application/Repositories/UserProfileRepository.cs index 150aeded..7ffdc024 100644 --- a/BMA.EHR.Application/Repositories/UserProfileRepository.cs +++ b/BMA.EHR.Application/Repositories/UserProfileRepository.cs @@ -574,6 +574,7 @@ namespace BMA.EHR.Application.Repositories // endDate = endDate date = endDate }; + Console.WriteLine(body); var profiles = new List(); @@ -585,7 +586,7 @@ namespace BMA.EHR.Application.Repositories return raw.Result; } - return null; + return new List(); } catch { @@ -983,7 +984,7 @@ namespace BMA.EHR.Application.Repositories { try { - var apiPath = $"{_configuration["API"]}/org/find/all"; + var apiPath = $"{_configuration["API"]}/org/find/allv2"; var apiKey = _configuration["API_KEY"]; var body = new { diff --git a/BMA.EHR.Leave/Controllers/LeaveReportController.cs b/BMA.EHR.Leave/Controllers/LeaveReportController.cs index 3f9b9cf1..b5f620c0 100644 --- a/BMA.EHR.Leave/Controllers/LeaveReportController.cs +++ b/BMA.EHR.Leave/Controllers/LeaveReportController.cs @@ -2241,7 +2241,7 @@ namespace BMA.EHR.Leave.Service.Controllers var enddate = req.EndDate.Date == req.StartDate.Date ? "" : $" - {req.EndDate.Date.ToThaiShortDate()}"; var org = _userProfileRepository.GetOc(Guid.Parse(req.nodeId), req.node, AccessToken); - var organizationName = $"{(!string.IsNullOrEmpty(org.Child4) ? org.Child4 + "/" : "")}{(!string.IsNullOrEmpty(org.Child3) ? org.Child3 + "/" : "")}{(!string.IsNullOrEmpty(org.Child2) ? org.Child2 + "/" : "")}{(!string.IsNullOrEmpty(org.Child1) ? org.Child1 + "/" : "")}{org.Root ?? ""}"; + var organizationName = org == null ? "" : $"{(!string.IsNullOrEmpty(org.Child4) ? org.Child4 + "/" : "")}{(!string.IsNullOrEmpty(org.Child3) ? org.Child3 + "/" : "")}{(!string.IsNullOrEmpty(org.Child2) ? org.Child2 + "/" : "")}{(!string.IsNullOrEmpty(org.Child1) ? org.Child1 + "/" : "")}{org.Root ?? ""}"; var dateTimeStamp = $"ประจำ ณ วัน{req.StartDate.Date.GetThaiDayOfWeek()} ที่ {req.StartDate.Date.ToThaiShortDate()}{enddate}"; var templatePath = Path.Combine(_hostingEnvironment.ContentRootPath, "Reports", "TimeStampRecords.xlsx"); From 909042445c7c52e0093d837bc31a1171299fa7bf Mon Sep 17 00:00:00 2001 From: kittapath <> Date: Fri, 2 Jan 2026 21:47:26 +0700 Subject: [PATCH 012/183] find org v2 --- BMA.EHR.Application/Repositories/UserProfileRepository.cs | 5 +++-- BMA.EHR.Leave/Controllers/LeaveReportController.cs | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/BMA.EHR.Application/Repositories/UserProfileRepository.cs b/BMA.EHR.Application/Repositories/UserProfileRepository.cs index 150aeded..7ffdc024 100644 --- a/BMA.EHR.Application/Repositories/UserProfileRepository.cs +++ b/BMA.EHR.Application/Repositories/UserProfileRepository.cs @@ -574,6 +574,7 @@ namespace BMA.EHR.Application.Repositories // endDate = endDate date = endDate }; + Console.WriteLine(body); var profiles = new List(); @@ -585,7 +586,7 @@ namespace BMA.EHR.Application.Repositories return raw.Result; } - return null; + return new List(); } catch { @@ -983,7 +984,7 @@ namespace BMA.EHR.Application.Repositories { try { - var apiPath = $"{_configuration["API"]}/org/find/all"; + var apiPath = $"{_configuration["API"]}/org/find/allv2"; var apiKey = _configuration["API_KEY"]; var body = new { diff --git a/BMA.EHR.Leave/Controllers/LeaveReportController.cs b/BMA.EHR.Leave/Controllers/LeaveReportController.cs index 3f9b9cf1..b5f620c0 100644 --- a/BMA.EHR.Leave/Controllers/LeaveReportController.cs +++ b/BMA.EHR.Leave/Controllers/LeaveReportController.cs @@ -2241,7 +2241,7 @@ namespace BMA.EHR.Leave.Service.Controllers var enddate = req.EndDate.Date == req.StartDate.Date ? "" : $" - {req.EndDate.Date.ToThaiShortDate()}"; var org = _userProfileRepository.GetOc(Guid.Parse(req.nodeId), req.node, AccessToken); - var organizationName = $"{(!string.IsNullOrEmpty(org.Child4) ? org.Child4 + "/" : "")}{(!string.IsNullOrEmpty(org.Child3) ? org.Child3 + "/" : "")}{(!string.IsNullOrEmpty(org.Child2) ? org.Child2 + "/" : "")}{(!string.IsNullOrEmpty(org.Child1) ? org.Child1 + "/" : "")}{org.Root ?? ""}"; + var organizationName = org == null ? "" : $"{(!string.IsNullOrEmpty(org.Child4) ? org.Child4 + "/" : "")}{(!string.IsNullOrEmpty(org.Child3) ? org.Child3 + "/" : "")}{(!string.IsNullOrEmpty(org.Child2) ? org.Child2 + "/" : "")}{(!string.IsNullOrEmpty(org.Child1) ? org.Child1 + "/" : "")}{org.Root ?? ""}"; var dateTimeStamp = $"ประจำ ณ วัน{req.StartDate.Date.GetThaiDayOfWeek()} ที่ {req.StartDate.Date.ToThaiShortDate()}{enddate}"; var templatePath = Path.Combine(_hostingEnvironment.ContentRootPath, "Reports", "TimeStampRecords.xlsx"); From 05689b5338b8fe0a47c659f5494005e3b2c7d6f0 Mon Sep 17 00:00:00 2001 From: harid Date: Mon, 5 Jan 2026 13:20:00 +0700 Subject: [PATCH 013/183] =?UTF-8?q?=E0=B8=9B=E0=B8=A3=E0=B8=B1=E0=B8=9A=20?= =?UTF-8?q?query=20=E0=B9=81=E0=B8=9A=E0=B8=9A=E0=B9=83=E0=B8=9A=E0=B8=82?= =?UTF-8?q?=E0=B8=AD=E0=B8=A2=E0=B8=81=E0=B9=80=E0=B8=A5=E0=B8=B4=E0=B8=81?= =?UTF-8?q?=E0=B8=A7=E0=B8=B1=E0=B8=99=E0=B8=A5=E0=B8=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Controllers/LeaveReportController.cs | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/BMA.EHR.Leave/Controllers/LeaveReportController.cs b/BMA.EHR.Leave/Controllers/LeaveReportController.cs index b5f620c0..9f88401b 100644 --- a/BMA.EHR.Leave/Controllers/LeaveReportController.cs +++ b/BMA.EHR.Leave/Controllers/LeaveReportController.cs @@ -935,7 +935,7 @@ namespace BMA.EHR.Leave.Service.Controllers // if (list.Count > 0) // approver = list.First().Name; //} - + var approveResult = await GetApproverData(data.Approvers); var result = new { template = "แบบใบขอยกเลิกวันลา", @@ -956,6 +956,22 @@ namespace BMA.EHR.Leave.Service.Controllers profileType = data.ProfileType, leaveReasonDelete = data.LeaveCancelComment == null ? "" : data.LeaveCancelComment!.ToThaiNumber(), leaveDetail = data.LeaveDetail.ToThaiNumber(), + + Type1 = profile.ProfileType == "OFFICER" ? "🗹" : "☐", + Type2 = profile.ProfileType != "OFFICER" ? "🗹" : "☐", + Type3 = "☐", + approve = approveResult, + approverComment = !string.IsNullOrEmpty(data.LeaveDirectorComment) + ? data.LeaveDirectorComment.Replace("\r", "").Replace("\n", "").Trim() + : "......................", + approverUpdatedAt = data.LastUpdatedAt.HasValue + ? data.LastUpdatedAt.Value.ToThaiShortDate().ToThaiNumber() + : "...... /...... /......", + leaveStatus = data.LeaveCancelStatus != null && data.LeaveCancelStatus!.ToUpper() == "APPROVE" + ? "🗹 อนุญาต ☐ ไม่อนุญาต" + : data.LeaveCancelStatus != null && data.LeaveCancelStatus!.ToUpper() == "REJECT" + ? "☐ อนุญาต 🗹 ไม่อนุญาต" + : "☐ อนุญาต ☐ ไม่อนุญาต" } }; From 517755d758272ea6bf93cc6098adcd4becfa4f77 Mon Sep 17 00:00:00 2001 From: harid Date: Mon, 5 Jan 2026 16:12:11 +0700 Subject: [PATCH 014/183] =?UTF-8?q?fix=20=E0=B8=A3=E0=B8=B1=E0=B8=99?= =?UTF-8?q?=E0=B9=80=E0=B8=84=E0=B8=A3=E0=B8=B7=E0=B9=88=E0=B8=AD=E0=B8=87?= =?UTF-8?q?=E0=B8=A3=E0=B8=B2=E0=B8=8A=E0=B9=81=E0=B8=95=E0=B9=88=E0=B8=AB?= =?UTF-8?q?=E0=B8=99=E0=B9=88=E0=B8=A7=E0=B8=A2=E0=B8=87=E0=B8=B2=E0=B8=99?= =?UTF-8?q?=E0=B9=84=E0=B8=A1=E0=B9=88=E0=B8=84=E0=B8=A3=E0=B8=9A=20#1831?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Repositories/InsigniaPeriodsRepository.cs | 1705 +++++++++-------- 1 file changed, 873 insertions(+), 832 deletions(-) diff --git a/BMA.EHR.Application/Repositories/InsigniaPeriodsRepository.cs b/BMA.EHR.Application/Repositories/InsigniaPeriodsRepository.cs index a520f502..ddee97cb 100644 --- a/BMA.EHR.Application/Repositories/InsigniaPeriodsRepository.cs +++ b/BMA.EHR.Application/Repositories/InsigniaPeriodsRepository.cs @@ -297,10 +297,10 @@ namespace BMA.EHR.Application.Repositories : p.ProfileInsignia!.OrderByDescending(x => x.Year).FirstOrDefault()!.InsigniaId.Value, Salary = p.Amount, SalaryCondition = p.ProfileSalary == null || p.ProfileSalary.Count == 0 ? 0 : - p.ProfileSalary.Where(x => x.Date != null).Where(x => x.Date.Value <= new DateTime(period.Year, 4, 29)) + p.ProfileSalary.Where(x => x.Date != null).Where(x => x.Date.HasValue && x.Date.Value <= new DateTime(period.Year, 4, 29)) .OrderByDescending(x => x.Order).FirstOrDefault() != null ? p.ProfileSalary - .Where(x => x.Date != null).Where(x => x.Date.Value <= new DateTime(period.Year, 4, 29)) - .OrderByDescending(x => x.Order).FirstOrDefault().Amount : + .Where(x => x.Date != null).Where(x => x.Date.HasValue && x.Date.Value <= new DateTime(period.Year, 4, 29)) + .OrderByDescending(x => x.Order).FirstOrDefault()?.Amount : p.Amount, PositionSalaryAmount = p.PositionSalaryAmount ?? 0, ProfileInsignia = p.ProfileInsignia, @@ -409,7 +409,7 @@ namespace BMA.EHR.Application.Repositories var s2_a = (from p in allProfilesByRoot where p.ProfileInsignia != null && p.ProfileInsignia.Count > 0 - && (p.ProfileInsignia.Where(x => x.InsigniaId.Value != coinInsignia.Id && + && (p.ProfileInsignia.Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != coinInsignia.Id && x.InsigniaId.Value == bcpRoyal.Id) .ToList() .Count() == 0) @@ -459,22 +459,22 @@ namespace BMA.EHR.Application.Repositories : p.ProfileInsignia!.OrderByDescending(x => x.Year).FirstOrDefault()!.InsigniaId.Value, Salary = p.Amount, SalaryCondition = p.ProfileSalary == null || p.ProfileSalary.Count == 0 ? 0 : - p.ProfileSalary.Where(x => x.Date != null).Where(x => x.Date.Value <= new DateTime(period.Year, 4, 29)) + p.ProfileSalary.Where(x => x.Date != null).Where(x => x.Date.HasValue && x.Date.Value <= new DateTime(period.Year, 4, 29)) .OrderByDescending(x => x.Order).FirstOrDefault() != null ? p.ProfileSalary - .Where(x => x.Date != null).Where(x => x.Date.Value <= new DateTime(period.Year, 4, 29)) - .OrderByDescending(x => x.Order).FirstOrDefault().Amount : + .Where(x => x.Date != null).Where(x => x.Date.HasValue && x.Date.Value <= new DateTime(period.Year, 4, 29)) + .OrderByDescending(x => x.Order).FirstOrDefault()?.Amount : p.Amount, IsHigherLevel = IsHigherLevel(p.ProfileInsignia.ToList() - .Where(x => x.InsigniaId.Value != coinInsignia.Id) + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != coinInsignia.Id) .OrderByDescending(x => x.Year) .FirstOrDefault(), "เบญจมาภรณ์ช้างเผือก"), FirstRecvInsigniaYear = p.ProfileInsignia == null || p.ProfileInsignia.Count == 0 ? 0 : - p.ProfileInsignia.Where(x => x.InsigniaId.Value == bcmRoyal.Id).OrderBy(x => x.Year) + p.ProfileInsignia.Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value == bcmRoyal.Id).OrderBy(x => x.Year) .FirstOrDefault() == null ? 0 : - p.ProfileInsignia.Where(x => x.InsigniaId.Value == bcmRoyal.Id).OrderBy(x => x.Year) - .FirstOrDefault().Year, + p.ProfileInsignia.Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value == bcmRoyal.Id).OrderBy(x => x.Year) + .FirstOrDefault()?.Year, ProfileType = p.ProfileType, MarkDiscipline = p.MarkDiscipline, @@ -555,7 +555,7 @@ namespace BMA.EHR.Application.Repositories var s3 = (from p in allProfilesByRoot where p.ProfileInsignia != null && p.ProfileInsignia.Count > 0 - && (p.ProfileInsignia.Where(x => x.InsigniaId.Value != coinInsignia.Id && + && (p.ProfileInsignia.Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != coinInsignia.Id && x.InsigniaId.Value == jtmRoyal.Id) .ToList() .Count() == 0) @@ -590,21 +590,21 @@ namespace BMA.EHR.Application.Repositories : p.ProfileInsignia!.OrderByDescending(x => x.Year).FirstOrDefault()!.InsigniaId.Value, Salary = p.Amount, SalaryCondition = p.ProfileSalary == null || p.ProfileSalary.Count == 0 ? 0 : - p.ProfileSalary.Where(x => x.Date != null).Where(x => x.Date.Value <= new DateTime(period.Year, 4, 29)) + p.ProfileSalary.Where(x => x.Date != null).Where(x => x.Date.HasValue && x.Date.Value <= new DateTime(period.Year, 4, 29)) .OrderByDescending(x => x.Order).FirstOrDefault() != null ? p.ProfileSalary - .Where(x => x.Date != null).Where(x => x.Date.Value <= new DateTime(period.Year, 4, 29)) - .OrderByDescending(x => x.Order).FirstOrDefault().Amount : + .Where(x => x.Date != null).Where(x => x.Date.HasValue && x.Date.Value <= new DateTime(period.Year, 4, 29)) + .OrderByDescending(x => x.Order).FirstOrDefault()?.Amount : p.Amount, IsHigherLevel = IsHigherLevel(p.ProfileInsignia.ToList() - .Where(x => x.InsigniaId.Value != coinInsignia.Id) + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != coinInsignia.Id) .OrderByDescending(x => x.Year) .FirstOrDefault(), "จัตุรถาภรณ์มงกุฎไทย"), FirstRecvInsigniaYear = p.ProfileInsignia == null ? 0 : - p.ProfileInsignia.Where(x => x.InsigniaId.Value == bcmRoyal.Id).OrderBy(x => x.Year) + p.ProfileInsignia.Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value == bcmRoyal.Id).OrderBy(x => x.Year) .FirstOrDefault() == null ? 0 : - p.ProfileInsignia.Where(x => x.InsigniaId.Value == bcmRoyal.Id).OrderBy(x => x.Year) - .FirstOrDefault().Year, + p.ProfileInsignia.Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value == bcmRoyal.Id).OrderBy(x => x.Year) + .FirstOrDefault()?.Year, PositionSalaryAmount = p.PositionSalaryAmount ?? 0, Amount = p.Amount ?? 0, RootId = p.RootId, @@ -811,10 +811,10 @@ namespace BMA.EHR.Application.Repositories : p.ProfileInsignia!.OrderByDescending(x => x.Year).FirstOrDefault()!.InsigniaId.Value, Salary = p.Amount, SalaryCondition = p.ProfileSalary == null || p.ProfileSalary.Count == 0 ? 0 : - p.ProfileSalary.Where(x => x.Date != null).Where(x => x.Date.Value <= new DateTime(period.Year, 4, 29)) + p.ProfileSalary.Where(x => x.Date != null).Where(x => x.Date.HasValue && x.Date.Value <= new DateTime(period.Year, 4, 29)) .OrderByDescending(x => x.Order).FirstOrDefault() != null ? p.ProfileSalary - .Where(x => x.Date != null).Where(x => x.Date.Value <= new DateTime(period.Year, 4, 29)) - .OrderByDescending(x => x.Order).FirstOrDefault().Amount : + .Where(x => x.Date != null).Where(x => x.Date.HasValue && x.Date.Value <= new DateTime(period.Year, 4, 29)) + .OrderByDescending(x => x.Order).FirstOrDefault()?.Amount : p.Amount, PositionSalaryAmount = p.PositionSalaryAmount ?? 0, ProfileInsignia = p.ProfileInsignia, @@ -923,7 +923,7 @@ namespace BMA.EHR.Application.Repositories var s2 = (from p in allProfilesByRoot where p.ProfileInsignia != null && p.ProfileInsignia.Count > 0 - && (p.ProfileInsignia.Where(x => x.InsigniaId.Value != coinInsignia.Id && + && (p.ProfileInsignia.Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != coinInsignia.Id && x.InsigniaId.Value == jtmRoyal.Id) .ToList() .Count() == 0) @@ -956,21 +956,21 @@ namespace BMA.EHR.Application.Repositories : p.ProfileInsignia!.OrderByDescending(x => x.Year).FirstOrDefault()!.InsigniaId.Value, Salary = p.Amount, SalaryCondition = p.ProfileSalary == null || p.ProfileSalary.Count == 0 ? 0 : - p.ProfileSalary.Where(x => x.Date != null).Where(x => x.Date.Value <= new DateTime(period.Year, 4, 29)) + p.ProfileSalary.Where(x => x.Date != null).Where(x => x.Date.HasValue && x.Date.Value <= new DateTime(period.Year, 4, 29)) .OrderByDescending(x => x.Order).FirstOrDefault() != null ? p.ProfileSalary - .Where(x => x.Date != null).Where(x => x.Date.Value <= new DateTime(period.Year, 4, 29)) - .OrderByDescending(x => x.Order).FirstOrDefault().Amount : + .Where(x => x.Date != null).Where(x => x.Date.HasValue && x.Date.Value <= new DateTime(period.Year, 4, 29)) + .OrderByDescending(x => x.Order).FirstOrDefault()?.Amount : p.Amount, IsHigherLevel = IsHigherLevel(p.ProfileInsignia.ToList() - .Where(x => x.InsigniaId.Value != coinInsignia.Id) + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != coinInsignia.Id) .OrderByDescending(x => x.Year) .FirstOrDefault(), "จัตุรถาภรณ์มงกุฎไทย"), FirstRecvInsigniaYear = p.ProfileInsignia == null ? 0 : - p.ProfileInsignia.Where(x => x.InsigniaId.Value == bcpRoyal.Id).OrderBy(x => x.Year) + p.ProfileInsignia.Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value == bcpRoyal.Id).OrderBy(x => x.Year) .FirstOrDefault() == null ? 0 : - p.ProfileInsignia.Where(x => x.InsigniaId.Value == bcpRoyal.Id).OrderBy(x => x.Year) - .FirstOrDefault().Year, + p.ProfileInsignia.Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value == bcpRoyal.Id).OrderBy(x => x.Year) + .FirstOrDefault()?.Year, PositionSalaryAmount = p.PositionSalaryAmount ?? 0, Amount = p.Amount ?? 0, RootId = p.RootId, @@ -1065,7 +1065,7 @@ namespace BMA.EHR.Application.Repositories var s3 = (from p in allProfilesByRoot where p.ProfileInsignia != null && p.ProfileInsignia.Count > 0 - && (p.ProfileInsignia.Where(x => x.InsigniaId.Value != coinInsignia.Id && + && (p.ProfileInsignia.Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != coinInsignia.Id && x.InsigniaId.Value == jtcRoyal.Id) .ToList() .Count() == 0) @@ -1098,21 +1098,21 @@ namespace BMA.EHR.Application.Repositories : p.ProfileInsignia!.OrderByDescending(x => x.Year).FirstOrDefault()!.InsigniaId.Value, Salary = p.Amount, SalaryCondition = p.ProfileSalary == null || p.ProfileSalary.Count == 0 ? 0 : - p.ProfileSalary.Where(x => x.Date != null).Where(x => x.Date.Value <= new DateTime(period.Year, 4, 29)) + p.ProfileSalary.Where(x => x.Date != null).Where(x => x.Date.HasValue && x.Date.Value <= new DateTime(period.Year, 4, 29)) .OrderByDescending(x => x.Order).FirstOrDefault() != null ? p.ProfileSalary - .Where(x => x.Date != null).Where(x => x.Date.Value <= new DateTime(period.Year, 4, 29)) - .OrderByDescending(x => x.Order).FirstOrDefault().Amount : + .Where(x => x.Date != null).Where(x => x.Date.HasValue && x.Date.Value <= new DateTime(period.Year, 4, 29)) + .OrderByDescending(x => x.Order).FirstOrDefault()?.Amount : p.Amount, IsHigherLevel = IsHigherLevel(p.ProfileInsignia.ToList() - .Where(x => x.InsigniaId.Value != coinInsignia.Id) + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != coinInsignia.Id) .OrderByDescending(x => x.Year) .FirstOrDefault(), "จัตุรถาภรณ์ช้างเผือก"), FirstRecvInsigniaYear = p.ProfileInsignia == null ? 0 : - p.ProfileInsignia.Where(x => x.InsigniaId.Value == jtmRoyal.Id).OrderBy(x => x.Year) + p.ProfileInsignia.Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value == jtmRoyal.Id).OrderBy(x => x.Year) .FirstOrDefault() == null ? 0 : - p.ProfileInsignia.Where(x => x.InsigniaId.Value == jtmRoyal.Id).OrderBy(x => x.Year) - .FirstOrDefault().Year, + p.ProfileInsignia.Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value == jtmRoyal.Id).OrderBy(x => x.Year) + .FirstOrDefault()?.Year, PositionSalaryAmount = p.PositionSalaryAmount, Amount = p.Amount, RootId = p.RootId, @@ -1311,24 +1311,24 @@ namespace BMA.EHR.Application.Repositories Gender = p.Gender ?? "", LastInsignia = p.ProfileInsignia == null || p.ProfileInsignia.Count == 0 ? "" : p.ProfileInsignia - .Where(x => x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) .FirstOrDefault() == null ? "" : p.ProfileInsignia - .Where(x => x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) - .OrderByDescending(x => x.Year).FirstOrDefault().Insignia, + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) + .OrderByDescending(x => x.Year).FirstOrDefault()?.Insignia, LastInsigniaId = p.ProfileInsignia == null || p.ProfileInsignia.Count == 0 ? Guid.Empty : p.ProfileInsignia - .Where(x => x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) .FirstOrDefault() == null ? Guid.Empty : p.ProfileInsignia - .Where(x => x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) - .OrderByDescending(x => x.Year).FirstOrDefault().InsigniaId, + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) + .OrderByDescending(x => x.Year).FirstOrDefault()?.InsigniaId, Salary = p.Amount, SalaryCondition = p.ProfileSalary == null || p.ProfileSalary.Count == 0 ? 0 : - p.ProfileSalary.Where(x => x.Date != null).Where(x => x.Date.Value <= new DateTime(period.Year, 4, 29)) + p.ProfileSalary.Where(x => x.Date != null).Where(x => x.Date.HasValue && x.Date.Value <= new DateTime(period.Year, 4, 29)) .OrderByDescending(x => x.Order).FirstOrDefault() != null ? p.ProfileSalary - .Where(x => x.Date != null).Where(x => x.Date.Value <= new DateTime(period.Year, 4, 29)) - .OrderByDescending(x => x.Order).FirstOrDefault().Amount : + .Where(x => x.Date != null).Where(x => x.Date.HasValue && x.Date.Value <= new DateTime(period.Year, 4, 29)) + .OrderByDescending(x => x.Order).FirstOrDefault()?.Amount : p.Amount, ProfileInsignia = p.ProfileInsignia, PositionSalaryAmount = p.PositionSalaryAmount, @@ -1426,8 +1426,9 @@ namespace BMA.EHR.Application.Repositories && p.ProfileInsignia != null && p.ProfileInsignia.Count > 0 && (p.ProfileInsignia.Where(x => - x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id && - x.InsigniaId.Value == GetInsigniaByName("เบญจมาภรณ์ช้างเผือก").Id) + x.InsigniaId.HasValue && + x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id && + x.InsigniaId.Value == GetInsigniaByName("เบญจมาภรณ์ช้างเผือก")?.Id) .ToList() .Count() == 0) select new @@ -1446,24 +1447,24 @@ namespace BMA.EHR.Application.Repositories ProfileDateAppoint = p.DateAppoint.Value, LastInsignia = p.ProfileInsignia == null || p.ProfileInsignia.Count == 0 ? "" : p.ProfileInsignia - .Where(x => x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) .FirstOrDefault() == null ? "" : p.ProfileInsignia - .Where(x => x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) - .OrderByDescending(x => x.Year).FirstOrDefault().Insignia, + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) + .OrderByDescending(x => x.Year).FirstOrDefault()?.Insignia, LastInsigniaId = p.ProfileInsignia == null || p.ProfileInsignia.Count == 0 ? Guid.Empty : p.ProfileInsignia - .Where(x => x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) .FirstOrDefault() == null ? Guid.Empty : p.ProfileInsignia - .Where(x => x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) - .OrderByDescending(x => x.Year).FirstOrDefault().InsigniaId, + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) + .OrderByDescending(x => x.Year).FirstOrDefault()?.InsigniaId, Salary = p.Amount, SalaryCondition = p.ProfileSalary == null || p.ProfileSalary.Count == 0 ? 0 : - p.ProfileSalary.Where(x => x.Date != null).Where(x => x.Date.Value <= new DateTime(period.Year, 4, 29)) + p.ProfileSalary.Where(x => x.Date != null).Where(x => x.Date.HasValue && x.Date.Value <= new DateTime(period.Year, 4, 29)) .OrderByDescending(x => x.Order).FirstOrDefault() != null ? p.ProfileSalary - .Where(x => x.Date != null).Where(x => x.Date.Value <= new DateTime(period.Year, 4, 29)) - .OrderByDescending(x => x.Order).FirstOrDefault().Amount : + .Where(x => x.Date != null).Where(x => x.Date.HasValue && x.Date.Value <= new DateTime(period.Year, 4, 29)) + .OrderByDescending(x => x.Order).FirstOrDefault()?.Amount : p.Amount, PosNo = p.PosNo ?? "", Gender = p.Gender == null ? "" : p.Gender, @@ -1472,7 +1473,7 @@ namespace BMA.EHR.Application.Repositories PositionTypeId = p.PosTypeId, PositionTypeName = p.PosType, IsHigherLevel = IsHigherLevel(p.ProfileInsignia - .Where(x => x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) .OrderByDescending(x => x.Year) .FirstOrDefault(), "เบญจมาภรณ์ช้างเผือก"), @@ -1573,8 +1574,9 @@ namespace BMA.EHR.Application.Repositories && p.ProfileInsignia != null && p.ProfileInsignia.Count > 0 && (p.ProfileInsignia.Where(x => - x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id && - x.InsigniaId.Value == GetInsigniaByName("จัตุรถาภรณ์มงกุฎไทย").Id) + x.InsigniaId.HasValue && + x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id && + x.InsigniaId.Value == GetInsigniaByName("จัตุรถาภรณ์มงกุฎไทย")?.Id) .ToList() .Count() == 0) select new @@ -1593,24 +1595,24 @@ namespace BMA.EHR.Application.Repositories ProfileDateAppoint = p.DateAppoint.Value, LastInsignia = p.ProfileInsignia == null || p.ProfileInsignia.Count == 0 ? "" : p.ProfileInsignia - .Where(x => x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) .FirstOrDefault() == null ? "" : p.ProfileInsignia - .Where(x => x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) - .OrderByDescending(x => x.Year).FirstOrDefault().Insignia, + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) + .OrderByDescending(x => x.Year).FirstOrDefault()?.Insignia, LastInsigniaId = p.ProfileInsignia == null || p.ProfileInsignia.Count == 0 ? Guid.Empty : p.ProfileInsignia - .Where(x => x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) .FirstOrDefault() == null ? Guid.Empty : p.ProfileInsignia - .Where(x => x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) - .OrderByDescending(x => x.Year).FirstOrDefault().InsigniaId, + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) + .OrderByDescending(x => x.Year).FirstOrDefault()?.InsigniaId, Salary = p.Amount, SalaryCondition = p.ProfileSalary == null || p.ProfileSalary.Count == 0 ? 0 : - p.ProfileSalary.Where(x => x.Date != null).Where(x => x.Date.Value <= new DateTime(period.Year, 4, 29)) + p.ProfileSalary.Where(x => x.Date != null).Where(x => x.Date.HasValue && x.Date.Value <= new DateTime(period.Year, 4, 29)) .OrderByDescending(x => x.Order).FirstOrDefault() != null ? p.ProfileSalary - .Where(x => x.Date != null).Where(x => x.Date.Value <= new DateTime(period.Year, 4, 29)) - .OrderByDescending(x => x.Order).FirstOrDefault().Amount : + .Where(x => x.Date != null).Where(x => x.Date.HasValue && x.Date.Value <= new DateTime(period.Year, 4, 29)) + .OrderByDescending(x => x.Order).FirstOrDefault()?.Amount : p.Amount, PosNo = p.PosNo ?? "", Gender = p.Gender == null ? "" : p.Gender, @@ -1620,12 +1622,12 @@ namespace BMA.EHR.Application.Repositories PositionTypeName = p.PosType, IsHigherLevel = p.ProfileInsignia - .Where(x => x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) .FirstOrDefault() == null ? true : IsHigherLevel(p.ProfileInsignia - .Where(x => x.InsigniaId.Value != - GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != + GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) .OrderByDescending(x => x.Year) .FirstOrDefault(), "จัตุรถาภรณ์มงกุฎไทย"), @@ -1719,8 +1721,9 @@ namespace BMA.EHR.Application.Repositories && p.ProfileInsignia != null && p.ProfileInsignia.Count > 0 && (p.ProfileInsignia.Where(x => - x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id && - x.InsigniaId.Value == GetInsigniaByName("จัตุรถาภรณ์ช้างเผือก").Id) + x.InsigniaId.HasValue && + x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id && + x.InsigniaId.Value == GetInsigniaByName("จัตุรถาภรณ์ช้างเผือก")?.Id) .ToList() .Count() == 0) select new @@ -1739,24 +1742,24 @@ namespace BMA.EHR.Application.Repositories ProfileDateAppoint = p.DateAppoint.Value, LastInsignia = p.ProfileInsignia == null || p.ProfileInsignia.Count == 0 ? "" : p.ProfileInsignia - .Where(x => x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) .FirstOrDefault() == null ? "" : p.ProfileInsignia - .Where(x => x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) - .OrderByDescending(x => x.Year).FirstOrDefault().Insignia, + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) + .OrderByDescending(x => x.Year).FirstOrDefault()?.Insignia, LastInsigniaId = p.ProfileInsignia == null || p.ProfileInsignia.Count == 0 ? Guid.Empty : p.ProfileInsignia - .Where(x => x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) .FirstOrDefault() == null ? Guid.Empty : p.ProfileInsignia - .Where(x => x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) - .OrderByDescending(x => x.Year).FirstOrDefault().InsigniaId, + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) + .OrderByDescending(x => x.Year).FirstOrDefault()?.InsigniaId, Salary = p.Amount, SalaryCondition = p.ProfileSalary == null || p.ProfileSalary.Count == 0 ? 0 : - p.ProfileSalary.Where(x => x.Date != null).Where(x => x.Date.Value <= new DateTime(period.Year, 4, 29)) + p.ProfileSalary.Where(x => x.Date != null).Where(x => x.Date.HasValue && x.Date.Value <= new DateTime(period.Year, 4, 29)) .OrderByDescending(x => x.Order).FirstOrDefault() != null ? p.ProfileSalary - .Where(x => x.Date != null).Where(x => x.Date.Value <= new DateTime(period.Year, 4, 29)) - .OrderByDescending(x => x.Order).FirstOrDefault().Amount : + .Where(x => x.Date != null).Where(x => x.Date.HasValue && x.Date.Value <= new DateTime(period.Year, 4, 29)) + .OrderByDescending(x => x.Order).FirstOrDefault()?.Amount : p.Amount, PosNo = p.PosNo ?? "", Gender = p.Gender == null ? "" : p.Gender, @@ -1766,12 +1769,12 @@ namespace BMA.EHR.Application.Repositories PositionTypeName = p.PosType, IsHigherLevel = p.ProfileInsignia - .Where(x => x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) .FirstOrDefault() == null ? true : IsHigherLevel(p.ProfileInsignia - .Where(x => x.InsigniaId.Value != - GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != + GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) .OrderByDescending(x => x.Year) .FirstOrDefault(), "จัตุรถาภรณ์ช้างเผือก"), @@ -1972,8 +1975,9 @@ namespace BMA.EHR.Application.Repositories && p.ProfileInsignia != null && p.ProfileInsignia.Count > 0 && (p.ProfileInsignia.Where(x => - x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id && - x.InsigniaId.Value == GetInsigniaByName("ตริตาภรณ์มงกุฎไทย").Id) + x.InsigniaId.HasValue && + x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id && + x.InsigniaId.Value == GetInsigniaByName("ตริตาภรณ์มงกุฎไทย")?.Id) .ToList() .Count() == 0) select new @@ -1992,24 +1996,24 @@ namespace BMA.EHR.Application.Repositories ProfileDateAppoint = p.DateAppoint.Value, LastInsignia = p.ProfileInsignia == null || p.ProfileInsignia.Count == 0 ? "" : p.ProfileInsignia - .Where(x => x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) .FirstOrDefault() == null ? "" : p.ProfileInsignia - .Where(x => x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) - .OrderByDescending(x => x.Year).FirstOrDefault().Insignia, + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) + .OrderByDescending(x => x.Year).FirstOrDefault()?.Insignia, LastInsigniaId = p.ProfileInsignia == null || p.ProfileInsignia.Count == 0 ? Guid.Empty : p.ProfileInsignia - .Where(x => x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) .FirstOrDefault() == null ? Guid.Empty : p.ProfileInsignia - .Where(x => x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) - .OrderByDescending(x => x.Year).FirstOrDefault().InsigniaId, + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) + .OrderByDescending(x => x.Year).FirstOrDefault()?.InsigniaId, Salary = p.Amount, SalaryCondition = p.ProfileSalary == null || p.ProfileSalary.Count == 0 ? 0 : - p.ProfileSalary.Where(x => x.Date != null).Where(x => x.Date.Value <= new DateTime(period.Year, 4, 29)) + p.ProfileSalary.Where(x => x.Date != null).Where(x => x.Date.HasValue && x.Date.Value <= new DateTime(period.Year, 4, 29)) .OrderByDescending(x => x.Order).FirstOrDefault() != null ? p.ProfileSalary - .Where(x => x.Date != null).Where(x => x.Date.Value <= new DateTime(period.Year, 4, 29)) - .OrderByDescending(x => x.Order).FirstOrDefault().Amount : + .Where(x => x.Date != null).Where(x => x.Date.HasValue && x.Date.Value <= new DateTime(period.Year, 4, 29)) + .OrderByDescending(x => x.Order).FirstOrDefault()?.Amount : p.Amount, PosNo = p.PosNo, Gender = p.Gender == null ? null : p.Gender, @@ -2019,12 +2023,12 @@ namespace BMA.EHR.Application.Repositories PositionTypeName = p.PosType, IsHigherLevel = p.ProfileInsignia - .Where(x => x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) .FirstOrDefault() == null ? true : IsHigherLevel(p.ProfileInsignia - .Where(x => x.InsigniaId.Value != - GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != + GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) .OrderByDescending(x => x.Year) .FirstOrDefault(), "ตริตาภรณ์มงกุฎไทย"), @@ -2118,8 +2122,9 @@ namespace BMA.EHR.Application.Repositories && p.ProfileInsignia != null && p.ProfileInsignia.Count > 0 && (p.ProfileInsignia.Where(x => - x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id && - x.InsigniaId.Value == GetInsigniaByName("ตริตาภรณ์ช้างเผือก").Id) + x.InsigniaId.HasValue && + x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id && + x.InsigniaId.Value == GetInsigniaByName("ตริตาภรณ์ช้างเผือก")?.Id) .ToList() .Count() == 0) select new @@ -2138,24 +2143,24 @@ namespace BMA.EHR.Application.Repositories ProfileDateAppoint = p.DateAppoint.Value, LastInsignia = p.ProfileInsignia == null || p.ProfileInsignia.Count == 0 ? "" : p.ProfileInsignia - .Where(x => x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) .FirstOrDefault() == null ? "" : p.ProfileInsignia - .Where(x => x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) - .OrderByDescending(x => x.Year).FirstOrDefault().Insignia, + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) + .OrderByDescending(x => x.Year).FirstOrDefault()?.Insignia, LastInsigniaId = p.ProfileInsignia == null || p.ProfileInsignia.Count == 0 ? Guid.Empty : p.ProfileInsignia - .Where(x => x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) .FirstOrDefault() == null ? Guid.Empty : p.ProfileInsignia - .Where(x => x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) - .OrderByDescending(x => x.Year).FirstOrDefault().InsigniaId, + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) + .OrderByDescending(x => x.Year).FirstOrDefault()?.InsigniaId, Salary = p.Amount, SalaryCondition = p.ProfileSalary == null || p.ProfileSalary.Count == 0 ? 0 : - p.ProfileSalary.Where(x => x.Date != null).Where(x => x.Date.Value <= new DateTime(period.Year, 4, 29)) + p.ProfileSalary.Where(x => x.Date != null).Where(x => x.Date.HasValue && x.Date.Value <= new DateTime(period.Year, 4, 29)) .OrderByDescending(x => x.Order).FirstOrDefault() != null ? p.ProfileSalary - .Where(x => x.Date != null).Where(x => x.Date.Value <= new DateTime(period.Year, 4, 29)) - .OrderByDescending(x => x.Order).FirstOrDefault().Amount : + .Where(x => x.Date != null).Where(x => x.Date.HasValue && x.Date.Value <= new DateTime(period.Year, 4, 29)) + .OrderByDescending(x => x.Order).FirstOrDefault()?.Amount : p.Amount, PosNo = p.PosNo, Gender = p.Gender == null ? null : p.Gender, @@ -2165,12 +2170,12 @@ namespace BMA.EHR.Application.Repositories PositionTypeName = p.PosType, IsHigherLevel = p.ProfileInsignia - .Where(x => x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) .FirstOrDefault() == null ? true : IsHigherLevel(p.ProfileInsignia - .Where(x => x.InsigniaId.Value != - GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != + GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) .OrderByDescending(x => x.Year) .FirstOrDefault(), "ตริตาภรณ์ช้างเผือก"), @@ -2180,7 +2185,7 @@ namespace BMA.EHR.Application.Repositories p.ProfileSalary.Where(x => x.PositionLevel == "ชำนาญงาน").OrderBy(p => p.Date) .FirstOrDefault() == null ? null : p.ProfileSalary.Where(x => x.PositionLevel == "ชำนาญงาน").OrderBy(p => p.Date) - .FirstOrDefault().Date, + .FirstOrDefault()?.Date, PositionSalaryAmount = p.PositionSalaryAmount, Amount = p.Amount, RootId = p.RootId, @@ -2336,8 +2341,9 @@ namespace BMA.EHR.Application.Repositories && p.ProfileInsignia != null && p.ProfileInsignia.Count > 0 && (p.ProfileInsignia.Where(x => - x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id && - x.InsigniaId.Value == GetInsigniaByName("ทวีติยาภรณ์มงกุฎไทย").Id) + x.InsigniaId.HasValue && + x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id && + x.InsigniaId.Value == GetInsigniaByName("ทวีติยาภรณ์มงกุฎไทย")?.Id) .ToList() .Count() == 0) select new @@ -2356,24 +2362,24 @@ namespace BMA.EHR.Application.Repositories ProfileDateAppoint = p.DateAppoint.Value, LastInsignia = p.ProfileInsignia == null || p.ProfileInsignia.Count == 0 ? "" : p.ProfileInsignia - .Where(x => x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) .FirstOrDefault() == null ? "" : p.ProfileInsignia - .Where(x => x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) - .OrderByDescending(x => x.Year).FirstOrDefault().Insignia, + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) + .OrderByDescending(x => x.Year).FirstOrDefault()?.Insignia, LastInsigniaId = p.ProfileInsignia == null || p.ProfileInsignia.Count == 0 ? Guid.Empty : p.ProfileInsignia - .Where(x => x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) .FirstOrDefault() == null ? Guid.Empty : p.ProfileInsignia - .Where(x => x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) - .OrderByDescending(x => x.Year).FirstOrDefault().InsigniaId, + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) + .OrderByDescending(x => x.Year).FirstOrDefault()?.InsigniaId, Salary = p.Amount, SalaryCondition = p.ProfileSalary == null || p.ProfileSalary.Count == 0 ? 0 : - p.ProfileSalary.Where(x => x.Date != null).Where(x => x.Date.Value <= new DateTime(period.Year, 4, 29)) + p.ProfileSalary.Where(x => x.Date != null).Where(x => x.Date.HasValue && x.Date.Value <= new DateTime(period.Year, 4, 29)) .OrderByDescending(x => x.Order).FirstOrDefault() != null ? p.ProfileSalary - .Where(x => x.Date != null).Where(x => x.Date.Value <= new DateTime(period.Year, 4, 29)) - .OrderByDescending(x => x.Order).FirstOrDefault().Amount : + .Where(x => x.Date != null).Where(x => x.Date.HasValue && x.Date.Value <= new DateTime(period.Year, 4, 29)) + .OrderByDescending(x => x.Order).FirstOrDefault()?.Amount : p.Amount, PosNo = p.PosNo, Gender = p.Gender == null ? null : p.Gender, @@ -2383,12 +2389,12 @@ namespace BMA.EHR.Application.Repositories PositionTypeName = p.PosType, IsHigherLevel = p.ProfileInsignia - .Where(x => x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) .FirstOrDefault() == null ? true : IsHigherLevel(p.ProfileInsignia - .Where(x => x.InsigniaId.Value != - GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != + GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) .OrderByDescending(x => x.Year) .FirstOrDefault(), "ทวีติยาภรณ์มงกุฎไทย"), @@ -2398,7 +2404,7 @@ namespace BMA.EHR.Application.Repositories p.ProfileSalary.Where(x => x.PositionLevel == "อาวุโส").OrderBy(p => p.Date) .FirstOrDefault() == null ? null : p.ProfileSalary.Where(x => x.PositionLevel == "อาวุโส").OrderBy(p => p.Date) - .FirstOrDefault().Date, + .FirstOrDefault()?.Date, PositionSalaryAmount = p.PositionSalaryAmount, Amount = p.Amount, RootId = p.RootId, @@ -2487,8 +2493,9 @@ namespace BMA.EHR.Application.Repositories && p.ProfileInsignia != null && p.ProfileInsignia.Count > 0 && (p.ProfileInsignia.Where(x => - x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id && - x.InsigniaId.Value == GetInsigniaByName("ทวีติยาภรณ์ช้างเผือก").Id) + x.InsigniaId.HasValue && + x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id && + x.InsigniaId.Value == GetInsigniaByName("ทวีติยาภรณ์ช้างเผือก")?.Id) .ToList() .Count() == 0) select new @@ -2507,24 +2514,24 @@ namespace BMA.EHR.Application.Repositories ProfileDateAppoint = p.DateAppoint.Value, LastInsignia = p.ProfileInsignia == null || p.ProfileInsignia.Count == 0 ? "" : p.ProfileInsignia - .Where(x => x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) .FirstOrDefault() == null ? "" : p.ProfileInsignia - .Where(x => x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) - .OrderByDescending(x => x.Year).FirstOrDefault().Insignia, + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) + .OrderByDescending(x => x.Year).FirstOrDefault()?.Insignia, LastInsigniaId = p.ProfileInsignia == null || p.ProfileInsignia.Count == 0 ? Guid.Empty : p.ProfileInsignia - .Where(x => x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) .FirstOrDefault() == null ? Guid.Empty : p.ProfileInsignia - .Where(x => x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) - .OrderByDescending(x => x.Year).FirstOrDefault().InsigniaId, + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) + .OrderByDescending(x => x.Year).FirstOrDefault()?.InsigniaId, Salary = p.Amount, SalaryCondition = p.ProfileSalary == null || p.ProfileSalary.Count == 0 ? 0 : - p.ProfileSalary.Where(x => x.Date != null).Where(x => x.Date.Value <= new DateTime(period.Year, 4, 29)) + p.ProfileSalary.Where(x => x.Date != null).Where(x => x.Date.HasValue && x.Date.Value <= new DateTime(period.Year, 4, 29)) .OrderByDescending(x => x.Order).FirstOrDefault() != null ? p.ProfileSalary - .Where(x => x.Date != null).Where(x => x.Date.Value <= new DateTime(period.Year, 4, 29)) - .OrderByDescending(x => x.Order).FirstOrDefault().Amount : + .Where(x => x.Date != null).Where(x => x.Date.HasValue && x.Date.Value <= new DateTime(period.Year, 4, 29)) + .OrderByDescending(x => x.Order).FirstOrDefault()?.Amount : p.Amount, PosNo = p.PosNo, Gender = p.Gender == null ? null : p.Gender, @@ -2534,12 +2541,12 @@ namespace BMA.EHR.Application.Repositories PositionTypeName = p.PosType, IsHigherLevel = p.ProfileInsignia - .Where(x => x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) .FirstOrDefault() == null ? true : IsHigherLevel(p.ProfileInsignia - .Where(x => x.InsigniaId.Value != - GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != + GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) .OrderByDescending(x => x.Year) .FirstOrDefault(), "ทวีติยาภรณ์ช้างเผือก"), @@ -2549,7 +2556,7 @@ namespace BMA.EHR.Application.Repositories p.ProfileSalary.Where(x => x.PositionLevel == "อาวุโส").OrderBy(p => p.Date) .FirstOrDefault() == null ? null : p.ProfileSalary.Where(x => x.PositionLevel == "อาวุโส").OrderBy(p => p.Date) - .FirstOrDefault().Date, + .FirstOrDefault()?.Date, PositionSalaryAmount = p.PositionSalaryAmount, Amount = p.Amount, RootId = p.RootId, @@ -2706,8 +2713,9 @@ namespace BMA.EHR.Application.Repositories && p.ProfileInsignia != null && p.ProfileInsignia.Count > 0 && (p.ProfileInsignia.Where(x => - x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id && - x.InsigniaId.Value == GetInsigniaByName("ทวีติยาภรณ์ช้างเผือก").Id) + x.InsigniaId.HasValue && + x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id && + x.InsigniaId.Value == GetInsigniaByName("ทวีติยาภรณ์ช้างเผือก")?.Id) .ToList() .Count() == 0) select new @@ -2726,24 +2734,24 @@ namespace BMA.EHR.Application.Repositories ProfileDateAppoint = p.DateAppoint.Value, LastInsignia = p.ProfileInsignia == null || p.ProfileInsignia.Count == 0 ? "" : p.ProfileInsignia - .Where(x => x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) .FirstOrDefault() == null ? "" : p.ProfileInsignia - .Where(x => x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) - .OrderByDescending(x => x.Year).FirstOrDefault().Insignia, + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) + .OrderByDescending(x => x.Year).FirstOrDefault()?.Insignia, LastInsigniaId = p.ProfileInsignia == null || p.ProfileInsignia.Count == 0 ? Guid.Empty : p.ProfileInsignia - .Where(x => x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) .FirstOrDefault() == null ? Guid.Empty : p.ProfileInsignia - .Where(x => x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) - .OrderByDescending(x => x.Year).FirstOrDefault().InsigniaId, + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) + .OrderByDescending(x => x.Year).FirstOrDefault()?.InsigniaId, Salary = p.Amount, SalaryCondition = p.ProfileSalary == null || p.ProfileSalary.Count == 0 ? 0 : - p.ProfileSalary.Where(x => x.Date != null).Where(x => x.Date.Value <= new DateTime(period.Year, 4, 29)) + p.ProfileSalary.Where(x => x.Date != null).Where(x => x.Date.HasValue && x.Date.Value <= new DateTime(period.Year, 4, 29)) .OrderByDescending(x => x.Order).FirstOrDefault() != null ? p.ProfileSalary - .Where(x => x.Date != null).Where(x => x.Date.Value <= new DateTime(period.Year, 4, 29)) - .OrderByDescending(x => x.Order).FirstOrDefault().Amount : + .Where(x => x.Date != null).Where(x => x.Date.HasValue && x.Date.Value <= new DateTime(period.Year, 4, 29)) + .OrderByDescending(x => x.Order).FirstOrDefault()?.Amount : p.Amount, PosNo = p.PosNo, Gender = p.Gender == null ? null : p.Gender, @@ -2753,12 +2761,12 @@ namespace BMA.EHR.Application.Repositories PositionTypeName = p.PosType, IsHigherLevel = p.ProfileInsignia - .Where(x => x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) .FirstOrDefault() == null ? true : IsHigherLevel(p.ProfileInsignia - .Where(x => x.InsigniaId.Value != - GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != + GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) .OrderByDescending(x => x.Year) .FirstOrDefault(), "ทวีติยาภรณ์ช้างเผือก"), @@ -2768,7 +2776,7 @@ namespace BMA.EHR.Application.Repositories p.ProfileSalary.Where(x => x.PositionLevel == "อาวุโส").OrderBy(p => p.Date) .FirstOrDefault() == null ? null : p.ProfileSalary.Where(x => x.PositionLevel == "อาวุโส").OrderBy(p => p.Date) - .FirstOrDefault().Date, + .FirstOrDefault()?.Date, PositionSalaryAmount = p.PositionSalaryAmount, Amount = p.Amount, RootId = p.RootId, @@ -2858,8 +2866,9 @@ namespace BMA.EHR.Application.Repositories && p.ProfileInsignia != null && p.ProfileInsignia.Count > 0 && (p.ProfileInsignia.Where(x => - x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id && - x.InsigniaId.Value == GetInsigniaByName("ประถมาภรณ์มงกุฎไทย").Id) + x.InsigniaId.HasValue && + x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id && + x.InsigniaId.Value == GetInsigniaByName("ประถมาภรณ์มงกุฎไทย")?.Id) .ToList() .Count() == 0) select new @@ -2878,24 +2887,24 @@ namespace BMA.EHR.Application.Repositories ProfileDateAppoint = p.DateAppoint.Value, LastInsignia = p.ProfileInsignia == null || p.ProfileInsignia.Count == 0 ? "" : p.ProfileInsignia - .Where(x => x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) .FirstOrDefault() == null ? "" : p.ProfileInsignia - .Where(x => x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) - .OrderByDescending(x => x.Year).FirstOrDefault().Insignia, + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) + .OrderByDescending(x => x.Year).FirstOrDefault()?.Insignia, LastInsigniaId = p.ProfileInsignia == null || p.ProfileInsignia.Count == 0 ? Guid.Empty : p.ProfileInsignia - .Where(x => x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) .FirstOrDefault() == null ? Guid.Empty : p.ProfileInsignia - .Where(x => x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) - .OrderByDescending(x => x.Year).FirstOrDefault().InsigniaId, + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) + .OrderByDescending(x => x.Year).FirstOrDefault()?.InsigniaId, Salary = p.Amount, SalaryCondition = p.ProfileSalary == null || p.ProfileSalary.Count == 0 ? 0 : - p.ProfileSalary.Where(x => x.Date != null).Where(x => x.Date.Value <= new DateTime(period.Year, 4, 29)) + p.ProfileSalary.Where(x => x.Date != null).Where(x => x.Date.HasValue && x.Date.Value <= new DateTime(period.Year, 4, 29)) .OrderByDescending(x => x.Order).FirstOrDefault() != null ? p.ProfileSalary - .Where(x => x.Date != null).Where(x => x.Date.Value <= new DateTime(period.Year, 4, 29)) - .OrderByDescending(x => x.Order).FirstOrDefault().Amount : + .Where(x => x.Date != null).Where(x => x.Date.HasValue && x.Date.Value <= new DateTime(period.Year, 4, 29)) + .OrderByDescending(x => x.Order).FirstOrDefault()?.Amount : p.Amount, PosNo = p.PosNo, Gender = p.Gender == null ? null : p.Gender, @@ -2905,12 +2914,12 @@ namespace BMA.EHR.Application.Repositories PositionTypeName = p.PosType, IsHigherLevel = p.ProfileInsignia - .Where(x => x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) .FirstOrDefault() == null ? true : IsHigherLevel(p.ProfileInsignia - .Where(x => x.InsigniaId.Value != - GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != + GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) .OrderByDescending(x => x.Year) .FirstOrDefault(), "ประถมาภรณ์มงกุฎไทย"), @@ -2920,14 +2929,14 @@ namespace BMA.EHR.Application.Repositories p.ProfileSalary.Where(x => x.PositionLevel == "อาวุโส").OrderBy(p => p.Date) .FirstOrDefault() == null ? null : p.ProfileSalary.Where(x => x.PositionLevel == "อาวุโส").OrderBy(p => p.Date) - .FirstOrDefault().Date, + .FirstOrDefault()?.Date, FirstRecvInsigniaYear = p.ProfileInsignia.Count == 0 ? 0 : p.ProfileInsignia - .Where(x => x.InsigniaId.Value == GetInsigniaByName("ทวีติยาภรณ์ช้างเผือก").Id) + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value == GetInsigniaByName("ทวีติยาภรณ์ช้างเผือก")?.Id) .OrderBy(x => x.Year).FirstOrDefault() == null ? 0 : p.ProfileInsignia - .Where(x => x.InsigniaId.Value == GetInsigniaByName("ทวีติยาภรณ์ช้างเผือก").Id) - .OrderBy(x => x.Year).FirstOrDefault().Year, + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value == GetInsigniaByName("ทวีติยาภรณ์ช้างเผือก")?.Id) + .OrderBy(x => x.Year).FirstOrDefault()?.Year, PositionSalaryAmount = p.PositionSalaryAmount, Amount = p.Amount, RootId = p.RootId, @@ -3019,8 +3028,9 @@ namespace BMA.EHR.Application.Repositories && p.ProfileInsignia != null && p.ProfileInsignia.Count > 0 && (p.ProfileInsignia.Where(x => - x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id && - x.InsigniaId.Value == GetInsigniaByName("ประถมาภรณ์ช้างเผือก").Id) + x.InsigniaId.HasValue && + x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id && + x.InsigniaId.Value == GetInsigniaByName("ประถมาภรณ์ช้างเผือก")?.Id) .ToList() .Count() == 0) select new @@ -3039,24 +3049,24 @@ namespace BMA.EHR.Application.Repositories ProfileDateAppoint = p.DateAppoint.Value, LastInsignia = p.ProfileInsignia == null || p.ProfileInsignia.Count == 0 ? "" : p.ProfileInsignia - .Where(x => x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) .FirstOrDefault() == null ? "" : p.ProfileInsignia - .Where(x => x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) - .OrderByDescending(x => x.Year).FirstOrDefault().Insignia, + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) + .OrderByDescending(x => x.Year).FirstOrDefault()?.Insignia, LastInsigniaId = p.ProfileInsignia == null || p.ProfileInsignia.Count == 0 ? Guid.Empty : p.ProfileInsignia - .Where(x => x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) .FirstOrDefault() == null ? Guid.Empty : p.ProfileInsignia - .Where(x => x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) - .OrderByDescending(x => x.Year).FirstOrDefault().InsigniaId, + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) + .OrderByDescending(x => x.Year).FirstOrDefault()?.InsigniaId, Salary = p.Amount, SalaryCondition = p.ProfileSalary == null || p.ProfileSalary.Count == 0 ? 0 : - p.ProfileSalary.Where(x => x.Date != null).Where(x => x.Date.Value <= new DateTime(period.Year, 4, 29)) + p.ProfileSalary.Where(x => x.Date != null).Where(x => x.Date.HasValue && x.Date.Value <= new DateTime(period.Year, 4, 29)) .OrderByDescending(x => x.Order).FirstOrDefault() != null ? p.ProfileSalary - .Where(x => x.Date != null).Where(x => x.Date.Value <= new DateTime(period.Year, 4, 29)) - .OrderByDescending(x => x.Order).FirstOrDefault().Amount : + .Where(x => x.Date != null).Where(x => x.Date.HasValue && x.Date.Value <= new DateTime(period.Year, 4, 29)) + .OrderByDescending(x => x.Order).FirstOrDefault()?.Amount : p.Amount, PosNo = p.PosNo, Gender = p.Gender == null ? null : p.Gender, @@ -3066,12 +3076,12 @@ namespace BMA.EHR.Application.Repositories PositionTypeName = p.PosType, IsHigherLevel = p.ProfileInsignia - .Where(x => x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) .FirstOrDefault() == null ? true : IsHigherLevel(p.ProfileInsignia - .Where(x => x.InsigniaId.Value != - GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != + GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) .OrderByDescending(x => x.Year) .FirstOrDefault(), "ประถมาภรณ์ช้างเผือก"), @@ -3081,14 +3091,14 @@ namespace BMA.EHR.Application.Repositories p.ProfileSalary.Where(x => x.PositionLevel == "อาวุโส").OrderBy(p => p.Date) .FirstOrDefault() == null ? null : p.ProfileSalary.Where(x => x.PositionLevel == "อาวุโส").OrderBy(p => p.Date) - .FirstOrDefault().Date, + .FirstOrDefault()?.Date, FirstRecvInsigniaYear = p.ProfileInsignia.Count == 0 ? 0 : p.ProfileInsignia - .Where(x => x.InsigniaId.Value == GetInsigniaByName("ประถมาภรณ์มงกุฎไทย").Id) + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value == GetInsigniaByName("ประถมาภรณ์มงกุฎไทย")?.Id) .OrderBy(x => x.Year).FirstOrDefault() == null ? 0 : p.ProfileInsignia - .Where(x => x.InsigniaId.Value == GetInsigniaByName("ประถมาภรณ์มงกุฎไทย").Id) - .OrderBy(x => x.Year).FirstOrDefault().Year, + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value == GetInsigniaByName("ประถมาภรณ์มงกุฎไทย")?.Id) + .OrderBy(x => x.Year).FirstOrDefault()?.Year, PositionSalaryAmount = p.PositionSalaryAmount, Amount = p.Amount, RootId = p.RootId, @@ -3265,8 +3275,9 @@ namespace BMA.EHR.Application.Repositories where p.PosType == "วิชาการ" && p.ProfileInsignia.Count > 0 && (p.ProfileInsignia.Where(x => - x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id && - x.InsigniaId.Value == GetInsigniaByName("ตริตาภรณ์มงกุฎไทย").Id) + x.InsigniaId.HasValue && + x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id && + x.InsigniaId.Value == GetInsigniaByName("ตริตาภรณ์มงกุฎไทย")?.Id) .ToList() .Count() == 0) select new @@ -3285,24 +3296,24 @@ namespace BMA.EHR.Application.Repositories ProfileDateAppoint = p.DateAppoint.Value, LastInsignia = p.ProfileInsignia == null || p.ProfileInsignia.Count == 0 ? "" : p.ProfileInsignia - .Where(x => x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) .FirstOrDefault() == null ? "" : p.ProfileInsignia - .Where(x => x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) - .OrderByDescending(x => x.Year).FirstOrDefault().Insignia, + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) + .OrderByDescending(x => x.Year).FirstOrDefault()?.Insignia, LastInsigniaId = p.ProfileInsignia == null || p.ProfileInsignia.Count == 0 ? Guid.Empty : p.ProfileInsignia - .Where(x => x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) .FirstOrDefault() == null ? Guid.Empty : p.ProfileInsignia - .Where(x => x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) - .OrderByDescending(x => x.Year).FirstOrDefault().InsigniaId, + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) + .OrderByDescending(x => x.Year).FirstOrDefault()?.InsigniaId, Salary = p.Amount, SalaryCondition = p.ProfileSalary == null || p.ProfileSalary.Count == 0 ? 0 : - p.ProfileSalary.Where(x => x.Date != null).Where(x => x.Date.Value <= new DateTime(period.Year, 4, 29)) + p.ProfileSalary.Where(x => x.Date != null).Where(x => x.Date.HasValue && x.Date.Value <= new DateTime(period.Year, 4, 29)) .OrderByDescending(x => x.Order).FirstOrDefault() != null ? p.ProfileSalary - .Where(x => x.Date != null).Where(x => x.Date.Value <= new DateTime(period.Year, 4, 29)) - .OrderByDescending(x => x.Order).FirstOrDefault().Amount : + .Where(x => x.Date != null).Where(x => x.Date.HasValue && x.Date.Value <= new DateTime(period.Year, 4, 29)) + .OrderByDescending(x => x.Order).FirstOrDefault()?.Amount : p.Amount, PosNo = p.PosNo, Gender = p.Gender == null ? null : p.Gender, @@ -3312,12 +3323,12 @@ namespace BMA.EHR.Application.Repositories PositionTypeName = p.PosType, IsHigherLevel = p.ProfileInsignia - .Where(x => x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) .FirstOrDefault() == null ? true : IsHigherLevel(p.ProfileInsignia - .Where(x => x.InsigniaId.Value != - GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != + GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) .OrderByDescending(x => x.Year) .FirstOrDefault(), "ตริตาภรณ์มงกุฎไทย"), @@ -3327,14 +3338,14 @@ namespace BMA.EHR.Application.Repositories p.ProfileSalary.Where(x => x.PositionLevel == "อาวุโส").OrderBy(p => p.Date) .FirstOrDefault() == null ? null : p.ProfileSalary.Where(x => x.PositionLevel == "อาวุโส").OrderBy(p => p.Date) - .FirstOrDefault().Date, + .FirstOrDefault()?.Date, FirstRecvInsigniaYear = p.ProfileInsignia.Count == 0 ? 0 : p.ProfileInsignia - .Where(x => x.InsigniaId.Value == GetInsigniaByName("ประถมาภรณ์มงกุฎไทย").Id) + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value == GetInsigniaByName("ประถมาภรณ์มงกุฎไทย")?.Id) .OrderBy(x => x.Year).FirstOrDefault() == null ? 0 : p.ProfileInsignia - .Where(x => x.InsigniaId.Value == GetInsigniaByName("ประถมาภรณ์มงกุฎไทย").Id) - .OrderBy(x => x.Year).FirstOrDefault().Year, + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value == GetInsigniaByName("ประถมาภรณ์มงกุฎไทย")?.Id) + .OrderBy(x => x.Year).FirstOrDefault()?.Year, PositionSalaryAmount = p.PositionSalaryAmount, Amount = p.Amount, RootId = p.RootId, @@ -3470,8 +3481,9 @@ namespace BMA.EHR.Application.Repositories && p.ProfileInsignia != null && p.ProfileInsignia.Count > 0 && (p.ProfileInsignia.Where(x => - x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id && - x.InsigniaId.Value == GetInsigniaByName("ตริตาภรณ์ช้างเผือก").Id) + x.InsigniaId.HasValue && + x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id && + x.InsigniaId.Value == GetInsigniaByName("ตริตาภรณ์ช้างเผือก")?.Id) .ToList() .Count() == 0) select new @@ -3490,24 +3502,24 @@ namespace BMA.EHR.Application.Repositories ProfileDateAppoint = p.DateAppoint.Value, LastInsignia = p.ProfileInsignia == null || p.ProfileInsignia.Count == 0 ? "" : p.ProfileInsignia - .Where(x => x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) .FirstOrDefault() == null ? "" : p.ProfileInsignia - .Where(x => x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) - .OrderByDescending(x => x.Year).FirstOrDefault().Insignia, + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) + .OrderByDescending(x => x.Year).FirstOrDefault()?.Insignia, LastInsigniaId = p.ProfileInsignia == null || p.ProfileInsignia.Count == 0 ? Guid.Empty : p.ProfileInsignia - .Where(x => x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) .FirstOrDefault() == null ? Guid.Empty : p.ProfileInsignia - .Where(x => x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) - .OrderByDescending(x => x.Year).FirstOrDefault().InsigniaId, + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) + .OrderByDescending(x => x.Year).FirstOrDefault()?.InsigniaId, Salary = p.Amount, SalaryCondition = p.ProfileSalary == null || p.ProfileSalary.Count == 0 ? 0 : - p.ProfileSalary.Where(x => x.Date != null).Where(x => x.Date.Value <= new DateTime(period.Year, 4, 29)) + p.ProfileSalary.Where(x => x.Date != null).Where(x => x.Date.HasValue && x.Date.Value <= new DateTime(period.Year, 4, 29)) .OrderByDescending(x => x.Order).FirstOrDefault() != null ? p.ProfileSalary - .Where(x => x.Date != null).Where(x => x.Date.Value <= new DateTime(period.Year, 4, 29)) - .OrderByDescending(x => x.Order).FirstOrDefault().Amount : + .Where(x => x.Date != null).Where(x => x.Date.HasValue && x.Date.Value <= new DateTime(period.Year, 4, 29)) + .OrderByDescending(x => x.Order).FirstOrDefault()?.Amount : p.Amount, PosNo = p.PosNo, Gender = p.Gender == null ? null : p.Gender, @@ -3517,12 +3529,12 @@ namespace BMA.EHR.Application.Repositories PositionTypeName = p.PosType, IsHigherLevel = p.ProfileInsignia - .Where(x => x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) .FirstOrDefault() == null ? true : IsHigherLevel(p.ProfileInsignia - .Where(x => x.InsigniaId.Value != - GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != + GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) .OrderByDescending(x => x.Year) .FirstOrDefault(), "ตริตาภรณ์ช้างเผือก"), @@ -3532,14 +3544,14 @@ namespace BMA.EHR.Application.Repositories p.ProfileSalary.Where(x => x.PositionLevel == "ชำนาญการ").OrderBy(p => p.Date) .FirstOrDefault() == null ? null : p.ProfileSalary.Where(x => x.PositionLevel == "ชำนาญการ").OrderBy(p => p.Date) - .FirstOrDefault().Date, + .FirstOrDefault()?.Date, FirstRecvInsigniaYear = p.ProfileInsignia.Count == 0 ? 0 : p.ProfileInsignia - .Where(x => x.InsigniaId.Value == GetInsigniaByName("ประถมาภรณ์มงกุฎไทย").Id) + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value == GetInsigniaByName("ประถมาภรณ์มงกุฎไทย")?.Id) .OrderBy(x => x.Year).FirstOrDefault() == null ? 0 : p.ProfileInsignia - .Where(x => x.InsigniaId.Value == GetInsigniaByName("ประถมาภรณ์มงกุฎไทย").Id) - .OrderBy(x => x.Year).FirstOrDefault().Year, + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value == GetInsigniaByName("ประถมาภรณ์มงกุฎไทย")?.Id) + .OrderBy(x => x.Year).FirstOrDefault()?.Year, PositionSalaryAmount = p.PositionSalaryAmount, Amount = p.Amount, RootId = p.RootId, @@ -3628,8 +3640,9 @@ namespace BMA.EHR.Application.Repositories && p.ProfileInsignia != null && p.ProfileInsignia.Count > 0 && (p.ProfileInsignia.Where(x => - x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id && - x.InsigniaId.Value == GetInsigniaByName("ทวีติยาภรณ์มงกุฎไทย").Id) + x.InsigniaId.HasValue && + x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id && + x.InsigniaId.Value == GetInsigniaByName("ทวีติยาภรณ์มงกุฎไทย")?.Id) .ToList() .Count() == 0) select new @@ -3648,24 +3661,24 @@ namespace BMA.EHR.Application.Repositories ProfileDateAppoint = p.DateAppoint.Value, LastInsignia = p.ProfileInsignia == null || p.ProfileInsignia.Count == 0 ? "" : p.ProfileInsignia - .Where(x => x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) .FirstOrDefault() == null ? "" : p.ProfileInsignia - .Where(x => x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) - .OrderByDescending(x => x.Year).FirstOrDefault().Insignia, + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) + .OrderByDescending(x => x.Year).FirstOrDefault()?.Insignia, LastInsigniaId = p.ProfileInsignia == null || p.ProfileInsignia.Count == 0 ? Guid.Empty : p.ProfileInsignia - .Where(x => x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) .FirstOrDefault() == null ? Guid.Empty : p.ProfileInsignia - .Where(x => x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) - .OrderByDescending(x => x.Year).FirstOrDefault().InsigniaId, + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) + .OrderByDescending(x => x.Year).FirstOrDefault()?.InsigniaId, Salary = p.Amount, SalaryCondition = p.ProfileSalary == null || p.ProfileSalary.Count == 0 ? 0 : - p.ProfileSalary.Where(x => x.Date != null).Where(x => x.Date.Value <= new DateTime(period.Year, 4, 29)) + p.ProfileSalary.Where(x => x.Date != null).Where(x => x.Date.HasValue && x.Date.Value <= new DateTime(period.Year, 4, 29)) .OrderByDescending(x => x.Order).FirstOrDefault() != null ? p.ProfileSalary - .Where(x => x.Date != null).Where(x => x.Date.Value <= new DateTime(period.Year, 4, 29)) - .OrderByDescending(x => x.Order).FirstOrDefault().Amount : + .Where(x => x.Date != null).Where(x => x.Date.HasValue && x.Date.Value <= new DateTime(period.Year, 4, 29)) + .OrderByDescending(x => x.Order).FirstOrDefault()?.Amount : p.Amount, PosNo = p.PosNo, Gender = p.Gender == null ? null : p.Gender, @@ -3675,12 +3688,12 @@ namespace BMA.EHR.Application.Repositories PositionTypeName = p.PosType, IsHigherLevel = p.ProfileInsignia - .Where(x => x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) .FirstOrDefault() == null ? true : IsHigherLevel(p.ProfileInsignia - .Where(x => x.InsigniaId.Value != - GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != + GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) .OrderByDescending(x => x.Year) .FirstOrDefault(), "ทวีติยาภรณ์มงกุฎไทย"), @@ -3690,14 +3703,14 @@ namespace BMA.EHR.Application.Repositories p.ProfileSalary.Where(x => x.PositionLevel == "ชำนาญการ").OrderBy(p => p.Date) .FirstOrDefault() == null ? null : p.ProfileSalary.Where(x => x.PositionLevel == "ชำนาญการ").OrderBy(p => p.Date) - .FirstOrDefault().Date, + .FirstOrDefault()?.Date, FirstRecvInsigniaYear = p.ProfileInsignia.Count == 0 ? 0 : p.ProfileInsignia - .Where(x => x.InsigniaId.Value == GetInsigniaByName("ประถมาภรณ์มงกุฎไทย").Id) + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value == GetInsigniaByName("ประถมาภรณ์มงกุฎไทย")?.Id) .OrderBy(x => x.Year).FirstOrDefault() == null ? 0 : p.ProfileInsignia - .Where(x => x.InsigniaId.Value == GetInsigniaByName("ประถมาภรณ์มงกุฎไทย").Id) - .OrderBy(x => x.Year).FirstOrDefault().Year, + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value == GetInsigniaByName("ประถมาภรณ์มงกุฎไทย")?.Id) + .OrderBy(x => x.Year).FirstOrDefault()?.Year, PositionSalaryAmount = p.PositionSalaryAmount, Amount = p.Amount, RootId = p.RootId, @@ -3789,8 +3802,9 @@ namespace BMA.EHR.Application.Repositories && p.ProfileInsignia != null && p.ProfileInsignia.Count > 0 && (p.ProfileInsignia.Where(x => - x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id && - x.InsigniaId.Value == GetInsigniaByName("ทวีติยาภรณ์ช้างเผือก").Id) + x.InsigniaId.HasValue && + x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id && + x.InsigniaId.Value == GetInsigniaByName("ทวีติยาภรณ์ช้างเผือก")?.Id) .ToList() .Count() == 0) select new @@ -3809,24 +3823,24 @@ namespace BMA.EHR.Application.Repositories ProfileDateAppoint = p.DateAppoint.Value, LastInsignia = p.ProfileInsignia == null || p.ProfileInsignia.Count == 0 ? "" : p.ProfileInsignia - .Where(x => x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) .FirstOrDefault() == null ? "" : p.ProfileInsignia - .Where(x => x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) - .OrderByDescending(x => x.Year).FirstOrDefault().Insignia, + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) + .OrderByDescending(x => x.Year).FirstOrDefault()?.Insignia, LastInsigniaId = p.ProfileInsignia == null || p.ProfileInsignia.Count == 0 ? Guid.Empty : p.ProfileInsignia - .Where(x => x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) .FirstOrDefault() == null ? Guid.Empty : p.ProfileInsignia - .Where(x => x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) - .OrderByDescending(x => x.Year).FirstOrDefault().InsigniaId, + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) + .OrderByDescending(x => x.Year).FirstOrDefault()?.InsigniaId, Salary = p.Amount, SalaryCondition = p.ProfileSalary == null || p.ProfileSalary.Count == 0 ? 0 : - p.ProfileSalary.Where(x => x.Date != null).Where(x => x.Date.Value <= new DateTime(period.Year - 5, 4, 29)) + p.ProfileSalary.Where(x => x.Date != null).Where(x => x.Date.HasValue && x.Date.Value <= new DateTime(period.Year - 5, 4, 29)) .OrderByDescending(x => x.Order).FirstOrDefault() != null ? p.ProfileSalary - .Where(x => x.Date != null).Where(x => x.Date.Value <= new DateTime(period.Year - 5, 4, 29)) - .OrderByDescending(x => x.Order).FirstOrDefault().Amount : + .Where(x => x.Date != null).Where(x => x.Date.HasValue && x.Date.Value <= new DateTime(period.Year - 5, 4, 29)) + .OrderByDescending(x => x.Order).FirstOrDefault()?.Amount : p.Amount, PosNo = p.PosNo, Gender = p.Gender == null ? null : p.Gender, @@ -3836,12 +3850,12 @@ namespace BMA.EHR.Application.Repositories PositionTypeName = p.PosType, IsHigherLevel = p.ProfileInsignia - .Where(x => x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) .FirstOrDefault() == null ? true : IsHigherLevel(p.ProfileInsignia - .Where(x => x.InsigniaId.Value != - GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != + GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) .OrderByDescending(x => x.Year) .FirstOrDefault(), "ทวีติยาภรณ์ช้างเผือก"), @@ -3851,14 +3865,14 @@ namespace BMA.EHR.Application.Repositories p.ProfileSalary.Where(x => x.PositionLevel == "ชำนาญการ").OrderBy(p => p.Date) .FirstOrDefault() == null ? null : p.ProfileSalary.Where(x => x.PositionLevel == "ชำนาญการ").OrderBy(p => p.Date) - .FirstOrDefault().Date, + .FirstOrDefault()?.Date, FirstRecvInsigniaYear = p.ProfileInsignia.Count == 0 ? 0 : p.ProfileInsignia - .Where(x => x.InsigniaId.Value == GetInsigniaByName("ประถมาภรณ์มงกุฎไทย").Id) + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value == GetInsigniaByName("ประถมาภรณ์มงกุฎไทย")?.Id) .OrderBy(x => x.Year).FirstOrDefault() == null ? 0 : p.ProfileInsignia - .Where(x => x.InsigniaId.Value == GetInsigniaByName("ประถมาภรณ์มงกุฎไทย").Id) - .OrderBy(x => x.Year).FirstOrDefault().Year, + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value == GetInsigniaByName("ประถมาภรณ์มงกุฎไทย")?.Id) + .OrderBy(x => x.Year).FirstOrDefault()?.Year, PositionSalaryAmount = p.PositionSalaryAmount, Amount = p.Amount, RootId = p.RootId, @@ -4034,8 +4048,9 @@ namespace BMA.EHR.Application.Repositories && p.ProfileInsignia != null && p.ProfileInsignia.Count > 0 && (p.ProfileInsignia.Where(x => - x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id && - x.InsigniaId.Value == GetInsigniaByName("ทวีติยาภรณ์ช้างเผือก").Id) + x.InsigniaId.HasValue && + x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id && + x.InsigniaId.Value == GetInsigniaByName("ทวีติยาภรณ์ช้างเผือก")?.Id) .ToList() .Count() == 0) select new @@ -4054,24 +4069,24 @@ namespace BMA.EHR.Application.Repositories ProfileDateAppoint = p.DateAppoint.Value, LastInsignia = p.ProfileInsignia == null || p.ProfileInsignia.Count == 0 ? "" : p.ProfileInsignia - .Where(x => x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) .FirstOrDefault() == null ? "" : p.ProfileInsignia - .Where(x => x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) - .OrderByDescending(x => x.Year).FirstOrDefault().Insignia, + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) + .OrderByDescending(x => x.Year).FirstOrDefault()?.Insignia, LastInsigniaId = p.ProfileInsignia == null || p.ProfileInsignia.Count == 0 ? Guid.Empty : p.ProfileInsignia - .Where(x => x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) .FirstOrDefault() == null ? Guid.Empty : p.ProfileInsignia - .Where(x => x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) - .OrderByDescending(x => x.Year).FirstOrDefault().InsigniaId, + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) + .OrderByDescending(x => x.Year).FirstOrDefault()?.InsigniaId, Salary = p.Amount, SalaryCondition = p.ProfileSalary == null || p.ProfileSalary.Count == 0 ? 0 : - p.ProfileSalary.Where(x => x.Date != null).Where(x => x.Date.Value <= new DateTime(period.Year, 4, 29)) + p.ProfileSalary.Where(x => x.Date != null).Where(x => x.Date.HasValue && x.Date.Value <= new DateTime(period.Year, 4, 29)) .OrderByDescending(x => x.Order).FirstOrDefault() != null ? p.ProfileSalary - .Where(x => x.Date != null).Where(x => x.Date.Value <= new DateTime(period.Year, 4, 29)) - .OrderByDescending(x => x.Order).FirstOrDefault().Amount : + .Where(x => x.Date != null).Where(x => x.Date.HasValue && x.Date.Value <= new DateTime(period.Year, 4, 29)) + .OrderByDescending(x => x.Order).FirstOrDefault()?.Amount : p.Amount, PosNo = p.PosNo, Gender = p.Gender == null ? null : p.Gender, @@ -4081,12 +4096,12 @@ namespace BMA.EHR.Application.Repositories PositionTypeName = p.PosType, IsHigherLevel = p.ProfileInsignia - .Where(x => x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) .FirstOrDefault() == null ? true : IsHigherLevel(p.ProfileInsignia - .Where(x => x.InsigniaId.Value != - GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != + GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) .OrderByDescending(x => x.Year) .FirstOrDefault(), "ทวีติยาภรณ์ช้างเผือก"), @@ -4096,14 +4111,14 @@ namespace BMA.EHR.Application.Repositories p.ProfileSalary.Where(x => x.PositionLevel == "ชำนาญการพิเศษ").OrderBy(p => p.Date) .FirstOrDefault() == null ? null : p.ProfileSalary.Where(x => x.PositionLevel == "ชำนาญการพิเศษ").OrderBy(p => p.Date) - .FirstOrDefault().Date, + .FirstOrDefault()?.Date, FirstRecvInsigniaYear = p.ProfileInsignia.Count == 0 ? 0 : p.ProfileInsignia - .Where(x => x.InsigniaId.Value == GetInsigniaByName("ประถมาภรณ์มงกุฎไทย").Id) + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value == GetInsigniaByName("ประถมาภรณ์มงกุฎไทย")?.Id) .OrderBy(x => x.Year).FirstOrDefault() == null ? 0 : p.ProfileInsignia - .Where(x => x.InsigniaId.Value == GetInsigniaByName("ประถมาภรณ์มงกุฎไทย").Id) - .OrderBy(x => x.Year).FirstOrDefault().Year, + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value == GetInsigniaByName("ประถมาภรณ์มงกุฎไทย")?.Id) + .OrderBy(x => x.Year).FirstOrDefault()?.Year, PositionSalaryAmount = p.PositionSalaryAmount, Amount = p.Amount, RootId = p.RootId, @@ -4175,8 +4190,9 @@ namespace BMA.EHR.Application.Repositories && p.ProfileInsignia != null && p.ProfileInsignia.Count > 0 && (p.ProfileInsignia.Where(x => - x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id && - x.InsigniaId.Value == GetInsigniaByName("ประถมาภรณ์มงกุฎไทย").Id) + x.InsigniaId.HasValue && + x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id && + x.InsigniaId.Value == GetInsigniaByName("ประถมาภรณ์มงกุฎไทย")?.Id) .ToList() .Count() == 0) select new @@ -4195,24 +4211,24 @@ namespace BMA.EHR.Application.Repositories ProfileDateAppoint = p.DateAppoint.Value, LastInsignia = p.ProfileInsignia == null || p.ProfileInsignia.Count == 0 ? "" : p.ProfileInsignia - .Where(x => x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) .FirstOrDefault() == null ? "" : p.ProfileInsignia - .Where(x => x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) - .OrderByDescending(x => x.Year).FirstOrDefault().Insignia, + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) + .OrderByDescending(x => x.Year).FirstOrDefault()?.Insignia, LastInsigniaId = p.ProfileInsignia == null || p.ProfileInsignia.Count == 0 ? Guid.Empty : p.ProfileInsignia - .Where(x => x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) .FirstOrDefault() == null ? Guid.Empty : p.ProfileInsignia - .Where(x => x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) - .OrderByDescending(x => x.Year).FirstOrDefault().InsigniaId, + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) + .OrderByDescending(x => x.Year).FirstOrDefault()?.InsigniaId, Salary = p.Amount, SalaryCondition = p.ProfileSalary == null || p.ProfileSalary.Count == 0 ? 0 : - p.ProfileSalary.Where(x => x.Date != null).Where(x => x.Date.Value <= new DateTime(period.Year, 4, 29)) + p.ProfileSalary.Where(x => x.Date != null).Where(x => x.Date.HasValue && x.Date.Value <= new DateTime(period.Year, 4, 29)) .OrderByDescending(x => x.Order).FirstOrDefault() != null ? p.ProfileSalary - .Where(x => x.Date != null).Where(x => x.Date.Value <= new DateTime(period.Year, 4, 29)) - .OrderByDescending(x => x.Order).FirstOrDefault().Amount : + .Where(x => x.Date != null).Where(x => x.Date.HasValue && x.Date.Value <= new DateTime(period.Year, 4, 29)) + .OrderByDescending(x => x.Order).FirstOrDefault()?.Amount : p.Amount, PosNo = p.PosNo, Gender = p.Gender == null ? null : p.Gender, @@ -4222,12 +4238,12 @@ namespace BMA.EHR.Application.Repositories PositionTypeName = p.PosType, IsHigherLevel = p.ProfileInsignia - .Where(x => x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) .FirstOrDefault() == null ? true : IsHigherLevel(p.ProfileInsignia - .Where(x => x.InsigniaId.Value != - GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != + GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) .OrderByDescending(x => x.Year) .FirstOrDefault(), "ประถมาภรณ์มงกุฎไทย"), @@ -4238,14 +4254,14 @@ namespace BMA.EHR.Application.Repositories p.ProfileSalary.Where(x => x.PositionLevel == "ชำนาญการพิเศษ").OrderBy(p => p.Date) .FirstOrDefault() == null ? null : p.ProfileSalary.Where(x => x.PositionLevel == "ชำนาญการพิเศษ").OrderBy(p => p.Date) - .FirstOrDefault().Date, + .FirstOrDefault()?.Date, FirstRecvInsigniaYear = p.ProfileInsignia.Count == 0 ? 0 : p.ProfileInsignia - .Where(x => x.InsigniaId.Value == GetInsigniaByName("ทวีติยาภรณ์ช้างเผือก").Id) + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value == GetInsigniaByName("ทวีติยาภรณ์ช้างเผือก")?.Id) .OrderBy(x => x.Year).FirstOrDefault() == null ? 0 : p.ProfileInsignia - .Where(x => x.InsigniaId.Value == GetInsigniaByName("ทวีติยาภรณ์ช้างเผือก").Id) - .OrderBy(x => x.Year).FirstOrDefault().Year, + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value == GetInsigniaByName("ทวีติยาภรณ์ช้างเผือก")?.Id) + .OrderBy(x => x.Year).FirstOrDefault()?.Year, PositionSalaryAmount = p.PositionSalaryAmount, Amount = p.Amount, RootId = p.RootId, @@ -4409,8 +4425,9 @@ namespace BMA.EHR.Application.Repositories && p.ProfileInsignia != null && p.ProfileInsignia.Count > 0 && (p.ProfileInsignia.Where(x => - x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id && - x.InsigniaId.Value == GetInsigniaByName("ทวีติยาภรณ์ช้างเผือก").Id) + x.InsigniaId.HasValue && + x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id && + x.InsigniaId.Value == GetInsigniaByName("ทวีติยาภรณ์ช้างเผือก")?.Id) .ToList() .Count() == 0) select new @@ -4429,24 +4446,24 @@ namespace BMA.EHR.Application.Repositories ProfileDateAppoint = p.DateAppoint.Value, LastInsignia = p.ProfileInsignia == null || p.ProfileInsignia.Count == 0 ? "" : p.ProfileInsignia - .Where(x => x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) .FirstOrDefault() == null ? "" : p.ProfileInsignia - .Where(x => x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) - .OrderByDescending(x => x.Year).FirstOrDefault().Insignia, + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) + .OrderByDescending(x => x.Year).FirstOrDefault()?.Insignia, LastInsigniaId = p.ProfileInsignia == null || p.ProfileInsignia.Count == 0 ? Guid.Empty : p.ProfileInsignia - .Where(x => x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) .FirstOrDefault() == null ? Guid.Empty : p.ProfileInsignia - .Where(x => x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) - .OrderByDescending(x => x.Year).FirstOrDefault().InsigniaId, + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) + .OrderByDescending(x => x.Year).FirstOrDefault()?.InsigniaId, Salary = p.Amount, SalaryCondition = p.ProfileSalary == null || p.ProfileSalary.Count == 0 ? 0 : - p.ProfileSalary.Where(x => x.Date != null).Where(x => x.Date.Value <= new DateTime(period.Year, 4, 29)) + p.ProfileSalary.Where(x => x.Date != null).Where(x => x.Date.HasValue && x.Date.Value <= new DateTime(period.Year, 4, 29)) .OrderByDescending(x => x.Order).FirstOrDefault() != null ? p.ProfileSalary - .Where(x => x.Date != null).Where(x => x.Date.Value <= new DateTime(period.Year, 4, 29)) - .OrderByDescending(x => x.Order).FirstOrDefault().Amount : + .Where(x => x.Date != null).Where(x => x.Date.HasValue && x.Date.Value <= new DateTime(period.Year, 4, 29)) + .OrderByDescending(x => x.Order).FirstOrDefault()?.Amount : p.Amount, PosNo = p.PosNo, Gender = p.Gender == null ? null : p.Gender, @@ -4456,12 +4473,12 @@ namespace BMA.EHR.Application.Repositories PositionTypeName = p.PosType, IsHigherLevel = p.ProfileInsignia - .Where(x => x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) .FirstOrDefault() == null ? true : IsHigherLevel(p.ProfileInsignia - .Where(x => x.InsigniaId.Value != - GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != + GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) .OrderByDescending(x => x.Year) .FirstOrDefault(), "ทวีติยาภรณ์ช้างเผือก"), @@ -4471,14 +4488,14 @@ namespace BMA.EHR.Application.Repositories p.ProfileSalary.Where(x => x.PositionLevel == "เชี่ยวชาญ").OrderBy(p => p.Date) .FirstOrDefault() == null ? null : p.ProfileSalary.Where(x => x.PositionLevel == "เชี่ยวชาญ").OrderBy(p => p.Date) - .FirstOrDefault().Date, + .FirstOrDefault()?.Date, FirstRecvInsigniaYear = p.ProfileInsignia.Count == 0 ? 0 : p.ProfileInsignia - .Where(x => x.InsigniaId.Value == GetInsigniaByName("ทวีติยาภรณ์ช้างเผือก").Id) + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value == GetInsigniaByName("ทวีติยาภรณ์ช้างเผือก")?.Id) .OrderBy(x => x.Year).FirstOrDefault() == null ? 0 : p.ProfileInsignia - .Where(x => x.InsigniaId.Value == GetInsigniaByName("ทวีติยาภรณ์ช้างเผือก").Id) - .OrderBy(x => x.Year).FirstOrDefault().Year, + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value == GetInsigniaByName("ทวีติยาภรณ์ช้างเผือก")?.Id) + .OrderBy(x => x.Year).FirstOrDefault()?.Year, PositionSalaryAmount = p.PositionSalaryAmount, Amount = p.Amount, RootId = p.RootId, @@ -4568,8 +4585,9 @@ namespace BMA.EHR.Application.Repositories && p.ProfileInsignia != null && p.ProfileInsignia.Count > 0 && (p.ProfileInsignia.Where(x => - x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id && - x.InsigniaId.Value == GetInsigniaByName("ประถมาภรณ์มงกุฎไทย").Id) + x.InsigniaId.HasValue && + x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id && + x.InsigniaId.Value == GetInsigniaByName("ประถมาภรณ์มงกุฎไทย")?.Id) .ToList() .Count() == 0) select new @@ -4588,24 +4606,24 @@ namespace BMA.EHR.Application.Repositories ProfileDateAppoint = p.DateAppoint.Value, LastInsignia = p.ProfileInsignia == null || p.ProfileInsignia.Count == 0 ? "" : p.ProfileInsignia - .Where(x => x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) .FirstOrDefault() == null ? "" : p.ProfileInsignia - .Where(x => x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) - .OrderByDescending(x => x.Year).FirstOrDefault().Insignia, + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) + .OrderByDescending(x => x.Year).FirstOrDefault()?.Insignia, LastInsigniaId = p.ProfileInsignia == null || p.ProfileInsignia.Count == 0 ? Guid.Empty : p.ProfileInsignia - .Where(x => x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) .FirstOrDefault() == null ? Guid.Empty : p.ProfileInsignia - .Where(x => x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) - .OrderByDescending(x => x.Year).FirstOrDefault().InsigniaId, + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) + .OrderByDescending(x => x.Year).FirstOrDefault()?.InsigniaId, Salary = p.Amount, SalaryCondition = p.ProfileSalary == null || p.ProfileSalary.Count == 0 ? 0 : - p.ProfileSalary.Where(x => x.Date != null).Where(x => x.Date.Value <= new DateTime(period.Year, 4, 29)) + p.ProfileSalary.Where(x => x.Date != null).Where(x => x.Date.HasValue && x.Date.Value <= new DateTime(period.Year, 4, 29)) .OrderByDescending(x => x.Order).FirstOrDefault() != null ? p.ProfileSalary - .Where(x => x.Date != null).Where(x => x.Date.Value <= new DateTime(period.Year, 4, 29)) - .OrderByDescending(x => x.Order).FirstOrDefault().Amount : + .Where(x => x.Date != null).Where(x => x.Date.HasValue && x.Date.Value <= new DateTime(period.Year, 4, 29)) + .OrderByDescending(x => x.Order).FirstOrDefault()?.Amount : p.Amount, PosNo = p.PosNo, Gender = p.Gender == null ? null : p.Gender, @@ -4615,12 +4633,12 @@ namespace BMA.EHR.Application.Repositories PositionTypeName = p.PosType, IsHigherLevel = p.ProfileInsignia - .Where(x => x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) .FirstOrDefault() == null ? true : IsHigherLevel(p.ProfileInsignia - .Where(x => x.InsigniaId.Value != - GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != + GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) .OrderByDescending(x => x.Year) .FirstOrDefault(), "ประถมาภรณ์มงกุฎไทย"), @@ -4631,14 +4649,14 @@ namespace BMA.EHR.Application.Repositories p.ProfileSalary.Where(x => x.PositionLevel == "เชี่ยวชาญ").OrderBy(p => p.Date) .FirstOrDefault() == null ? null : p.ProfileSalary.Where(x => x.PositionLevel == "เชี่ยวชาญ").OrderBy(p => p.Date) - .FirstOrDefault().Date, + .FirstOrDefault()?.Date, FirstRecvInsigniaYear = p.ProfileInsignia.Count == 0 ? 0 : p.ProfileInsignia - .Where(x => x.InsigniaId.Value == GetInsigniaByName("ทวีติยาภรณ์ช้างเผือก").Id) + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value == GetInsigniaByName("ทวีติยาภรณ์ช้างเผือก")?.Id) .OrderBy(x => x.Year).FirstOrDefault() == null ? 0 : p.ProfileInsignia - .Where(x => x.InsigniaId.Value == GetInsigniaByName("ทวีติยาภรณ์ช้างเผือก").Id) - .OrderBy(x => x.Year).FirstOrDefault().Year, + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value == GetInsigniaByName("ทวีติยาภรณ์ช้างเผือก")?.Id) + .OrderBy(x => x.Year).FirstOrDefault()?.Year, PositionSalaryAmount = p.PositionSalaryAmount, Amount = p.Amount, RootId = p.RootId, @@ -4729,8 +4747,9 @@ namespace BMA.EHR.Application.Repositories && p.ProfileInsignia != null && p.ProfileInsignia.Count > 0 && (p.ProfileInsignia.Where(x => - x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id && - x.InsigniaId.Value == GetInsigniaByName("ประถมาภรณ์ช้างเผือก").Id) + x.InsigniaId.HasValue && + x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id && + x.InsigniaId.Value == GetInsigniaByName("ประถมาภรณ์ช้างเผือก")?.Id) .ToList() .Count() == 0) select new @@ -4749,24 +4768,24 @@ namespace BMA.EHR.Application.Repositories ProfileDateAppoint = p.DateAppoint.Value, LastInsignia = p.ProfileInsignia == null || p.ProfileInsignia.Count == 0 ? "" : p.ProfileInsignia - .Where(x => x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) .FirstOrDefault() == null ? "" : p.ProfileInsignia - .Where(x => x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) - .OrderByDescending(x => x.Year).FirstOrDefault().Insignia, + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) + .OrderByDescending(x => x.Year).FirstOrDefault()?.Insignia, LastInsigniaId = p.ProfileInsignia == null || p.ProfileInsignia.Count == 0 ? Guid.Empty : p.ProfileInsignia - .Where(x => x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) .FirstOrDefault() == null ? Guid.Empty : p.ProfileInsignia - .Where(x => x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) - .OrderByDescending(x => x.Year).FirstOrDefault().InsigniaId, + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) + .OrderByDescending(x => x.Year).FirstOrDefault()?.InsigniaId, Salary = p.Amount, SalaryCondition = p.ProfileSalary == null || p.ProfileSalary.Count == 0 ? 0 : - p.ProfileSalary.Where(x => x.Date != null).Where(x => x.Date.Value <= new DateTime(period.Year, 4, 29)) + p.ProfileSalary.Where(x => x.Date != null).Where(x => x.Date.HasValue && x.Date.Value <= new DateTime(period.Year, 4, 29)) .OrderByDescending(x => x.Order).FirstOrDefault() != null ? p.ProfileSalary - .Where(x => x.Date != null).Where(x => x.Date.Value <= new DateTime(period.Year, 4, 29)) - .OrderByDescending(x => x.Order).FirstOrDefault().Amount : + .Where(x => x.Date != null).Where(x => x.Date.HasValue && x.Date.Value <= new DateTime(period.Year, 4, 29)) + .OrderByDescending(x => x.Order).FirstOrDefault()?.Amount : p.Amount, PosNo = p.PosNo, Gender = p.Gender == null ? null : p.Gender, @@ -4776,12 +4795,12 @@ namespace BMA.EHR.Application.Repositories PositionTypeName = p.PosType, IsHigherLevel = p.ProfileInsignia - .Where(x => x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) .FirstOrDefault() == null ? true : IsHigherLevel(p.ProfileInsignia - .Where(x => x.InsigniaId.Value != - GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != + GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) .OrderByDescending(x => x.Year) .FirstOrDefault(), "ประถมาภรณ์ช้างเผือก"), @@ -4791,14 +4810,14 @@ namespace BMA.EHR.Application.Repositories p.ProfileSalary.Where(x => x.PositionLevel == "เชี่ยวชาญ").OrderBy(p => p.Date) .FirstOrDefault() == null ? null : p.ProfileSalary.Where(x => x.PositionLevel == "เชี่ยวชาญ").OrderBy(p => p.Date) - .FirstOrDefault().Date, + .FirstOrDefault()?.Date, FirstRecvInsigniaYear = p.ProfileInsignia.Count == 0 ? 0 : p.ProfileInsignia - .Where(x => x.InsigniaId.Value == GetInsigniaByName("ประถมาภรณ์ช้างเผือก").Id) + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value == GetInsigniaByName("ประถมาภรณ์ช้างเผือก")?.Id) .OrderBy(x => x.Year).FirstOrDefault() == null ? 0 : p.ProfileInsignia - .Where(x => x.InsigniaId.Value == GetInsigniaByName("ประถมาภรณ์ช้างเผือก").Id) - .OrderBy(x => x.Year).FirstOrDefault().Year, + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value == GetInsigniaByName("ประถมาภรณ์ช้างเผือก")?.Id) + .OrderBy(x => x.Year).FirstOrDefault()?.Year, PositionSalaryAmount = p.PositionSalaryAmount, Amount = p.Amount, RootId = p.RootId, @@ -4972,8 +4991,9 @@ namespace BMA.EHR.Application.Repositories && p.ProfileInsignia != null && p.ProfileInsignia.Count > 0 && (p.ProfileInsignia.Where(x => - x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id && - x.InsigniaId.Value == GetInsigniaByName("ประถมาภรณ์ช้างเผือก").Id) + x.InsigniaId.HasValue && + x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id && + x.InsigniaId.Value == GetInsigniaByName("ประถมาภรณ์ช้างเผือก")?.Id) .ToList() .Count() == 0) select new @@ -4992,25 +5012,25 @@ namespace BMA.EHR.Application.Repositories ProfileDateAppoint = p.DateAppoint.Value, LastInsignia = p.ProfileInsignia == null || p.ProfileInsignia.Count == 0 ? "" : p.ProfileInsignia - .Where(x => x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) .FirstOrDefault() == null ? "" : p.ProfileInsignia - .Where(x => x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) - .OrderByDescending(x => x.Year).FirstOrDefault().Insignia, + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) + .OrderByDescending(x => x.Year).FirstOrDefault()?.Insignia, LastInsigniaId = p.ProfileInsignia == null || p.ProfileInsignia.Count == 0 ? Guid.Empty : p.ProfileInsignia - .Where(x => x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) .FirstOrDefault() == null ? Guid.Empty : p.ProfileInsignia - .Where(x => x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) - .OrderByDescending(x => x.Year).FirstOrDefault().InsigniaId, + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) + .OrderByDescending(x => x.Year).FirstOrDefault()?.InsigniaId, Salary = p.Amount, SalaryPosition = p.PositionSalaryAmount, SalaryCondition = p.ProfileSalary == null || p.ProfileSalary.Count == 0 ? 0 : - p.ProfileSalary.Where(x => x.Date != null).Where(x => x.Date.Value <= new DateTime(period.Year, 4, 29)) + p.ProfileSalary.Where(x => x.Date != null).Where(x => x.Date.HasValue && x.Date.Value <= new DateTime(period.Year, 4, 29)) .OrderByDescending(x => x.Order).FirstOrDefault() != null ? p.ProfileSalary - .Where(x => x.Date != null).Where(x => x.Date.Value <= new DateTime(period.Year, 4, 29)) - .OrderByDescending(x => x.Order).FirstOrDefault().Amount : + .Where(x => x.Date != null).Where(x => x.Date.HasValue && x.Date.Value <= new DateTime(period.Year, 4, 29)) + .OrderByDescending(x => x.Order).FirstOrDefault()?.Amount : p.Amount, PosNo = p.PosNo, Gender = p.Gender == null ? null : p.Gender, @@ -5020,12 +5040,12 @@ namespace BMA.EHR.Application.Repositories PositionTypeName = p.PosType, IsHigherLevel = p.ProfileInsignia - .Where(x => x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) .FirstOrDefault() == null ? true : IsHigherLevel(p.ProfileInsignia - .Where(x => x.InsigniaId.Value != - GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != + GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) .OrderByDescending(x => x.Year) .FirstOrDefault(), "ประถมาภรณ์ช้างเผือก"), @@ -5035,14 +5055,14 @@ namespace BMA.EHR.Application.Repositories p.ProfileSalary.Where(x => x.PositionLevel == "ทรงคุณวุฒิ").OrderBy(p => p.Date) .FirstOrDefault() == null ? null : p.ProfileSalary.Where(x => x.PositionLevel == "ทรงคุณวุฒิ").OrderBy(p => p.Date) - .FirstOrDefault().Date, + .FirstOrDefault()?.Date, FirstRecvInsigniaYear = p.ProfileInsignia.Count == 0 ? 0 : p.ProfileInsignia - .Where(x => x.InsigniaId.Value == GetInsigniaByName("ประถมาภรณ์มงกุฎไทย").Id) + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value == GetInsigniaByName("ประถมาภรณ์มงกุฎไทย")?.Id) .OrderBy(x => x.Year).FirstOrDefault() == null ? 0 : p.ProfileInsignia - .Where(x => x.InsigniaId.Value == GetInsigniaByName("ประถมาภรณ์มงกุฎไทย").Id) - .OrderBy(x => x.Year).FirstOrDefault().Year, + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value == GetInsigniaByName("ประถมาภรณ์มงกุฎไทย")?.Id) + .OrderBy(x => x.Year).FirstOrDefault()?.Year, PositionSalaryAmount = p.PositionSalaryAmount, Amount = p.Amount, RootId = p.RootId, @@ -5139,8 +5159,9 @@ namespace BMA.EHR.Application.Repositories && p.ProfileInsignia != null && p.ProfileInsignia.Count > 0 && (p.ProfileInsignia.Where(x => - x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id && - x.InsigniaId.Value == GetInsigniaByName("มหาวชิรมงกุฎ").Id) + x.InsigniaId.HasValue && + x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id && + x.InsigniaId.Value == GetInsigniaByName("มหาวชิรมงกุฎ")?.Id) .ToList() .Count() == 0) select new @@ -5159,25 +5180,25 @@ namespace BMA.EHR.Application.Repositories ProfileDateAppoint = p.DateAppoint.Value, LastInsignia = p.ProfileInsignia == null || p.ProfileInsignia.Count == 0 ? "" : p.ProfileInsignia - .Where(x => x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) .FirstOrDefault() == null ? "" : p.ProfileInsignia - .Where(x => x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) - .OrderByDescending(x => x.Year).FirstOrDefault().Insignia, + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) + .OrderByDescending(x => x.Year).FirstOrDefault()?.Insignia, LastInsigniaId = p.ProfileInsignia == null || p.ProfileInsignia.Count == 0 ? Guid.Empty : p.ProfileInsignia - .Where(x => x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) .FirstOrDefault() == null ? Guid.Empty : p.ProfileInsignia - .Where(x => x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) - .OrderByDescending(x => x.Year).FirstOrDefault().InsigniaId, + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) + .OrderByDescending(x => x.Year).FirstOrDefault()?.InsigniaId, Salary = p.Amount, SalaryPosition = p.PositionSalaryAmount, SalaryCondition = p.ProfileSalary == null || p.ProfileSalary.Count == 0 ? 0 : - p.ProfileSalary.Where(x => x.Date != null).Where(x => x.Date.Value <= new DateTime(period.Year, 4, 29)) + p.ProfileSalary.Where(x => x.Date != null).Where(x => x.Date.HasValue && x.Date.Value <= new DateTime(period.Year, 4, 29)) .OrderByDescending(x => x.Order).FirstOrDefault() != null ? p.ProfileSalary - .Where(x => x.Date != null).Where(x => x.Date.Value <= new DateTime(period.Year, 4, 29)) - .OrderByDescending(x => x.Order).FirstOrDefault().Amount : + .Where(x => x.Date != null).Where(x => x.Date.HasValue && x.Date.Value <= new DateTime(period.Year, 4, 29)) + .OrderByDescending(x => x.Order).FirstOrDefault()?.Amount : p.Amount, PosNo = p.PosNo == null ? "" : p.PosNo, Gender = p.Gender == null ? null : p.Gender, @@ -5187,12 +5208,12 @@ namespace BMA.EHR.Application.Repositories PositionTypeName = p.PosType, IsHigherLevel = p.ProfileInsignia - .Where(x => x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) .FirstOrDefault() == null ? true : IsHigherLevel(p.ProfileInsignia - .Where(x => x.InsigniaId.Value != - GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != + GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) .OrderByDescending(x => x.Year) .FirstOrDefault(), "มหาวชิรมงกุฎ"), @@ -5202,14 +5223,14 @@ namespace BMA.EHR.Application.Repositories p.ProfileSalary.Where(x => x.PositionLevel == "ทรงคุณวุฒิ").OrderBy(p => p.Date) .FirstOrDefault() == null ? null : p.ProfileSalary.Where(x => x.PositionLevel == "ทรงคุณวุฒิ").OrderBy(p => p.Date) - .FirstOrDefault().Date, + .FirstOrDefault()?.Date, FirstRecvInsigniaYear = p.ProfileInsignia.Count == 0 ? 0 : p.ProfileInsignia - .Where(x => x.InsigniaId.Value == GetInsigniaByName("ประถมาภรณ์ช้างเผือก").Id) + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value == GetInsigniaByName("ประถมาภรณ์ช้างเผือก")?.Id) .OrderBy(x => x.Year).FirstOrDefault() == null ? 0 : p.ProfileInsignia - .Where(x => x.InsigniaId.Value == GetInsigniaByName("ประถมาภรณ์ช้างเผือก").Id) - .OrderBy(x => x.Year).FirstOrDefault().Year, + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value == GetInsigniaByName("ประถมาภรณ์ช้างเผือก")?.Id) + .OrderBy(x => x.Year).FirstOrDefault()?.Year, PositionSalaryAmount = p.PositionSalaryAmount, Amount = p.Amount, RootId = p.RootId, @@ -5306,8 +5327,9 @@ namespace BMA.EHR.Application.Repositories && p.ProfileInsignia != null && p.ProfileInsignia.Count > 0 && (p.ProfileInsignia.Where(x => - x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id && - x.InsigniaId.Value == GetInsigniaByName("มหาปรมาภรณ์ช้างเผือก").Id) + x.InsigniaId.HasValue && + x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id && + x.InsigniaId.Value == GetInsigniaByName("มหาปรมาภรณ์ช้างเผือก")?.Id) .ToList() .Count() == 0) select new @@ -5326,25 +5348,25 @@ namespace BMA.EHR.Application.Repositories ProfileDateAppoint = p.DateAppoint.Value, LastInsignia = p.ProfileInsignia == null || p.ProfileInsignia.Count == 0 ? "" : p.ProfileInsignia - .Where(x => x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) .FirstOrDefault() == null ? "" : p.ProfileInsignia - .Where(x => x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) - .OrderByDescending(x => x.Year).FirstOrDefault().Insignia, + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) + .OrderByDescending(x => x.Year).FirstOrDefault()?.Insignia, LastInsigniaId = p.ProfileInsignia == null || p.ProfileInsignia.Count == 0 ? Guid.Empty : p.ProfileInsignia - .Where(x => x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) .FirstOrDefault() == null ? Guid.Empty : p.ProfileInsignia - .Where(x => x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) - .OrderByDescending(x => x.Year).FirstOrDefault().InsigniaId, + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) + .OrderByDescending(x => x.Year).FirstOrDefault()?.InsigniaId, Salary = p.Amount, SalaryPosition = p.PositionSalaryAmount, SalaryCondition = p.ProfileSalary == null || p.ProfileSalary.Count == 0 ? 0 : - p.ProfileSalary.Where(x => x.Date != null).Where(x => x.Date.Value <= new DateTime(period.Year, 4, 29)) + p.ProfileSalary.Where(x => x.Date != null).Where(x => x.Date.HasValue && x.Date.Value <= new DateTime(period.Year, 4, 29)) .OrderByDescending(x => x.Order).FirstOrDefault() != null ? p.ProfileSalary - .Where(x => x.Date != null).Where(x => x.Date.Value <= new DateTime(period.Year, 4, 29)) - .OrderByDescending(x => x.Order).FirstOrDefault().Amount : + .Where(x => x.Date != null).Where(x => x.Date.HasValue && x.Date.Value <= new DateTime(period.Year, 4, 29)) + .OrderByDescending(x => x.Order).FirstOrDefault()?.Amount : p.Amount, PosNo = p.PosNo, @@ -5355,12 +5377,12 @@ namespace BMA.EHR.Application.Repositories PositionTypeName = p.PosType, IsHigherLevel = p.ProfileInsignia - .Where(x => x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) .FirstOrDefault() == null ? true : IsHigherLevel(p.ProfileInsignia - .Where(x => x.InsigniaId.Value != - GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != + GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) .OrderByDescending(x => x.Year) .FirstOrDefault(), "มหาปรมาภรณ์ช้างเผือก"), @@ -5371,12 +5393,12 @@ namespace BMA.EHR.Application.Repositories p.ProfileSalary.Where(x => x.PositionLevel == "ทรงคุณวุฒิ").OrderBy(p => p.Date) .FirstOrDefault() == null ? null : p.ProfileSalary.Where(x => x.PositionLevel == "ทรงคุณวุฒิ").OrderBy(p => p.Date) - .FirstOrDefault().Date, + .FirstOrDefault()?.Date, FirstRecvInsigniaYear = p.ProfileInsignia.Count == 0 ? 0 : - p.ProfileInsignia.Where(x => x.InsigniaId.Value == GetInsigniaByName("มหาวชิรมงกุฎ").Id) + p.ProfileInsignia.Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value == GetInsigniaByName("มหาวชิรมงกุฎ")?.Id) .OrderBy(x => x.Year).FirstOrDefault() == null ? 0 : - p.ProfileInsignia.Where(x => x.InsigniaId.Value == GetInsigniaByName("มหาวชิรมงกุฎ").Id) - .OrderBy(x => x.Year).FirstOrDefault().Year, + p.ProfileInsignia.Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value == GetInsigniaByName("มหาวชิรมงกุฎ")?.Id) + .OrderBy(x => x.Year).FirstOrDefault()?.Year, PositionSalaryAmount = p.PositionSalaryAmount, Amount = p.Amount, RootId = p.RootId, @@ -5555,8 +5577,9 @@ namespace BMA.EHR.Application.Repositories && p.ProfileInsignia != null && p.ProfileInsignia.Count > 0 && (p.ProfileInsignia.Where(x => - x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id && - x.InsigniaId.Value == GetInsigniaByName("ประถมาภรณ์ช้างเผือก").Id) + x.InsigniaId.HasValue && + x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id && + x.InsigniaId.Value == GetInsigniaByName("ประถมาภรณ์ช้างเผือก")?.Id) .ToList() .Count() == 0) select new @@ -5575,25 +5598,25 @@ namespace BMA.EHR.Application.Repositories ProfileDateAppoint = p.DateAppoint.Value, LastInsignia = p.ProfileInsignia == null || p.ProfileInsignia.Count == 0 ? "" : p.ProfileInsignia - .Where(x => x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) .FirstOrDefault() == null ? "" : p.ProfileInsignia - .Where(x => x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) - .OrderByDescending(x => x.Year).FirstOrDefault().Insignia, + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) + .OrderByDescending(x => x.Year).FirstOrDefault()?.Insignia, LastInsigniaId = p.ProfileInsignia == null || p.ProfileInsignia.Count == 0 ? Guid.Empty : p.ProfileInsignia - .Where(x => x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) .FirstOrDefault() == null ? Guid.Empty : p.ProfileInsignia - .Where(x => x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) - .OrderByDescending(x => x.Year).FirstOrDefault().InsigniaId, + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) + .OrderByDescending(x => x.Year).FirstOrDefault()?.InsigniaId, Salary = p.Amount, SalaryPosition = p.PositionSalaryAmount, SalaryCondition = p.ProfileSalary == null || p.ProfileSalary.Count == 0 ? 0 : - p.ProfileSalary.Where(x => x.Date != null).Where(x => x.Date.Value <= new DateTime(period.Year, 4, 29)) + p.ProfileSalary.Where(x => x.Date != null).Where(x => x.Date.HasValue && x.Date.Value <= new DateTime(period.Year, 4, 29)) .OrderByDescending(x => x.Order).FirstOrDefault() != null ? p.ProfileSalary - .Where(x => x.Date != null).Where(x => x.Date.Value <= new DateTime(period.Year, 4, 29)) - .OrderByDescending(x => x.Order).FirstOrDefault().Amount : + .Where(x => x.Date != null).Where(x => x.Date.HasValue && x.Date.Value <= new DateTime(period.Year, 4, 29)) + .OrderByDescending(x => x.Order).FirstOrDefault()?.Amount : p.Amount, PosNo = p.PosNo, Gender = p.Gender == null ? null : p.Gender, @@ -5603,12 +5626,12 @@ namespace BMA.EHR.Application.Repositories PositionTypeName = p.PosType, IsHigherLevel = p.ProfileInsignia - .Where(x => x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) .FirstOrDefault() == null ? true : IsHigherLevel(p.ProfileInsignia - .Where(x => x.InsigniaId.Value != - GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != + GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) .OrderByDescending(x => x.Year) .FirstOrDefault(), "ประถมาภรณ์ช้างเผือก"), @@ -5618,14 +5641,14 @@ namespace BMA.EHR.Application.Repositories p.ProfileSalary.Where(x => x.PositionLevel == "ทรงคุณวุฒิ").OrderBy(p => p.Date) .FirstOrDefault() == null ? null : p.ProfileSalary.Where(x => x.PositionLevel == "ทรงคุณวุฒิ").OrderBy(p => p.Date) - .FirstOrDefault().Date, + .FirstOrDefault()?.Date, FirstRecvInsigniaYear = p.ProfileInsignia.Count == 0 ? 0 : p.ProfileInsignia - .Where(x => x.InsigniaId.Value == GetInsigniaByName("ประถมาภรณ์มงกุฎไทย").Id) + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value == GetInsigniaByName("ประถมาภรณ์มงกุฎไทย")?.Id) .OrderBy(x => x.Year).FirstOrDefault() == null ? 0 : p.ProfileInsignia - .Where(x => x.InsigniaId.Value == GetInsigniaByName("ประถมาภรณ์มงกุฎไทย").Id) - .OrderBy(x => x.Year).FirstOrDefault().Year, + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value == GetInsigniaByName("ประถมาภรณ์มงกุฎไทย")?.Id) + .OrderBy(x => x.Year).FirstOrDefault()?.Year, PositionSalaryAmount = p.PositionSalaryAmount, Amount = p.Amount, RootId = p.RootId, @@ -5722,8 +5745,9 @@ namespace BMA.EHR.Application.Repositories && p.ProfileInsignia != null && p.ProfileInsignia.Count > 0 && (p.ProfileInsignia.Where(x => - x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id && - x.InsigniaId.Value == GetInsigniaByName("มหาวชิรมงกุฎ").Id) + x.InsigniaId.HasValue && + x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id && + x.InsigniaId.Value == GetInsigniaByName("มหาวชิรมงกุฎ")?.Id) .ToList() .Count() == 0) select new @@ -5742,25 +5766,25 @@ namespace BMA.EHR.Application.Repositories ProfileDateAppoint = p.DateAppoint.Value, LastInsignia = p.ProfileInsignia == null || p.ProfileInsignia.Count == 0 ? "" : p.ProfileInsignia - .Where(x => x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) .FirstOrDefault() == null ? "" : p.ProfileInsignia - .Where(x => x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) - .OrderByDescending(x => x.Year).FirstOrDefault().Insignia, + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) + .OrderByDescending(x => x.Year).FirstOrDefault()?.Insignia, LastInsigniaId = p.ProfileInsignia == null || p.ProfileInsignia.Count == 0 ? Guid.Empty : p.ProfileInsignia - .Where(x => x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) .FirstOrDefault() == null ? Guid.Empty : p.ProfileInsignia - .Where(x => x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) - .OrderByDescending(x => x.Year).FirstOrDefault().InsigniaId, + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) + .OrderByDescending(x => x.Year).FirstOrDefault()?.InsigniaId, Salary = p.Amount, SalaryPosition = p.PositionSalaryAmount, SalaryCondition = p.ProfileSalary == null || p.ProfileSalary.Count == 0 ? 0 : - p.ProfileSalary.Where(x => x.Date != null).Where(x => x.Date.Value <= new DateTime(period.Year, 4, 29)) + p.ProfileSalary.Where(x => x.Date != null).Where(x => x.Date.HasValue && x.Date.Value <= new DateTime(period.Year, 4, 29)) .OrderByDescending(x => x.Order).FirstOrDefault() != null ? p.ProfileSalary - .Where(x => x.Date != null).Where(x => x.Date.Value <= new DateTime(period.Year, 4, 29)) - .OrderByDescending(x => x.Order).FirstOrDefault().Amount : + .Where(x => x.Date != null).Where(x => x.Date.HasValue && x.Date.Value <= new DateTime(period.Year, 4, 29)) + .OrderByDescending(x => x.Order).FirstOrDefault()?.Amount : p.Amount, PosNo = p.PosNo, Gender = p.Gender == null ? null : p.Gender, @@ -5770,12 +5794,12 @@ namespace BMA.EHR.Application.Repositories PositionTypeName = p.PosType, IsHigherLevel = p.ProfileInsignia - .Where(x => x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) .FirstOrDefault() == null ? true : IsHigherLevel(p.ProfileInsignia - .Where(x => x.InsigniaId.Value != - GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != + GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) .OrderByDescending(x => x.Year) .FirstOrDefault(), "มหาวชิรมงกุฎ"), @@ -5785,14 +5809,14 @@ namespace BMA.EHR.Application.Repositories p.ProfileSalary.Where(x => x.PositionLevel == "อาวุโส").OrderBy(p => p.Date) .FirstOrDefault() == null ? null : p.ProfileSalary.Where(x => x.PositionLevel == "อาวุโส").OrderBy(p => p.Date) - .FirstOrDefault().Date, + .FirstOrDefault()?.Date, FirstRecvInsigniaYear = p.ProfileInsignia.Count == 0 ? 0 : p.ProfileInsignia - .Where(x => x.InsigniaId.Value == GetInsigniaByName("ประถมาภรณ์ช้างเผือก").Id) + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value == GetInsigniaByName("ประถมาภรณ์ช้างเผือก")?.Id) .OrderBy(x => x.Year).FirstOrDefault() == null ? 0 : p.ProfileInsignia - .Where(x => x.InsigniaId.Value == GetInsigniaByName("ประถมาภรณ์ช้างเผือก").Id) - .OrderBy(x => x.Year).FirstOrDefault().Year, + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value == GetInsigniaByName("ประถมาภรณ์ช้างเผือก")?.Id) + .OrderBy(x => x.Year).FirstOrDefault()?.Year, PositionSalaryAmount = p.PositionSalaryAmount, Amount = p.Amount, RootId = p.RootId, @@ -5889,8 +5913,9 @@ namespace BMA.EHR.Application.Repositories && p.ProfileInsignia != null && p.ProfileInsignia.Count > 0 && (p.ProfileInsignia.Where(x => - x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id && - x.InsigniaId.Value == GetInsigniaByName("มหาปรมาภรณ์ช้างเผือก").Id) + x.InsigniaId.HasValue && + x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id && + x.InsigniaId.Value == GetInsigniaByName("มหาปรมาภรณ์ช้างเผือก")?.Id) .ToList() .Count() == 0) select new @@ -5909,25 +5934,25 @@ namespace BMA.EHR.Application.Repositories ProfileDateAppoint = p.DateAppoint.Value, LastInsignia = p.ProfileInsignia == null || p.ProfileInsignia.Count == 0 ? "" : p.ProfileInsignia - .Where(x => x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) .FirstOrDefault() == null ? "" : p.ProfileInsignia - .Where(x => x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) - .OrderByDescending(x => x.Year).FirstOrDefault().Insignia, + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) + .OrderByDescending(x => x.Year).FirstOrDefault()?.Insignia, LastInsigniaId = p.ProfileInsignia == null || p.ProfileInsignia.Count == 0 ? Guid.Empty : p.ProfileInsignia - .Where(x => x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) .FirstOrDefault() == null ? Guid.Empty : p.ProfileInsignia - .Where(x => x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) - .OrderByDescending(x => x.Year).FirstOrDefault().InsigniaId, + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) + .OrderByDescending(x => x.Year).FirstOrDefault()?.InsigniaId, Salary = p.Amount, SalaryPosition = p.PositionSalaryAmount, SalaryCondition = p.ProfileSalary == null || p.ProfileSalary.Count == 0 ? 0 : - p.ProfileSalary.Where(x => x.Date != null).Where(x => x.Date.Value <= new DateTime(period.Year, 4, 29)) + p.ProfileSalary.Where(x => x.Date != null).Where(x => x.Date.HasValue && x.Date.Value <= new DateTime(period.Year, 4, 29)) .OrderByDescending(x => x.Order).FirstOrDefault() != null ? p.ProfileSalary - .Where(x => x.Date != null).Where(x => x.Date.Value <= new DateTime(period.Year, 4, 29)) - .OrderByDescending(x => x.Order).FirstOrDefault().Amount : + .Where(x => x.Date != null).Where(x => x.Date.HasValue && x.Date.Value <= new DateTime(period.Year, 4, 29)) + .OrderByDescending(x => x.Order).FirstOrDefault()?.Amount : p.Amount, PosNo = p.PosNo, Gender = p.Gender == null ? null : p.Gender, @@ -5937,12 +5962,12 @@ namespace BMA.EHR.Application.Repositories PositionTypeName = p.PosType, IsHigherLevel = p.ProfileInsignia - .Where(x => x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) .FirstOrDefault() == null ? true : IsHigherLevel(p.ProfileInsignia - .Where(x => x.InsigniaId.Value != - GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != + GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) .OrderByDescending(x => x.Year) .FirstOrDefault(), "มหาปรมาภรณ์ช้างเผือก"), @@ -5952,12 +5977,12 @@ namespace BMA.EHR.Application.Repositories p.ProfileSalary.Where(x => x.PositionLevel == "ทรงคุณวุฒิ").OrderBy(p => p.Date) .FirstOrDefault() == null ? null : p.ProfileSalary.Where(x => x.PositionLevel == "ทรงคุณวุฒิ").OrderBy(p => p.Date) - .FirstOrDefault().Date, + .FirstOrDefault()?.Date, FirstRecvInsigniaYear = p.ProfileInsignia.Count == 0 ? 0 : - p.ProfileInsignia.Where(x => x.InsigniaId.Value == GetInsigniaByName("มหาวชิรมงกุฎ").Id) + p.ProfileInsignia.Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value == GetInsigniaByName("มหาวชิรมงกุฎ")?.Id) .OrderBy(x => x.Year).FirstOrDefault() == null ? 0 : - p.ProfileInsignia.Where(x => x.InsigniaId.Value == GetInsigniaByName("มหาวชิรมงกุฎ").Id) - .OrderBy(x => x.Year).FirstOrDefault().Year, + p.ProfileInsignia.Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value == GetInsigniaByName("มหาวชิรมงกุฎ")?.Id) + .OrderBy(x => x.Year).FirstOrDefault()?.Year, PositionSalaryAmount = p.PositionSalaryAmount, Amount = p.Amount, RootId = p.RootId, @@ -6134,8 +6159,9 @@ namespace BMA.EHR.Application.Repositories && p.ProfileInsignia != null && p.ProfileInsignia.Count > 0 && (p.ProfileInsignia.Where(x => - x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id && - x.InsigniaId.Value == GetInsigniaByName("ทวีติยาภรณ์ช้างเผือก").Id) + x.InsigniaId.HasValue && + x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id && + x.InsigniaId.Value == GetInsigniaByName("ทวีติยาภรณ์ช้างเผือก")?.Id) .ToList() .Count() == 0) select new @@ -6154,25 +6180,25 @@ namespace BMA.EHR.Application.Repositories ProfileDateAppoint = p.DateAppoint.Value, LastInsignia = p.ProfileInsignia == null || p.ProfileInsignia.Count == 0 ? "" : p.ProfileInsignia - .Where(x => x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) .FirstOrDefault() == null ? "" : p.ProfileInsignia - .Where(x => x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) - .OrderByDescending(x => x.Year).FirstOrDefault().Insignia, + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) + .OrderByDescending(x => x.Year).FirstOrDefault()?.Insignia, LastInsigniaId = p.ProfileInsignia == null || p.ProfileInsignia.Count == 0 ? Guid.Empty : p.ProfileInsignia - .Where(x => x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) .FirstOrDefault() == null ? Guid.Empty : p.ProfileInsignia - .Where(x => x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) - .OrderByDescending(x => x.Year).FirstOrDefault().InsigniaId, + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) + .OrderByDescending(x => x.Year).FirstOrDefault()?.InsigniaId, Salary = p.Amount, SalaryPosition = p.PositionSalaryAmount, SalaryCondition = p.ProfileSalary == null || p.ProfileSalary.Count == 0 ? 0 : - p.ProfileSalary.Where(x => x.Date != null).Where(x => x.Date.Value <= new DateTime(period.Year, 4, 29)) + p.ProfileSalary.Where(x => x.Date != null).Where(x => x.Date.HasValue && x.Date.Value <= new DateTime(period.Year, 4, 29)) .OrderByDescending(x => x.Order).FirstOrDefault() != null ? p.ProfileSalary - .Where(x => x.Date != null).Where(x => x.Date.Value <= new DateTime(period.Year, 4, 29)) - .OrderByDescending(x => x.Order).FirstOrDefault().Amount : + .Where(x => x.Date != null).Where(x => x.Date.HasValue && x.Date.Value <= new DateTime(period.Year, 4, 29)) + .OrderByDescending(x => x.Order).FirstOrDefault()?.Amount : p.Amount, PosNo = p.PosNo == null ? "" : p.PosNo, Gender = p.Gender == null ? "" : p.Gender, @@ -6182,12 +6208,12 @@ namespace BMA.EHR.Application.Repositories PositionTypeName = p.PosType, IsHigherLevel = p.ProfileInsignia - .Where(x => x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) .FirstOrDefault() == null ? true : IsHigherLevel(p.ProfileInsignia - .Where(x => x.InsigniaId.Value != - GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != + GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) .OrderByDescending(x => x.Year) .FirstOrDefault(), "ทวีติยาภรณ์ช้างเผือก"), @@ -6197,12 +6223,12 @@ namespace BMA.EHR.Application.Repositories p.ProfileSalary.Where(x => x.PositionLevel == "ต้น" && x.PositionType == "อำนวยการ") .OrderBy(p => p.Date).FirstOrDefault() == null ? null : p.ProfileSalary.Where(x => x.PositionLevel == "ต้น" && x.PositionType == "อำนวยการ") - .OrderBy(p => p.Date).FirstOrDefault().Date, + .OrderBy(p => p.Date).FirstOrDefault()?.Date, FirstRecvInsigniaYear = p.ProfileInsignia.Count == 0 ? 0 : - p.ProfileInsignia.Where(x => x.InsigniaId.Value == GetInsigniaByName("มหาวชิรมงกุฎ").Id) + p.ProfileInsignia.Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value == GetInsigniaByName("มหาวชิรมงกุฎ")?.Id) .OrderBy(x => x.Year).FirstOrDefault() == null ? 0 : - p.ProfileInsignia.Where(x => x.InsigniaId.Value == GetInsigniaByName("มหาวชิรมงกุฎ").Id) - .OrderBy(x => x.Year).FirstOrDefault().Year, + p.ProfileInsignia.Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value == GetInsigniaByName("มหาวชิรมงกุฎ")?.Id) + .OrderBy(x => x.Year).FirstOrDefault()?.Year, PositionSalaryAmount = p.PositionSalaryAmount, Amount = p.Amount, RootId = p.RootId, @@ -6291,8 +6317,9 @@ namespace BMA.EHR.Application.Repositories && p.ProfileInsignia != null && p.ProfileInsignia.Count > 0 && (p.ProfileInsignia.Where(x => - x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id && - x.InsigniaId.Value == GetInsigniaByName("ประถมาภรณ์มงกุฎไทย").Id) + x.InsigniaId.HasValue && + x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id && + x.InsigniaId.Value == GetInsigniaByName("ประถมาภรณ์มงกุฎไทย")?.Id) .ToList() .Count() == 0) select new @@ -6311,25 +6338,25 @@ namespace BMA.EHR.Application.Repositories ProfileDateAppoint = p.DateAppoint.Value, LastInsignia = p.ProfileInsignia == null || p.ProfileInsignia.Count == 0 ? "" : p.ProfileInsignia - .Where(x => x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) .FirstOrDefault() == null ? "" : p.ProfileInsignia - .Where(x => x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) - .OrderByDescending(x => x.Year).FirstOrDefault().Insignia, + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) + .OrderByDescending(x => x.Year).FirstOrDefault()?.Insignia, LastInsigniaId = p.ProfileInsignia == null || p.ProfileInsignia.Count == 0 ? Guid.Empty : p.ProfileInsignia - .Where(x => x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) .FirstOrDefault() == null ? Guid.Empty : p.ProfileInsignia - .Where(x => x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) - .OrderByDescending(x => x.Year).FirstOrDefault().InsigniaId, + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) + .OrderByDescending(x => x.Year).FirstOrDefault()?.InsigniaId, Salary = p.Amount, SalaryPosition = p.PositionSalaryAmount, SalaryCondition = p.ProfileSalary == null || p.ProfileSalary.Count == 0 ? 0 : - p.ProfileSalary.Where(x => x.Date != null).Where(x => x.Date.Value <= new DateTime(period.Year, 4, 29)) + p.ProfileSalary.Where(x => x.Date != null).Where(x => x.Date.HasValue && x.Date.Value <= new DateTime(period.Year, 4, 29)) .OrderByDescending(x => x.Order).FirstOrDefault() != null ? p.ProfileSalary - .Where(x => x.Date != null).Where(x => x.Date.Value <= new DateTime(period.Year, 4, 29)) - .OrderByDescending(x => x.Order).FirstOrDefault().Amount : + .Where(x => x.Date != null).Where(x => x.Date.HasValue && x.Date.Value <= new DateTime(period.Year, 4, 29)) + .OrderByDescending(x => x.Order).FirstOrDefault()?.Amount : p.Amount, PosNo = p.PosNo, Gender = p.Gender == null ? "" : p.Gender, @@ -6339,12 +6366,12 @@ namespace BMA.EHR.Application.Repositories PositionTypeName = p.PosType, IsHigherLevel = p.ProfileInsignia - .Where(x => x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) .FirstOrDefault() == null ? true : IsHigherLevel(p.ProfileInsignia - .Where(x => x.InsigniaId.Value != - GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != + GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) .OrderByDescending(x => x.Year) .FirstOrDefault(), "ประถมาภรณ์มงกุฎไทย"), @@ -6354,14 +6381,14 @@ namespace BMA.EHR.Application.Repositories p.ProfileSalary.Where(x => x.PositionLevel == "ต้น" && x.PositionType == "อำนวยการ") .OrderBy(p => p.Date).FirstOrDefault() == null ? null : p.ProfileSalary.Where(x => x.PositionLevel == "ต้น" && x.PositionType == "อำนวยการ") - .OrderBy(p => p.Date).FirstOrDefault().Date, + .OrderBy(p => p.Date).FirstOrDefault()?.Date, FirstRecvInsigniaYear = p.ProfileInsignia.Count == 0 ? 0 : p.ProfileInsignia - .Where(x => x.InsigniaId.Value == GetInsigniaByName("ทวีติยาภรณ์ช้างเผือก").Id) + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value == GetInsigniaByName("ทวีติยาภรณ์ช้างเผือก")?.Id) .OrderBy(x => x.Year).FirstOrDefault() == null ? 0 : p.ProfileInsignia - .Where(x => x.InsigniaId.Value == GetInsigniaByName("ทวีติยาภรณ์ช้างเผือก").Id) - .OrderBy(x => x.Year).FirstOrDefault().Year, + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value == GetInsigniaByName("ทวีติยาภรณ์ช้างเผือก")?.Id) + .OrderBy(x => x.Year).FirstOrDefault()?.Year, PositionSalaryAmount = p.PositionSalaryAmount, Amount = p.Amount, RootId = p.RootId, @@ -6519,8 +6546,9 @@ namespace BMA.EHR.Application.Repositories && p.ProfileInsignia != null && p.ProfileInsignia.Count > 0 && (p.ProfileInsignia.Where(x => - x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id && - x.InsigniaId.Value == GetInsigniaByName("ประถมาภรณ์มงกุฎไทย").Id) + x.InsigniaId.HasValue && + x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id && + x.InsigniaId.Value == GetInsigniaByName("ประถมาภรณ์มงกุฎไทย")?.Id) .ToList() .Count() == 0) select new @@ -6539,25 +6567,25 @@ namespace BMA.EHR.Application.Repositories ProfileDateAppoint = p.DateAppoint.Value, LastInsignia = p.ProfileInsignia == null || p.ProfileInsignia.Count == 0 ? "" : p.ProfileInsignia - .Where(x => x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) .FirstOrDefault() == null ? "" : p.ProfileInsignia - .Where(x => x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) - .OrderByDescending(x => x.Year).FirstOrDefault().Insignia, + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) + .OrderByDescending(x => x.Year).FirstOrDefault()?.Insignia, LastInsigniaId = p.ProfileInsignia == null || p.ProfileInsignia.Count == 0 ? Guid.Empty : p.ProfileInsignia - .Where(x => x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) .FirstOrDefault() == null ? Guid.Empty : p.ProfileInsignia - .Where(x => x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) - .OrderByDescending(x => x.Year).FirstOrDefault().InsigniaId, + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) + .OrderByDescending(x => x.Year).FirstOrDefault()?.InsigniaId, Salary = p.Amount, SalaryPosition = p.PositionSalaryAmount, SalaryCondition = p.ProfileSalary == null || p.ProfileSalary.Count == 0 ? 0 : - p.ProfileSalary.Where(x => x.Date != null).Where(x => x.Date.Value <= new DateTime(period.Year, 4, 29)) + p.ProfileSalary.Where(x => x.Date != null).Where(x => x.Date.HasValue && x.Date.Value <= new DateTime(period.Year, 4, 29)) .OrderByDescending(x => x.Order).FirstOrDefault() != null ? p.ProfileSalary - .Where(x => x.Date != null).Where(x => x.Date.Value <= new DateTime(period.Year, 4, 29)) - .OrderByDescending(x => x.Order).FirstOrDefault().Amount : + .Where(x => x.Date != null).Where(x => x.Date.HasValue && x.Date.Value <= new DateTime(period.Year, 4, 29)) + .OrderByDescending(x => x.Order).FirstOrDefault()?.Amount : p.Amount, PosNo = p.PosNo, Gender = p.Gender == null ? "" : p.Gender, @@ -6567,12 +6595,12 @@ namespace BMA.EHR.Application.Repositories PositionTypeName = p.PosType, IsHigherLevel = p.ProfileInsignia - .Where(x => x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) .FirstOrDefault() == null ? true : IsHigherLevel(p.ProfileInsignia - .Where(x => x.InsigniaId.Value != - GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != + GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) .OrderByDescending(x => x.Year) .FirstOrDefault(), "ประถมาภรณ์มงกุฎไทย"), @@ -6582,14 +6610,14 @@ namespace BMA.EHR.Application.Repositories p.ProfileSalary.Where(x => x.PositionLevel == "สูง" && x.PositionType == "อำนวยการ") .OrderBy(p => p.Date).FirstOrDefault() == null ? null : p.ProfileSalary.Where(x => x.PositionLevel == "สูง" && x.PositionType == "อำนวยการ") - .OrderBy(p => p.Date).FirstOrDefault().Date, + .OrderBy(p => p.Date).FirstOrDefault()?.Date, FirstRecvInsigniaYear = p.ProfileInsignia.Count == 0 ? 0 : p.ProfileInsignia - .Where(x => x.InsigniaId.Value == GetInsigniaByName("ทวีติยาภรณ์ช้างเผือก").Id) + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value == GetInsigniaByName("ทวีติยาภรณ์ช้างเผือก")?.Id) .OrderBy(x => x.Year).FirstOrDefault() == null ? 0 : p.ProfileInsignia - .Where(x => x.InsigniaId.Value == GetInsigniaByName("ทวีติยาภรณ์ช้างเผือก").Id) - .OrderBy(x => x.Year).FirstOrDefault().Year, + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value == GetInsigniaByName("ทวีติยาภรณ์ช้างเผือก")?.Id) + .OrderBy(x => x.Year).FirstOrDefault()?.Year, PositionSalaryAmount = p.PositionSalaryAmount, Amount = p.Amount, RootId = p.RootId, @@ -6680,8 +6708,9 @@ namespace BMA.EHR.Application.Repositories && p.ProfileInsignia != null && p.ProfileInsignia.Count > 0 && (p.ProfileInsignia.Where(x => - x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id && - x.InsigniaId.Value == GetInsigniaByName("ประถมาภรณ์ช้างเผือก").Id) + x.InsigniaId.HasValue && + x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id && + x.InsigniaId.Value == GetInsigniaByName("ประถมาภรณ์ช้างเผือก")?.Id) .ToList() .Count() == 0) select new @@ -6700,25 +6729,25 @@ namespace BMA.EHR.Application.Repositories ProfileDateAppoint = p.DateAppoint.Value, LastInsignia = p.ProfileInsignia == null || p.ProfileInsignia.Count == 0 ? "" : p.ProfileInsignia - .Where(x => x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) .FirstOrDefault() == null ? "" : p.ProfileInsignia - .Where(x => x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) - .OrderByDescending(x => x.Year).FirstOrDefault().Insignia, + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) + .OrderByDescending(x => x.Year).FirstOrDefault()?.Insignia, LastInsigniaId = p.ProfileInsignia == null || p.ProfileInsignia.Count == 0 ? Guid.Empty : p.ProfileInsignia - .Where(x => x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) .FirstOrDefault() == null ? Guid.Empty : p.ProfileInsignia - .Where(x => x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) - .OrderByDescending(x => x.Year).FirstOrDefault().InsigniaId, + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) + .OrderByDescending(x => x.Year).FirstOrDefault()?.InsigniaId, Salary = p.Amount, SalaryPosition = p.PositionSalaryAmount, SalaryCondition = p.ProfileSalary == null || p.ProfileSalary.Count == 0 ? 0 : - p.ProfileSalary.Where(x => x.Date != null).Where(x => x.Date.Value <= new DateTime(period.Year, 4, 29)) + p.ProfileSalary.Where(x => x.Date != null).Where(x => x.Date.HasValue && x.Date.Value <= new DateTime(period.Year, 4, 29)) .OrderByDescending(x => x.Order).FirstOrDefault() != null ? p.ProfileSalary - .Where(x => x.Date != null).Where(x => x.Date.Value <= new DateTime(period.Year, 4, 29)) - .OrderByDescending(x => x.Order).FirstOrDefault().Amount : + .Where(x => x.Date != null).Where(x => x.Date.HasValue && x.Date.Value <= new DateTime(period.Year, 4, 29)) + .OrderByDescending(x => x.Order).FirstOrDefault()?.Amount : p.Amount, PosNo = p.PosNo, Gender = p.Gender == null ? "" : p.Gender, @@ -6728,12 +6757,12 @@ namespace BMA.EHR.Application.Repositories PositionTypeName = p.PosType, IsHigherLevel = p.ProfileInsignia - .Where(x => x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) .FirstOrDefault() == null ? true : IsHigherLevel(p.ProfileInsignia - .Where(x => x.InsigniaId.Value != - GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != + GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) .OrderByDescending(x => x.Year) .FirstOrDefault(), "ประถมาภรณ์ช้างเผือก"), @@ -6743,14 +6772,14 @@ namespace BMA.EHR.Application.Repositories p.ProfileSalary.Where(x => x.PositionLevel == "สูง" && x.PositionType == "อำนวยการ") .OrderBy(p => p.Date).FirstOrDefault() == null ? null : p.ProfileSalary.Where(x => x.PositionLevel == "สูง" && x.PositionType == "อำนวยการ") - .OrderBy(p => p.Date).FirstOrDefault().Date, + .OrderBy(p => p.Date).FirstOrDefault()?.Date, FirstRecvInsigniaYear = p.ProfileInsignia.Count == 0 ? 0 : p.ProfileInsignia - .Where(x => x.InsigniaId.Value == GetInsigniaByName("ประถมาภรณ์มงกุฎไทย").Id) + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value == GetInsigniaByName("ประถมาภรณ์มงกุฎไทย")?.Id) .OrderBy(x => x.Year).FirstOrDefault() == null ? 0 : p.ProfileInsignia - .Where(x => x.InsigniaId.Value == GetInsigniaByName("ประถมาภรณ์มงกุฎไทย").Id) - .OrderBy(x => x.Year).FirstOrDefault().Year, + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value == GetInsigniaByName("ประถมาภรณ์มงกุฎไทย")?.Id) + .OrderBy(x => x.Year).FirstOrDefault()?.Year, PositionSalaryAmount = p.PositionSalaryAmount, Amount = p.Amount, RootId = p.RootId, @@ -6842,8 +6871,9 @@ namespace BMA.EHR.Application.Repositories && p.ProfileInsignia != null && p.ProfileInsignia.Count > 0 && (p.ProfileInsignia.Where(x => - x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id && - x.InsigniaId.Value == GetInsigniaByName("มหาวชิรมงกุฎ").Id) + x.InsigniaId.HasValue && + x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id && + x.InsigniaId.Value == GetInsigniaByName("มหาวชิรมงกุฎ")?.Id) .ToList() .Count() == 0) select new @@ -6862,25 +6892,25 @@ namespace BMA.EHR.Application.Repositories ProfileDateAppoint = p.DateAppoint.Value, LastInsignia = p.ProfileInsignia == null || p.ProfileInsignia.Count == 0 ? "" : p.ProfileInsignia - .Where(x => x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) .FirstOrDefault() == null ? "" : p.ProfileInsignia - .Where(x => x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) - .OrderByDescending(x => x.Year).FirstOrDefault().Insignia, + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) + .OrderByDescending(x => x.Year).FirstOrDefault()?.Insignia, LastInsigniaId = p.ProfileInsignia == null || p.ProfileInsignia.Count == 0 ? Guid.Empty : p.ProfileInsignia - .Where(x => x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) .FirstOrDefault() == null ? Guid.Empty : p.ProfileInsignia - .Where(x => x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) - .OrderByDescending(x => x.Year).FirstOrDefault().InsigniaId, + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) + .OrderByDescending(x => x.Year).FirstOrDefault()?.InsigniaId, Salary = p.Amount, SalaryPosition = p.PositionSalaryAmount, SalaryCondition = p.ProfileSalary == null || p.ProfileSalary.Count == 0 ? 0 : - p.ProfileSalary.Where(x => x.Date != null).Where(x => x.Date.Value <= new DateTime(period.Year, 4, 29)) + p.ProfileSalary.Where(x => x.Date != null).Where(x => x.Date.HasValue && x.Date.Value <= new DateTime(period.Year, 4, 29)) .OrderByDescending(x => x.Order).FirstOrDefault() != null ? p.ProfileSalary - .Where(x => x.Date != null).Where(x => x.Date.Value <= new DateTime(period.Year, 4, 29)) - .OrderByDescending(x => x.Order).FirstOrDefault().Amount : + .Where(x => x.Date != null).Where(x => x.Date.HasValue && x.Date.Value <= new DateTime(period.Year, 4, 29)) + .OrderByDescending(x => x.Order).FirstOrDefault()?.Amount : p.Amount, PosNo = p.PosNo, Gender = p.Gender == null ? "" : p.Gender, @@ -6890,12 +6920,12 @@ namespace BMA.EHR.Application.Repositories PositionTypeName = p.PosType, IsHigherLevel = p.ProfileInsignia - .Where(x => x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) .FirstOrDefault() == null ? true : IsHigherLevel(p.ProfileInsignia - .Where(x => x.InsigniaId.Value != - GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != + GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) .OrderByDescending(x => x.Year) .FirstOrDefault(), "มหาวชิรมงกุฎ"), @@ -6905,14 +6935,14 @@ namespace BMA.EHR.Application.Repositories p.ProfileSalary.Where(x => x.PositionLevel == "สูง" && x.PositionType == "อำนวยการ") .OrderBy(p => p.Date).FirstOrDefault() == null ? null : p.ProfileSalary.Where(x => x.PositionLevel == "สูง" && x.PositionType == "อำนวยการ") - .OrderBy(p => p.Date).FirstOrDefault().Date, + .OrderBy(p => p.Date).FirstOrDefault()?.Date, FirstRecvInsigniaYear = p.ProfileInsignia.Count == 0 ? 0 : p.ProfileInsignia - .Where(x => x.InsigniaId.Value == GetInsigniaByName("ประถมาภรณ์ช้างเผือก").Id) + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value == GetInsigniaByName("ประถมาภรณ์ช้างเผือก")?.Id) .OrderBy(x => x.Year).FirstOrDefault() == null ? 0 : p.ProfileInsignia - .Where(x => x.InsigniaId.Value == GetInsigniaByName("ประถมาภรณ์ช้างเผือก").Id) - .OrderBy(x => x.Year).FirstOrDefault().Year, + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value == GetInsigniaByName("ประถมาภรณ์ช้างเผือก")?.Id) + .OrderBy(x => x.Year).FirstOrDefault()?.Year, PositionSalaryAmount = p.PositionSalaryAmount, Amount = p.Amount, RootId = p.RootId, @@ -7083,8 +7113,9 @@ namespace BMA.EHR.Application.Repositories && p.ProfileInsignia != null && p.ProfileInsignia.Count > 0 && (p.ProfileInsignia.Where(x => - x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id && - x.InsigniaId.Value == GetInsigniaByName("ประถมาภรณ์มงกุฎไทย").Id) + x.InsigniaId.HasValue && + x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id && + x.InsigniaId.Value == GetInsigniaByName("ประถมาภรณ์มงกุฎไทย")?.Id) .ToList() .Count() == 0) select new @@ -7103,25 +7134,25 @@ namespace BMA.EHR.Application.Repositories ProfileDateAppoint = p.DateAppoint.Value, LastInsignia = p.ProfileInsignia == null || p.ProfileInsignia.Count == 0 ? "" : p.ProfileInsignia - .Where(x => x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) .FirstOrDefault() == null ? "" : p.ProfileInsignia - .Where(x => x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) - .OrderByDescending(x => x.Year).FirstOrDefault().Insignia, + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) + .OrderByDescending(x => x.Year).FirstOrDefault()?.Insignia, LastInsigniaId = p.ProfileInsignia == null || p.ProfileInsignia.Count == 0 ? Guid.Empty : p.ProfileInsignia - .Where(x => x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) .FirstOrDefault() == null ? Guid.Empty : p.ProfileInsignia - .Where(x => x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) - .OrderByDescending(x => x.Year).FirstOrDefault().InsigniaId, + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) + .OrderByDescending(x => x.Year).FirstOrDefault()?.InsigniaId, Salary = p.Amount, SalaryPosition = p.PositionSalaryAmount, SalaryCondition = p.ProfileSalary == null || p.ProfileSalary.Count == 0 ? 0 : - p.ProfileSalary.Where(x => x.Date != null).Where(x => x.Date.Value <= new DateTime(period.Year, 4, 29)) + p.ProfileSalary.Where(x => x.Date != null).Where(x => x.Date.HasValue && x.Date.Value <= new DateTime(period.Year, 4, 29)) .OrderByDescending(x => x.Order).FirstOrDefault() != null ? p.ProfileSalary - .Where(x => x.Date != null).Where(x => x.Date.Value <= new DateTime(period.Year, 4, 29)) - .OrderByDescending(x => x.Order).FirstOrDefault().Amount : + .Where(x => x.Date != null).Where(x => x.Date.HasValue && x.Date.Value <= new DateTime(period.Year, 4, 29)) + .OrderByDescending(x => x.Order).FirstOrDefault()?.Amount : p.Amount, PosNo = p.PosNo, Gender = p.Gender == null ? null : p.Gender, @@ -7131,12 +7162,12 @@ namespace BMA.EHR.Application.Repositories PositionTypeName = p.PosType, IsHigherLevel = p.ProfileInsignia - .Where(x => x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) .FirstOrDefault() == null ? true : IsHigherLevel(p.ProfileInsignia - .Where(x => x.InsigniaId.Value != - GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != + GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) .OrderByDescending(x => x.Year) .FirstOrDefault(), "ประถมาภรณ์มงกุฎไทย"), @@ -7146,14 +7177,14 @@ namespace BMA.EHR.Application.Repositories p.ProfileSalary.Where(x => x.PositionLevel == "ต้น" && x.PositionType == "บริหาร") .OrderBy(p => p.Date).FirstOrDefault() == null ? null : p.ProfileSalary.Where(x => x.PositionLevel == "ต้น" && x.PositionType == "บริหาร") - .OrderBy(p => p.Date).FirstOrDefault().Date, + .OrderBy(p => p.Date).FirstOrDefault()?.Date, FirstRecvInsigniaYear = p.ProfileInsignia.Count == 0 ? 0 : p.ProfileInsignia - .Where(x => x.InsigniaId.Value == GetInsigniaByName("ทวีติยาภรณ์ช้างเผือก").Id) + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value == GetInsigniaByName("ทวีติยาภรณ์ช้างเผือก")?.Id) .OrderBy(x => x.Year).FirstOrDefault() == null ? 0 : p.ProfileInsignia - .Where(x => x.InsigniaId.Value == GetInsigniaByName("ทวีติยาภรณ์ช้างเผือก").Id) - .OrderBy(x => x.Year).FirstOrDefault().Year, + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value == GetInsigniaByName("ทวีติยาภรณ์ช้างเผือก")?.Id) + .OrderBy(x => x.Year).FirstOrDefault()?.Year, PositionSalaryAmount = p.PositionSalaryAmount, Amount = p.Amount, RootId = p.RootId, @@ -7244,8 +7275,9 @@ namespace BMA.EHR.Application.Repositories && p.ProfileInsignia != null && p.ProfileInsignia.Count > 0 && (p.ProfileInsignia.Where(x => - x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id && - x.InsigniaId.Value == GetInsigniaByName("ประถมาภรณ์ช้างเผือก").Id) + x.InsigniaId.HasValue && + x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id && + x.InsigniaId.Value == GetInsigniaByName("ประถมาภรณ์ช้างเผือก")?.Id) .ToList() .Count() == 0) select new @@ -7264,25 +7296,25 @@ namespace BMA.EHR.Application.Repositories ProfileDateAppoint = p.DateAppoint.Value, LastInsignia = p.ProfileInsignia == null || p.ProfileInsignia.Count == 0 ? "" : p.ProfileInsignia - .Where(x => x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) .FirstOrDefault() == null ? "" : p.ProfileInsignia - .Where(x => x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) - .OrderByDescending(x => x.Year).FirstOrDefault().Insignia, + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) + .OrderByDescending(x => x.Year).FirstOrDefault()?.Insignia, LastInsigniaId = p.ProfileInsignia == null || p.ProfileInsignia.Count == 0 ? Guid.Empty : p.ProfileInsignia - .Where(x => x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) .FirstOrDefault() == null ? Guid.Empty : p.ProfileInsignia - .Where(x => x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) - .OrderByDescending(x => x.Year).FirstOrDefault().InsigniaId, + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) + .OrderByDescending(x => x.Year).FirstOrDefault()?.InsigniaId, Salary = p.Amount, SalaryPosition = p.PositionSalaryAmount, SalaryCondition = p.ProfileSalary == null || p.ProfileSalary.Count == 0 ? 0 : - p.ProfileSalary.Where(x => x.Date != null).Where(x => x.Date.Value <= new DateTime(period.Year, 4, 29)) + p.ProfileSalary.Where(x => x.Date != null).Where(x => x.Date.HasValue && x.Date.Value <= new DateTime(period.Year, 4, 29)) .OrderByDescending(x => x.Order).FirstOrDefault() != null ? p.ProfileSalary - .Where(x => x.Date != null).Where(x => x.Date.Value <= new DateTime(period.Year, 4, 29)) - .OrderByDescending(x => x.Order).FirstOrDefault().Amount : + .Where(x => x.Date != null).Where(x => x.Date.HasValue && x.Date.Value <= new DateTime(period.Year, 4, 29)) + .OrderByDescending(x => x.Order).FirstOrDefault()?.Amount : p.Amount, PosNo = p.PosNo, Gender = p.Gender == null ? null : p.Gender, @@ -7292,12 +7324,12 @@ namespace BMA.EHR.Application.Repositories PositionTypeName = p.PosType, IsHigherLevel = p.ProfileInsignia - .Where(x => x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) .FirstOrDefault() == null ? true : IsHigherLevel(p.ProfileInsignia - .Where(x => x.InsigniaId.Value != - GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != + GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) .OrderByDescending(x => x.Year) .FirstOrDefault(), "ประถมาภรณ์ช้างเผือก"), @@ -7307,14 +7339,14 @@ namespace BMA.EHR.Application.Repositories p.ProfileSalary.Where(x => x.PositionLevel == "ต้น" && x.PositionType == "บริหาร") .OrderBy(p => p.Date).FirstOrDefault() == null ? null : p.ProfileSalary.Where(x => x.PositionLevel == "ต้น" && x.PositionType == "บริหาร") - .OrderBy(p => p.Date).FirstOrDefault().Date, + .OrderBy(p => p.Date).FirstOrDefault()?.Date, FirstRecvInsigniaYear = p.ProfileInsignia.Count == 0 ? 0 : p.ProfileInsignia - .Where(x => x.InsigniaId.Value == GetInsigniaByName("ประถมาภรณ์มงกุฎไทย").Id) + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value == GetInsigniaByName("ประถมาภรณ์มงกุฎไทย")?.Id) .OrderBy(x => x.Year).FirstOrDefault() == null ? 0 : p.ProfileInsignia - .Where(x => x.InsigniaId.Value == GetInsigniaByName("ประถมาภรณ์มงกุฎไทย").Id) - .OrderBy(x => x.Year).FirstOrDefault().Year, + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value == GetInsigniaByName("ประถมาภรณ์มงกุฎไทย")?.Id) + .OrderBy(x => x.Year).FirstOrDefault()?.Year, PositionSalaryAmount = p.PositionSalaryAmount, Amount = p.Amount, RootId = p.RootId, @@ -7405,8 +7437,9 @@ namespace BMA.EHR.Application.Repositories && p.ProfileInsignia != null && p.ProfileInsignia.Count > 0 && (p.ProfileInsignia.Where(x => - x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id && - x.InsigniaId.Value == GetInsigniaByName("มหาวชิรมงกุฎ").Id) + x.InsigniaId.HasValue && + x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id && + x.InsigniaId.Value == GetInsigniaByName("มหาวชิรมงกุฎ")?.Id) .ToList() .Count() == 0) select new @@ -7425,25 +7458,25 @@ namespace BMA.EHR.Application.Repositories ProfileDateAppoint = p.DateAppoint.Value, LastInsignia = p.ProfileInsignia == null || p.ProfileInsignia.Count == 0 ? "" : p.ProfileInsignia - .Where(x => x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) .FirstOrDefault() == null ? "" : p.ProfileInsignia - .Where(x => x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) - .OrderByDescending(x => x.Year).FirstOrDefault().Insignia, + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) + .OrderByDescending(x => x.Year).FirstOrDefault()?.Insignia, LastInsigniaId = p.ProfileInsignia == null || p.ProfileInsignia.Count == 0 ? Guid.Empty : p.ProfileInsignia - .Where(x => x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) .FirstOrDefault() == null ? Guid.Empty : p.ProfileInsignia - .Where(x => x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) - .OrderByDescending(x => x.Year).FirstOrDefault().InsigniaId, + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) + .OrderByDescending(x => x.Year).FirstOrDefault()?.InsigniaId, Salary = p.Amount, SalaryPosition = p.PositionSalaryAmount, SalaryCondition = p.ProfileSalary == null || p.ProfileSalary.Count == 0 ? 0 : - p.ProfileSalary.Where(x => x.Date != null).Where(x => x.Date.Value <= new DateTime(period.Year, 4, 29)) + p.ProfileSalary.Where(x => x.Date != null).Where(x => x.Date.HasValue && x.Date.Value <= new DateTime(period.Year, 4, 29)) .OrderByDescending(x => x.Order).FirstOrDefault() != null ? p.ProfileSalary - .Where(x => x.Date != null).Where(x => x.Date.Value <= new DateTime(period.Year, 4, 29)) - .OrderByDescending(x => x.Order).FirstOrDefault().Amount : + .Where(x => x.Date != null).Where(x => x.Date.HasValue && x.Date.Value <= new DateTime(period.Year, 4, 29)) + .OrderByDescending(x => x.Order).FirstOrDefault()?.Amount : p.Amount, PosNo = p.PosNo, Gender = p.Gender == null ? null : p.Gender, @@ -7453,12 +7486,12 @@ namespace BMA.EHR.Application.Repositories PositionTypeName = p.PosType, IsHigherLevel = p.ProfileInsignia - .Where(x => x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) .FirstOrDefault() == null ? true : IsHigherLevel(p.ProfileInsignia - .Where(x => x.InsigniaId.Value != - GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != + GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) .OrderByDescending(x => x.Year) .FirstOrDefault(), "มหาวชิรมงกุฎ"), @@ -7468,14 +7501,14 @@ namespace BMA.EHR.Application.Repositories p.ProfileSalary.Where(x => x.PositionLevel == "ต้น" && x.PositionType == "บริหาร") .OrderBy(p => p.Date).FirstOrDefault() == null ? null : p.ProfileSalary.Where(x => x.PositionLevel == "ต้น" && x.PositionType == "บริหาร") - .OrderBy(p => p.Date).FirstOrDefault().Date, + .OrderBy(p => p.Date).FirstOrDefault()?.Date, FirstRecvInsigniaYear = p.ProfileInsignia.Count == 0 ? 0 : p.ProfileInsignia - .Where(x => x.InsigniaId.Value == GetInsigniaByName("ประถมาภรณ์ช้างเผือก").Id) + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value == GetInsigniaByName("ประถมาภรณ์ช้างเผือก")?.Id) .OrderBy(x => x.Year).FirstOrDefault() == null ? 0 : p.ProfileInsignia - .Where(x => x.InsigniaId.Value == GetInsigniaByName("ประถมาภรณ์ช้างเผือก").Id) - .OrderBy(x => x.Year).FirstOrDefault().Year, + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value == GetInsigniaByName("ประถมาภรณ์ช้างเผือก")?.Id) + .OrderBy(x => x.Year).FirstOrDefault()?.Year, PositionSalaryAmount = p.PositionSalaryAmount, Amount = p.Amount, RootId = p.RootId, @@ -7617,8 +7650,9 @@ namespace BMA.EHR.Application.Repositories && p.ProfileInsignia != null && p.ProfileInsignia.Count > 0 && (p.ProfileInsignia.Where(x => - x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id && - x.InsigniaId.Value == GetInsigniaByName("ประถมาภรณ์ช้างเผือก").Id) + x.InsigniaId.HasValue && + x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id && + x.InsigniaId.Value == GetInsigniaByName("ประถมาภรณ์ช้างเผือก")?.Id) .ToList() .Count() == 0) select new @@ -7637,25 +7671,25 @@ namespace BMA.EHR.Application.Repositories ProfileDateAppoint = p.DateAppoint.Value, LastInsignia = p.ProfileInsignia == null || p.ProfileInsignia.Count == 0 ? "" : p.ProfileInsignia - .Where(x => x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) .FirstOrDefault() == null ? "" : p.ProfileInsignia - .Where(x => x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) - .OrderByDescending(x => x.Year).FirstOrDefault().Insignia, + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) + .OrderByDescending(x => x.Year).FirstOrDefault()?.Insignia, LastInsigniaId = p.ProfileInsignia == null || p.ProfileInsignia.Count == 0 ? Guid.Empty : p.ProfileInsignia - .Where(x => x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) .FirstOrDefault() == null ? Guid.Empty : p.ProfileInsignia - .Where(x => x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) - .OrderByDescending(x => x.Year).FirstOrDefault().InsigniaId, + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) + .OrderByDescending(x => x.Year).FirstOrDefault()?.InsigniaId, Salary = p.Amount, SalaryPosition = p.PositionSalaryAmount, SalaryCondition = p.ProfileSalary == null || p.ProfileSalary.Count == 0 ? 0 : - p.ProfileSalary.Where(x => x.Date != null).Where(x => x.Date.Value <= new DateTime(period.Year, 4, 29)) + p.ProfileSalary.Where(x => x.Date != null).Where(x => x.Date.HasValue && x.Date.Value <= new DateTime(period.Year, 4, 29)) .OrderByDescending(x => x.Order).FirstOrDefault() != null ? p.ProfileSalary - .Where(x => x.Date != null).Where(x => x.Date.Value <= new DateTime(period.Year, 4, 29)) - .OrderByDescending(x => x.Order).FirstOrDefault().Amount : + .Where(x => x.Date != null).Where(x => x.Date.HasValue && x.Date.Value <= new DateTime(period.Year, 4, 29)) + .OrderByDescending(x => x.Order).FirstOrDefault()?.Amount : p.Amount, PosNo = p.PosNo, Gender = p.Gender == null ? null : p.Gender, @@ -7665,12 +7699,12 @@ namespace BMA.EHR.Application.Repositories PositionTypeName = p.PosType, IsHigherLevel = p.ProfileInsignia - .Where(x => x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) .FirstOrDefault() == null ? true : IsHigherLevel(p.ProfileInsignia - .Where(x => x.InsigniaId.Value != - GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != + GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) .OrderByDescending(x => x.Year) .FirstOrDefault(), "ประถมาภรณ์ช้างเผือก"), @@ -7680,14 +7714,14 @@ namespace BMA.EHR.Application.Repositories p.ProfileSalary.Where(x => x.PositionLevel == "สูง" && x.PositionType == "บริหาร") .OrderBy(p => p.Date).FirstOrDefault() == null ? null : p.ProfileSalary.Where(x => x.PositionLevel == "สูง" && x.PositionType == "บริหาร") - .OrderBy(p => p.Date).FirstOrDefault().Date, + .OrderBy(p => p.Date).FirstOrDefault()?.Date, FirstRecvInsigniaYear = p.ProfileInsignia.Count == 0 ? 0 : p.ProfileInsignia - .Where(x => x.InsigniaId.Value == GetInsigniaByName("ประถมาภรณ์มงกุฎไทย").Id) + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value == GetInsigniaByName("ประถมาภรณ์มงกุฎไทย")?.Id) .OrderBy(x => x.Year).FirstOrDefault() == null ? 0 : p.ProfileInsignia - .Where(x => x.InsigniaId.Value == GetInsigniaByName("ประถมาภรณ์มงกุฎไทย").Id) - .OrderBy(x => x.Year).FirstOrDefault().Year, + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value == GetInsigniaByName("ประถมาภรณ์มงกุฎไทย")?.Id) + .OrderBy(x => x.Year).FirstOrDefault()?.Year, PositionSalaryAmount = p.PositionSalaryAmount, Amount = p.Amount, RootId = p.RootId, @@ -7784,8 +7818,9 @@ namespace BMA.EHR.Application.Repositories && p.ProfileInsignia != null && p.ProfileInsignia.Count > 0 && (p.ProfileInsignia.Where(x => - x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id && - x.InsigniaId.Value == GetInsigniaByName("มหาวชิรมงกุฎ").Id) + x.InsigniaId.HasValue && + x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id && + x.InsigniaId.Value == GetInsigniaByName("มหาวชิรมงกุฎ")?.Id) .ToList() .Count() == 0) select new @@ -7804,25 +7839,25 @@ namespace BMA.EHR.Application.Repositories ProfileDateAppoint = p.DateAppoint.Value, LastInsignia = p.ProfileInsignia == null || p.ProfileInsignia.Count == 0 ? "" : p.ProfileInsignia - .Where(x => x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) .FirstOrDefault() == null ? "" : p.ProfileInsignia - .Where(x => x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) - .OrderByDescending(x => x.Year).FirstOrDefault().Insignia, + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) + .OrderByDescending(x => x.Year).FirstOrDefault()?.Insignia, LastInsigniaId = p.ProfileInsignia == null || p.ProfileInsignia.Count == 0 ? Guid.Empty : p.ProfileInsignia - .Where(x => x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) .FirstOrDefault() == null ? Guid.Empty : p.ProfileInsignia - .Where(x => x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) - .OrderByDescending(x => x.Year).FirstOrDefault().InsigniaId, + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) + .OrderByDescending(x => x.Year).FirstOrDefault()?.InsigniaId, Salary = p.Amount, SalaryPosition = p.PositionSalaryAmount, SalaryCondition = p.ProfileSalary == null || p.ProfileSalary.Count == 0 ? 0 : - p.ProfileSalary.Where(x => x.Date != null).Where(x => x.Date.Value <= new DateTime(period.Year, 4, 29)) + p.ProfileSalary.Where(x => x.Date != null).Where(x => x.Date.HasValue && x.Date.Value <= new DateTime(period.Year, 4, 29)) .OrderByDescending(x => x.Order).FirstOrDefault() != null ? p.ProfileSalary - .Where(x => x.Date != null).Where(x => x.Date.Value <= new DateTime(period.Year, 4, 29)) - .OrderByDescending(x => x.Order).FirstOrDefault().Amount : + .Where(x => x.Date != null).Where(x => x.Date.HasValue && x.Date.Value <= new DateTime(period.Year, 4, 29)) + .OrderByDescending(x => x.Order).FirstOrDefault()?.Amount : p.Amount, PosNo = p.PosNo, Gender = p.Gender == null ? null : p.Gender, @@ -7832,12 +7867,12 @@ namespace BMA.EHR.Application.Repositories PositionTypeName = p.PosType, IsHigherLevel = p.ProfileInsignia - .Where(x => x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) .FirstOrDefault() == null ? true : IsHigherLevel(p.ProfileInsignia - .Where(x => x.InsigniaId.Value != - GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != + GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) .OrderByDescending(x => x.Year) .FirstOrDefault(), "มหาวชิรมงกุฎ"), @@ -7847,14 +7882,14 @@ namespace BMA.EHR.Application.Repositories p.ProfileSalary.Where(x => x.PositionLevel == "สูง" && x.PositionType == "บริหาร") .OrderBy(p => p.Date).FirstOrDefault() == null ? null : p.ProfileSalary.Where(x => x.PositionLevel == "สูง" && x.PositionType == "บริหาร") - .OrderBy(p => p.Date).FirstOrDefault().Date, + .OrderBy(p => p.Date).FirstOrDefault()?.Date, FirstRecvInsigniaYear = p.ProfileInsignia.Count == 0 ? 0 : p.ProfileInsignia - .Where(x => x.InsigniaId.Value == GetInsigniaByName("ประถมาภรณ์ช้างเผือก").Id) + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value == GetInsigniaByName("ประถมาภรณ์ช้างเผือก")?.Id) .OrderBy(x => x.Year).FirstOrDefault() == null ? 0 : p.ProfileInsignia - .Where(x => x.InsigniaId.Value == GetInsigniaByName("ประถมาภรณ์ช้างเผือก").Id) - .OrderBy(x => x.Year).FirstOrDefault().Year, + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value == GetInsigniaByName("ประถมาภรณ์ช้างเผือก")?.Id) + .OrderBy(x => x.Year).FirstOrDefault()?.Year, PositionSalaryAmount = p.PositionSalaryAmount, Amount = p.Amount, RootId = p.RootId, @@ -7951,8 +7986,9 @@ namespace BMA.EHR.Application.Repositories && p.ProfileInsignia != null && p.ProfileInsignia.Count > 0 && (p.ProfileInsignia.Where(x => - x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id && - x.InsigniaId.Value == GetInsigniaByName("มหาปรมาภรณ์ช้างเผือก").Id) + x.InsigniaId.HasValue && + x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id && + x.InsigniaId.Value == GetInsigniaByName("มหาปรมาภรณ์ช้างเผือก")?.Id) .ToList() .Count() == 0) select new @@ -7971,25 +8007,25 @@ namespace BMA.EHR.Application.Repositories ProfileDateAppoint = p.DateAppoint.Value, LastInsignia = p.ProfileInsignia == null || p.ProfileInsignia.Count == 0 ? "" : p.ProfileInsignia - .Where(x => x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) .FirstOrDefault() == null ? "" : p.ProfileInsignia - .Where(x => x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) - .OrderByDescending(x => x.Year).FirstOrDefault().Insignia, + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) + .OrderByDescending(x => x.Year).FirstOrDefault()?.Insignia, LastInsigniaId = p.ProfileInsignia == null || p.ProfileInsignia.Count == 0 ? Guid.Empty : p.ProfileInsignia - .Where(x => x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) .FirstOrDefault() == null ? Guid.Empty : p.ProfileInsignia - .Where(x => x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) - .OrderByDescending(x => x.Year).FirstOrDefault().InsigniaId, + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) + .OrderByDescending(x => x.Year).FirstOrDefault()?.InsigniaId, Salary = p.Amount, SalaryPosition = p.PositionSalaryAmount, SalaryCondition = p.ProfileSalary == null || p.ProfileSalary.Count == 0 ? 0 : - p.ProfileSalary.Where(x => x.Date != null).Where(x => x.Date.Value <= new DateTime(period.Year, 4, 29)) + p.ProfileSalary.Where(x => x.Date != null).Where(x => x.Date.HasValue && x.Date.Value <= new DateTime(period.Year, 4, 29)) .OrderByDescending(x => x.Order).FirstOrDefault() != null ? p.ProfileSalary - .Where(x => x.Date != null).Where(x => x.Date.Value <= new DateTime(period.Year, 4, 29)) - .OrderByDescending(x => x.Order).FirstOrDefault().Amount : + .Where(x => x.Date != null).Where(x => x.Date.HasValue && x.Date.Value <= new DateTime(period.Year, 4, 29)) + .OrderByDescending(x => x.Order).FirstOrDefault()?.Amount : p.Amount, PosNo = p.PosNo, Gender = p.Gender == null ? null : p.Gender, @@ -7999,12 +8035,12 @@ namespace BMA.EHR.Application.Repositories PositionTypeName = p.PosType, IsHigherLevel = p.ProfileInsignia - .Where(x => x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) .FirstOrDefault() == null ? true : IsHigherLevel(p.ProfileInsignia - .Where(x => x.InsigniaId.Value != - GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != + GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) .OrderByDescending(x => x.Year) .FirstOrDefault(), "มหาปรมาภรณ์ช้างเผือก"), @@ -8014,12 +8050,12 @@ namespace BMA.EHR.Application.Repositories p.ProfileSalary.Where(x => x.PositionLevel == "สูง" && x.PositionType == "บริหาร") .OrderBy(p => p.Date).FirstOrDefault() == null ? null : p.ProfileSalary.Where(x => x.PositionLevel == "สูง" && x.PositionType == "บริหาร") - .OrderBy(p => p.Date).FirstOrDefault().Date, + .OrderBy(p => p.Date).FirstOrDefault()?.Date, FirstRecvInsigniaYear = p.ProfileInsignia.Count == 0 ? 0 : - p.ProfileInsignia.Where(x => x.InsigniaId.Value == GetInsigniaByName("มหาวชิรมงกุฎ").Id) + p.ProfileInsignia.Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value == GetInsigniaByName("มหาวชิรมงกุฎ")?.Id) .OrderBy(x => x.Year).FirstOrDefault() == null ? 0 : - p.ProfileInsignia.Where(x => x.InsigniaId.Value == GetInsigniaByName("มหาวชิรมงกุฎ").Id) - .OrderBy(x => x.Year).FirstOrDefault().Year, + p.ProfileInsignia.Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value == GetInsigniaByName("มหาวชิรมงกุฎ")?.Id) + .OrderBy(x => x.Year).FirstOrDefault()?.Year, PositionSalaryAmount = p.PositionSalaryAmount, Amount = p.Amount, RootId = p.RootId, @@ -8179,144 +8215,146 @@ namespace BMA.EHR.Application.Repositories if (type.ToLower().Trim() == "officer") { var allOfficerProfilesByRoot = (await _userProfileRepository.GetOfficerProfileByRootIdAsync(ocId, AccessToken)); - - // calculate ตามแต่ละชั้น - var type_coin = allOfficerProfilesByRoot.Count() > 0 ? await GetCoinCandidate(periodId, ocId, allOfficerProfilesByRoot) : new List(); - var type1_level1 = allOfficerProfilesByRoot.Count() > 0 ? await GetInsigniaCandidate_Type1_Level1(periodId, ocId, allOfficerProfilesByRoot) : new List(); - var type1_level2 = allOfficerProfilesByRoot.Count() > 0 ? await GetInsigniaCandidate_Type1_Level2(periodId, ocId, allOfficerProfilesByRoot) : new List(); - var type1_level3 = allOfficerProfilesByRoot.Count() > 0 ? await GetInsigniaCandidate_Type1_Level3(periodId, ocId, allOfficerProfilesByRoot) : new List(); - var type1_level4 = allOfficerProfilesByRoot.Count() > 0 ? await GetInsigniaCandidate_Type1_Level4(periodId, ocId, allOfficerProfilesByRoot) : new List(); - var type2_level5 = allOfficerProfilesByRoot.Count() > 0 ? await GetInsigniaCandidate_Type2_Level5(periodId, ocId, allOfficerProfilesByRoot) : new List(); - var type2_level6 = allOfficerProfilesByRoot.Count() > 0 ? await GetInsigniaCandidate_Type2_Level6(periodId, ocId, allOfficerProfilesByRoot) : new List(); - var type2_level7 = allOfficerProfilesByRoot.Count() > 0 ? await GetInsigniaCandidate_Type2_Level7(periodId, ocId, allOfficerProfilesByRoot) : new List(); - var type2_level8 = allOfficerProfilesByRoot.Count() > 0 ? await GetInsigniaCandidate_Type2_Level8(periodId, ocId, allOfficerProfilesByRoot) : new List(); - var type2_level9_1 = - allOfficerProfilesByRoot.Count() > 0 ? await GetInsigniaCandidate_Type2_Level9_1(periodId, ocId, allOfficerProfilesByRoot) : new List(); - var type2_level9_2 = - allOfficerProfilesByRoot.Count() > 0 ? await GetInsigniaCandidate_Type2_Level9_2(periodId, ocId, allOfficerProfilesByRoot) : new List(); - var type3_level10 = allOfficerProfilesByRoot.Count() > 0 ? await GetInsigniaCandidate_Type3_Level10(periodId, ocId, allOfficerProfilesByRoot) : new List(); - var type3_level11 = allOfficerProfilesByRoot.Count() > 0 ? await GetInsigniaCandidate_Type3_Level11(periodId, ocId, allOfficerProfilesByRoot) : new List(); - var type4_level10 = allOfficerProfilesByRoot.Count() > 0 ? await GetInsigniaCandidate_Type4_Level10(periodId, ocId, allOfficerProfilesByRoot) : new List(); - var type4_level11 = allOfficerProfilesByRoot.Count() > 0 ? await GetInsigniaCandidate_Type4_Level11(periodId, ocId, allOfficerProfilesByRoot) : new List(); - - - // union result - foreach (var r in type_coin) + if (allOfficerProfilesByRoot != null) { - var old = result_candidate.FirstOrDefault(x => x.ProfileId == r.ProfileId); - if (old == null) - result_candidate.Add(r); - } - - foreach (var r in type4_level11) - { - var old = result_candidate.FirstOrDefault(x => x.ProfileId == r.ProfileId); - if (old == null) - result_candidate.Add(r); - } - - foreach (var r in type4_level10) - { - var old = result_candidate.FirstOrDefault(x => x.ProfileId == r.ProfileId); - if (old == null) - result_candidate.Add(r); - } - - foreach (var r in type3_level11) - { - var old = result_candidate.FirstOrDefault(x => x.ProfileId == r.ProfileId); - if (old == null) - result_candidate.Add(r); - } - - foreach (var r in type3_level10) - { - var old = result_candidate.FirstOrDefault(x => x.ProfileId == r.ProfileId); - if (old == null) - result_candidate.Add(r); - } - - foreach (var r in type2_level9_2) - { - var old = result_candidate.FirstOrDefault(x => x.ProfileId == r.ProfileId); - if (old == null) - result_candidate.Add(r); - } - - foreach (var r in type2_level9_1) - { - var old = result_candidate.FirstOrDefault(x => x.ProfileId == r.ProfileId); - if (old == null) - result_candidate.Add(r); - } - - foreach (var r in type2_level8) - { - var old = result_candidate.FirstOrDefault(x => x.ProfileId == r.ProfileId); - if (old == null) - result_candidate.Add(r); - } - - foreach (var r in type2_level7) - { - var old = result_candidate.FirstOrDefault(x => x.ProfileId == r.ProfileId); - if (old == null) - result_candidate.Add(r); - } - - foreach (var r in type2_level6) - { - var old = result_candidate.FirstOrDefault(x => x.ProfileId == r.ProfileId); - if (old == null) - result_candidate.Add(r); - } - - foreach (var r in type2_level5) - { - var old = result_candidate.FirstOrDefault(x => x.ProfileId == r.ProfileId); - if (old == null) - result_candidate.Add(r); - } - - foreach (var r in type1_level4) - { - var old = result_candidate.FirstOrDefault(x => x.ProfileId == r.ProfileId); - if (old == null) - result_candidate.Add(r); - } - - foreach (var r in type1_level3) - { - var old = result_candidate.FirstOrDefault(x => x.ProfileId == r.ProfileId); - if (old == null) - result_candidate.Add(r); - } - - foreach (var r in type1_level2) - { - var old = result_candidate.FirstOrDefault(x => x.ProfileId == r.ProfileId); - if (old == null) - result_candidate.Add(r); - } - - foreach (var r in type1_level1) - { - var old = result_candidate.FirstOrDefault(x => x.ProfileId == r.ProfileId); - if (old == null) - result_candidate.Add(r); - } - - // ย้ายที่ตามที่ มอสแจ้ง - if (period.Round != 1) - { - var insigniaIdList = await _dbContext.Set() - .Include(x => x.InsigniaType) - .Where(x => x.InsigniaType!.Name == "ชั้นสายสะพาย") - .Select(x => x.Id) - .ToListAsync(); + // calculate ตามแต่ละชั้น + var type_coin = allOfficerProfilesByRoot.Count() > 0 ? await GetCoinCandidate(periodId, ocId, allOfficerProfilesByRoot) : new List(); + var type1_level1 = allOfficerProfilesByRoot.Count() > 0 ? await GetInsigniaCandidate_Type1_Level1(periodId, ocId, allOfficerProfilesByRoot) : new List(); + var type1_level2 = allOfficerProfilesByRoot.Count() > 0 ? await GetInsigniaCandidate_Type1_Level2(periodId, ocId, allOfficerProfilesByRoot) : new List(); + var type1_level3 = allOfficerProfilesByRoot.Count() > 0 ? await GetInsigniaCandidate_Type1_Level3(periodId, ocId, allOfficerProfilesByRoot) : new List(); + var type1_level4 = allOfficerProfilesByRoot.Count() > 0 ? await GetInsigniaCandidate_Type1_Level4(periodId, ocId, allOfficerProfilesByRoot) : new List(); + var type2_level5 = allOfficerProfilesByRoot.Count() > 0 ? await GetInsigniaCandidate_Type2_Level5(periodId, ocId, allOfficerProfilesByRoot) : new List(); + var type2_level6 = allOfficerProfilesByRoot.Count() > 0 ? await GetInsigniaCandidate_Type2_Level6(periodId, ocId, allOfficerProfilesByRoot) : new List(); + var type2_level7 = allOfficerProfilesByRoot.Count() > 0 ? await GetInsigniaCandidate_Type2_Level7(periodId, ocId, allOfficerProfilesByRoot) : new List(); + var type2_level8 = allOfficerProfilesByRoot.Count() > 0 ? await GetInsigniaCandidate_Type2_Level8(periodId, ocId, allOfficerProfilesByRoot) : new List(); + var type2_level9_1 = + allOfficerProfilesByRoot.Count() > 0 ? await GetInsigniaCandidate_Type2_Level9_1(periodId, ocId, allOfficerProfilesByRoot) : new List(); + var type2_level9_2 = + allOfficerProfilesByRoot.Count() > 0 ? await GetInsigniaCandidate_Type2_Level9_2(periodId, ocId, allOfficerProfilesByRoot) : new List(); + var type3_level10 = allOfficerProfilesByRoot.Count() > 0 ? await GetInsigniaCandidate_Type3_Level10(periodId, ocId, allOfficerProfilesByRoot) : new List(); + var type3_level11 = allOfficerProfilesByRoot.Count() > 0 ? await GetInsigniaCandidate_Type3_Level11(periodId, ocId, allOfficerProfilesByRoot) : new List(); + var type4_level10 = allOfficerProfilesByRoot.Count() > 0 ? await GetInsigniaCandidate_Type4_Level10(periodId, ocId, allOfficerProfilesByRoot) : new List(); + var type4_level11 = allOfficerProfilesByRoot.Count() > 0 ? await GetInsigniaCandidate_Type4_Level11(periodId, ocId, allOfficerProfilesByRoot) : new List(); - result_candidate = result_candidate.Where(x => insigniaIdList.Contains(x.RequestInsignia.Id)).ToList(); + // union result + foreach (var r in type_coin) + { + var old = result_candidate.FirstOrDefault(x => x.ProfileId == r.ProfileId); + if (old == null) + result_candidate.Add(r); + } + + foreach (var r in type4_level11) + { + var old = result_candidate.FirstOrDefault(x => x.ProfileId == r.ProfileId); + if (old == null) + result_candidate.Add(r); + } + + foreach (var r in type4_level10) + { + var old = result_candidate.FirstOrDefault(x => x.ProfileId == r.ProfileId); + if (old == null) + result_candidate.Add(r); + } + + foreach (var r in type3_level11) + { + var old = result_candidate.FirstOrDefault(x => x.ProfileId == r.ProfileId); + if (old == null) + result_candidate.Add(r); + } + + foreach (var r in type3_level10) + { + var old = result_candidate.FirstOrDefault(x => x.ProfileId == r.ProfileId); + if (old == null) + result_candidate.Add(r); + } + + foreach (var r in type2_level9_2) + { + var old = result_candidate.FirstOrDefault(x => x.ProfileId == r.ProfileId); + if (old == null) + result_candidate.Add(r); + } + + foreach (var r in type2_level9_1) + { + var old = result_candidate.FirstOrDefault(x => x.ProfileId == r.ProfileId); + if (old == null) + result_candidate.Add(r); + } + + foreach (var r in type2_level8) + { + var old = result_candidate.FirstOrDefault(x => x.ProfileId == r.ProfileId); + if (old == null) + result_candidate.Add(r); + } + + foreach (var r in type2_level7) + { + var old = result_candidate.FirstOrDefault(x => x.ProfileId == r.ProfileId); + if (old == null) + result_candidate.Add(r); + } + + foreach (var r in type2_level6) + { + var old = result_candidate.FirstOrDefault(x => x.ProfileId == r.ProfileId); + if (old == null) + result_candidate.Add(r); + } + + foreach (var r in type2_level5) + { + var old = result_candidate.FirstOrDefault(x => x.ProfileId == r.ProfileId); + if (old == null) + result_candidate.Add(r); + } + + foreach (var r in type1_level4) + { + var old = result_candidate.FirstOrDefault(x => x.ProfileId == r.ProfileId); + if (old == null) + result_candidate.Add(r); + } + + foreach (var r in type1_level3) + { + var old = result_candidate.FirstOrDefault(x => x.ProfileId == r.ProfileId); + if (old == null) + result_candidate.Add(r); + } + + foreach (var r in type1_level2) + { + var old = result_candidate.FirstOrDefault(x => x.ProfileId == r.ProfileId); + if (old == null) + result_candidate.Add(r); + } + + foreach (var r in type1_level1) + { + var old = result_candidate.FirstOrDefault(x => x.ProfileId == r.ProfileId); + if (old == null) + result_candidate.Add(r); + } + + // ย้ายที่ตามที่ มอสแจ้ง + if (period.Round != 1) + { + var insigniaIdList = await _dbContext.Set() + .Include(x => x.InsigniaType) + .Where(x => x.InsigniaType!.Name == "ชั้นสายสะพาย") + .Select(x => x.Id) + .ToListAsync(); + + + result_candidate = result_candidate.Where(x => insigniaIdList.Contains(x.RequestInsignia.Id)).ToList(); + } } } else if (type.ToLower().Trim() == "employee") @@ -8327,31 +8365,34 @@ namespace BMA.EHR.Application.Repositories allEmployeeProfileByRoot = (await _userProfileRepository.GetEmployeeProfileByPositionAsync(ocId, period.InsigniaEmployees.Select(x => x.RefId!.ValueOrBlank()).ToArray(), AccessToken)); } - var type_coin = allEmployeeProfileByRoot.Count() > 0 ? await GetCoinCandidate(periodId, ocId, allEmployeeProfileByRoot) : new List(); - - var employee_type1 = allEmployeeProfileByRoot.Count() > 0 ? await GetEmployeeInsignia_Type1(periodId, ocId, allEmployeeProfileByRoot) : new List(); - var employee_type2 = allEmployeeProfileByRoot.Count() > 0 ? await GetEmployeeInsignia_Type2(periodId, ocId, allEmployeeProfileByRoot) : new List(); - - // union result - foreach (var r in type_coin) + if (allEmployeeProfileByRoot != null) { - var old = result_candidate.FirstOrDefault(x => x.ProfileId == r.ProfileId); - if (old == null) - result_candidate.Add(r); - } + var type_coin = allEmployeeProfileByRoot.Count() > 0 ? await GetCoinCandidate(periodId, ocId, allEmployeeProfileByRoot) : new List(); - foreach (var r in employee_type2) - { - var old = result_candidate.FirstOrDefault(x => x.ProfileId == r.ProfileId); - if (old == null) - result_candidate.Add(r); - } + var employee_type1 = allEmployeeProfileByRoot.Count() > 0 ? await GetEmployeeInsignia_Type1(periodId, ocId, allEmployeeProfileByRoot) : new List(); + var employee_type2 = allEmployeeProfileByRoot.Count() > 0 ? await GetEmployeeInsignia_Type2(periodId, ocId, allEmployeeProfileByRoot) : new List(); - foreach (var r in employee_type1) - { - var old = result_candidate.FirstOrDefault(x => x.ProfileId == r.ProfileId); - if (old == null) - result_candidate.Add(r); + // union result + foreach (var r in type_coin) + { + var old = result_candidate.FirstOrDefault(x => x.ProfileId == r.ProfileId); + if (old == null) + result_candidate.Add(r); + } + + foreach (var r in employee_type2) + { + var old = result_candidate.FirstOrDefault(x => x.ProfileId == r.ProfileId); + if (old == null) + result_candidate.Add(r); + } + + foreach (var r in employee_type1) + { + var old = result_candidate.FirstOrDefault(x => x.ProfileId == r.ProfileId); + if (old == null) + result_candidate.Add(r); + } } } else @@ -8571,7 +8612,7 @@ namespace BMA.EHR.Application.Repositories if (period == null) throw new Exception(GlobalMessages.CoinPeriodNotFound); - var inst_profile = allProfilesByRoot.Where(x => x.DateAppoint != null) + var inst_profile = allProfilesByRoot.Where(x => x.DateAppoint.HasValue && x.DateAppoint != null) .Select(p => new { ProfileId = p.Id, @@ -8594,24 +8635,24 @@ namespace BMA.EHR.Application.Repositories Gender = p.Gender ?? "", LastInsignia = p.ProfileInsignia == null || p.ProfileInsignia.Count == 0 ? "" : p.ProfileInsignia - .Where(x => x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) .FirstOrDefault() == null ? "" : p.ProfileInsignia - .Where(x => x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) - .OrderByDescending(x => x.Year).FirstOrDefault().Insignia, + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) + .OrderByDescending(x => x.Year).FirstOrDefault()?.Insignia, LastInsigniaId = p.ProfileInsignia == null || p.ProfileInsignia.Count == 0 ? Guid.Empty : p.ProfileInsignia - .Where(x => x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) .FirstOrDefault() == null ? Guid.Empty : p.ProfileInsignia - .Where(x => x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา").Id) - .OrderByDescending(x => x.Year).FirstOrDefault().InsigniaId, + .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) + .OrderByDescending(x => x.Year).FirstOrDefault()?.InsigniaId, Salary = p.Amount, SalaryCondition = p.ProfileSalary == null || p.ProfileSalary.Count == 0 ? 0 : - p.ProfileSalary.Where(x => x.Date != null).Where(x => x.Date.Value <= new DateTime(period.Year, 4, 29)) + p.ProfileSalary.Where(x => x.Date != null).Where(x => x.Date.HasValue && x.Date.Value <= new DateTime(period.Year, 4, 29)) .OrderByDescending(x => x.Order).FirstOrDefault() != null ? p.ProfileSalary - .Where(x => x.Date != null).Where(x => x.Date.Value <= new DateTime(period.Year, 4, 29)) - .OrderByDescending(x => x.Order).FirstOrDefault().Amount : + .Where(x => x.Date != null).Where(x => x.Date.HasValue && x.Date.Value <= new DateTime(period.Year, 4, 29)) + .OrderByDescending(x => x.Order).FirstOrDefault()?.Amount : p.Amount, PositionSalaryAmount = p.PositionSalaryAmount, ProfileInsignia = p.ProfileInsignia, From f9ca9b52af70ef1eb8fa2d62a7bd7a542574d28d Mon Sep 17 00:00:00 2001 From: Adisak Date: Mon, 5 Jan 2026 16:38:30 +0700 Subject: [PATCH 015/183] #2150 --- .../Controllers/PlacementReceiveController.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/BMA.EHR.Placement.Service/Controllers/PlacementReceiveController.cs b/BMA.EHR.Placement.Service/Controllers/PlacementReceiveController.cs index f18e2b32..db8a28a7 100644 --- a/BMA.EHR.Placement.Service/Controllers/PlacementReceiveController.cs +++ b/BMA.EHR.Placement.Service/Controllers/PlacementReceiveController.cs @@ -553,18 +553,23 @@ namespace BMA.EHR.Placement.Service.Controllers // placementReceive.citizenId = org.result.citizenId; placementReceive.rootOld = org.result.root; placementReceive.rootOldId = org.result.rootId; + placementReceive.rootDnaId = org.result.rootDnaId; placementReceive.rootShortNameOld = org.result.rootShortName; placementReceive.child1Old = org.result.child1; placementReceive.child1OldId = org.result.child1Id; + placementReceive.child1DnaId = org.result.child1DnaId; placementReceive.child1ShortNameOld = org.result.child1ShortName; placementReceive.child2Old = org.result.child2; placementReceive.child2OldId = org.result.child2Id; + placementReceive.child2DnaId = org.result.child2DnaId; placementReceive.child2ShortNameOld = org.result.child2ShortName; placementReceive.child3Old = org.result.child3; placementReceive.child3OldId = org.result.child3Id; + placementReceive.child4DnaId = org.result.child4DnaId; placementReceive.child3ShortNameOld = org.result.child3ShortName; placementReceive.child4Old = org.result.child4; placementReceive.child4OldId = org.result.child4Id; + placementReceive.child4DnaId = org.result.child4DnaId; placementReceive.child4ShortNameOld = org.result.child4ShortName; placementReceive.posMasterNoOld = org.result.posMasterNo; placementReceive.posTypeOldId = org.result.posTypeId; From e8dfe976a2eaf3f407c2db0e9e44bbd3ecadad0f Mon Sep 17 00:00:00 2001 From: Suphonchai Phoonsawat Date: Wed, 7 Jan 2026 12:06:49 +0700 Subject: [PATCH 016/183] remove workflow integration from LeaveRequestController #2164 --- .../Controllers/LeaveRequestController.cs | 81 ++++++++++--------- 1 file changed, 41 insertions(+), 40 deletions(-) diff --git a/BMA.EHR.Leave/Controllers/LeaveRequestController.cs b/BMA.EHR.Leave/Controllers/LeaveRequestController.cs index 09addc23..9233b374 100644 --- a/BMA.EHR.Leave/Controllers/LeaveRequestController.cs +++ b/BMA.EHR.Leave/Controllers/LeaveRequestController.cs @@ -2247,46 +2247,47 @@ namespace BMA.EHR.Leave.Service.Controllers { await _leaveRequestRepository.SendToOfficerAsync(id); - var userId = UserId == null ? Guid.Empty : Guid.Parse(UserId); - var profile = await _userProfileRepository.GetProfileByKeycloakIdAsync(userId, AccessToken); - if (profile == null) - { - return Error(GlobalMessages.DataNotFound, StatusCodes.Status404NotFound); - } - var baseAPIOrg = _configuration["API"]; - var apiUrlOrg = $"{baseAPIOrg}/org/workflow/add-workflow"; - if (profile.ProfileType == "OFFICER") - { - using (var client = new HttpClient()) - { - client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", AccessToken.Replace("Bearer ", "")); - client.DefaultRequestHeaders.Add("api-key", _configuration["API_KEY"]); - var _res = await client.PostAsJsonAsync(apiUrlOrg, new - { - refId = id, - sysName = "SYS_LEAVE_LIST", - posLevelName = profile.PosLevel ?? "", - posTypeName = profile.PosType ?? "", - fullName = $"{profile.Prefix}{profile.FirstName} {profile.LastName}" - }); - } - } - else - { - using (var client = new HttpClient()) - { - client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", AccessToken.Replace("Bearer ", "")); - client.DefaultRequestHeaders.Add("api-key", _configuration["API_KEY"]); - var _res = await client.PostAsJsonAsync(apiUrlOrg, new - { - refId = id, - sysName = "SYS_LEAVE_LIST_EMP", - posLevelName = profile.PosLevel ?? "", - posTypeName = profile.PosType ?? "", - fullName = $"{profile.Prefix}{profile.FirstName} {profile.LastName}" - }); - } - } + // Remove Workflow Integration + // var userId = UserId == null ? Guid.Empty : Guid.Parse(UserId); + // var profile = await _userProfileRepository.GetProfileByKeycloakIdAsync(userId, AccessToken); + // if (profile == null) + // { + // return Error(GlobalMessages.DataNotFound, StatusCodes.Status404NotFound); + // } + // var baseAPIOrg = _configuration["API"]; + // var apiUrlOrg = $"{baseAPIOrg}/org/workflow/add-workflow"; + // if (profile.ProfileType == "OFFICER") + // { + // using (var client = new HttpClient()) + // { + // client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", AccessToken.Replace("Bearer ", "")); + // client.DefaultRequestHeaders.Add("api-key", _configuration["API_KEY"]); + // var _res = await client.PostAsJsonAsync(apiUrlOrg, new + // { + // refId = id, + // sysName = "SYS_LEAVE_LIST", + // posLevelName = profile.PosLevel ?? "", + // posTypeName = profile.PosType ?? "", + // fullName = $"{profile.Prefix}{profile.FirstName} {profile.LastName}" + // }); + // } + // } + // else + // { + // using (var client = new HttpClient()) + // { + // client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", AccessToken.Replace("Bearer ", "")); + // client.DefaultRequestHeaders.Add("api-key", _configuration["API_KEY"]); + // var _res = await client.PostAsJsonAsync(apiUrlOrg, new + // { + // refId = id, + // sysName = "SYS_LEAVE_LIST_EMP", + // posLevelName = profile.PosLevel ?? "", + // posTypeName = profile.PosType ?? "", + // fullName = $"{profile.Prefix}{profile.FirstName} {profile.LastName}" + // }); + // } + // } return Success(); } From 8c1a219084b6f0ad592b77d6a09bc45699e95b16 Mon Sep 17 00:00:00 2001 From: waruneeauy Date: Fri, 9 Jan 2026 16:29:43 +0700 Subject: [PATCH 017/183] fix build report to v2 and add noti --- ...-release-report.yaml => dockerhub-release-reportv2.yaml} | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) rename .github/workflows/{dockerhub-release-report.yaml => dockerhub-release-reportv2.yaml} (94%) diff --git a/.github/workflows/dockerhub-release-report.yaml b/.github/workflows/dockerhub-release-reportv2.yaml similarity index 94% rename from .github/workflows/dockerhub-release-report.yaml rename to .github/workflows/dockerhub-release-reportv2.yaml index 132b218b..f3ddead5 100644 --- a/.github/workflows/dockerhub-release-report.yaml +++ b/.github/workflows/dockerhub-release-reportv2.yaml @@ -3,7 +3,7 @@ run-name: DockerHub Release - Report Service by ${{ github.actor }} on: push: tags: - - "report-[0-9]+.[0-9]+.[0-9]+" + - "reportv2-[0-9]+.[0-9]+.[0-9]+" workflow_dispatch: inputs: IMAGE_VER: @@ -13,7 +13,7 @@ on: env: DOCKERHUB_REGISTRY: docker.io - IMAGE_NAME: hrms-api-report + IMAGE_NAME: hrms-api-reportv2 jobs: release-to-dockerhub: @@ -59,7 +59,7 @@ jobs: uses: docker/build-push-action@v5 with: context: . - file: BMA.EHR.Report.Service/Dockerfile + file: BMA.EHR.ReportV2.Service/Dockerfile platforms: linux/amd64 push: true tags: | From 0690337422cee2deaac48dc6fe416fcdeb05202a Mon Sep 17 00:00:00 2001 From: waruneeauy Date: Fri, 9 Jan 2026 16:35:33 +0700 Subject: [PATCH 018/183] fix build report --- .../workflows/dockerhub-release-reportv2.yaml | 31 ++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/.github/workflows/dockerhub-release-reportv2.yaml b/.github/workflows/dockerhub-release-reportv2.yaml index f3ddead5..4dc73f3f 100644 --- a/.github/workflows/dockerhub-release-reportv2.yaml +++ b/.github/workflows/dockerhub-release-reportv2.yaml @@ -14,6 +14,7 @@ on: env: DOCKERHUB_REGISTRY: docker.io IMAGE_NAME: hrms-api-reportv2 + DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }} jobs: release-to-dockerhub: @@ -59,7 +60,7 @@ jobs: uses: docker/build-push-action@v5 with: context: . - file: BMA.EHR.ReportV2.Service/Dockerfile + file: BMA.EHR.Report.Service/Dockerfile platforms: linux/amd64 push: true tags: | @@ -70,3 +71,31 @@ jobs: org.opencontainers.image.description=HRMS Report API Service cache-from: type=gha cache-to: type=gha,mode=max + + - name: Notify Discord on success + if: success() + env: + IMAGE_VER: ${{ steps.gen_ver.outputs.image_ver }} + run: | + TAG_INFO="Tag: ${IMAGE_VER:-unknown}" + REF_INFO="Ref: ${GITHUB_REF}" + ACTOR_INFO="Actor: ${GITHUB_ACTOR}" + MSG="✅ DockerHub release succeeded\n${TAG_INFO}\n${REF_INFO}\n${ACTOR_INFO}" + curl -s -H "Content-Type: application/json" \ + -X POST \ + -d "{\"content\":\"${MSG}\"}" \ + "$DISCORD_WEBHOOK" + + - name: Notify Discord on failure + if: failure() + env: + IMAGE_VER: ${{ steps.gen_ver.outputs.image_ver }} + run: | + TAG_INFO="Tag: ${IMAGE_VER:-unknown}" + REF_INFO="Ref: ${GITHUB_REF}" + ACTOR_INFO="Actor: ${GITHUB_ACTOR}" + MSG="❌ DockerHub release failed\n${TAG_INFO}\n${REF_INFO}\n${ACTOR_INFO}" + curl -s -H "Content-Type: application/json" \ + -X POST \ + -d "{\"content\":\"${MSG}\"}" \ + "$DISCORD_WEBHOOK" From a54091220251c92e6632b78cd73ff0b609f566dc Mon Sep 17 00:00:00 2001 From: waruneeauy Date: Fri, 9 Jan 2026 18:43:59 +0700 Subject: [PATCH 019/183] add noti discord --- .../workflows/dockerhub-release-checkin.yaml | 29 +++++++++++++++++++ .../workflows/dockerhub-release-command.yaml | 29 +++++++++++++++++++ .../dockerhub-release-discipline.yaml | 29 +++++++++++++++++++ .../workflows/dockerhub-release-insignia.yaml | 29 +++++++++++++++++++ .../workflows/dockerhub-release-leave.yaml | 29 +++++++++++++++++++ .../dockerhub-release-placement.yaml | 29 +++++++++++++++++++ .../dockerhub-release-retirement.yaml | 29 +++++++++++++++++++ 7 files changed, 203 insertions(+) diff --git a/.github/workflows/dockerhub-release-checkin.yaml b/.github/workflows/dockerhub-release-checkin.yaml index 9e7c4e23..b80c44f7 100644 --- a/.github/workflows/dockerhub-release-checkin.yaml +++ b/.github/workflows/dockerhub-release-checkin.yaml @@ -14,6 +14,7 @@ on: env: DOCKERHUB_REGISTRY: docker.io IMAGE_NAME: hrms-api-checkin + DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }} jobs: release-to-dockerhub: @@ -70,3 +71,31 @@ jobs: org.opencontainers.image.description=HRMS CheckIn Consumer Service cache-from: type=gha cache-to: type=gha,mode=max + + - name: Notify Discord on success + if: success() + env: + IMAGE_VER: ${{ steps.gen_ver.outputs.image_ver }} + run: | + TAG_INFO="Tag: ${IMAGE_VER:-unknown}" + REF_INFO="Ref: ${GITHUB_REF}" + ACTOR_INFO="Actor: ${GITHUB_ACTOR}" + MSG="✅ DockerHub release succeeded\n${TAG_INFO}\n${REF_INFO}\n${ACTOR_INFO}" + curl -s -H "Content-Type: application/json" \ + -X POST \ + -d "{\"content\":\"${MSG}\"}" \ + "$DISCORD_WEBHOOK" + + - name: Notify Discord on failure + if: failure() + env: + IMAGE_VER: ${{ steps.gen_ver.outputs.image_ver }} + run: | + TAG_INFO="Tag: ${IMAGE_VER:-unknown}" + REF_INFO="Ref: ${GITHUB_REF}" + ACTOR_INFO="Actor: ${GITHUB_ACTOR}" + MSG="❌ DockerHub release failed\n${TAG_INFO}\n${REF_INFO}\n${ACTOR_INFO}" + curl -s -H "Content-Type: application/json" \ + -X POST \ + -d "{\"content\":\"${MSG}\"}" \ + "$DISCORD_WEBHOOK" diff --git a/.github/workflows/dockerhub-release-command.yaml b/.github/workflows/dockerhub-release-command.yaml index 33d6b330..571fd6c9 100644 --- a/.github/workflows/dockerhub-release-command.yaml +++ b/.github/workflows/dockerhub-release-command.yaml @@ -14,6 +14,7 @@ on: env: DOCKERHUB_REGISTRY: docker.io IMAGE_NAME: hrms-api-command + DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }} jobs: release-to-dockerhub: @@ -70,3 +71,31 @@ jobs: org.opencontainers.image.description=HRMS Command API Service cache-from: type=gha cache-to: type=gha,mode=max + + - name: Notify Discord on success + if: success() + env: + IMAGE_VER: ${{ steps.gen_ver.outputs.image_ver }} + run: | + TAG_INFO="Tag: ${IMAGE_VER:-unknown}" + REF_INFO="Ref: ${GITHUB_REF}" + ACTOR_INFO="Actor: ${GITHUB_ACTOR}" + MSG="✅ DockerHub release succeeded\n${TAG_INFO}\n${REF_INFO}\n${ACTOR_INFO}" + curl -s -H "Content-Type: application/json" \ + -X POST \ + -d "{\"content\":\"${MSG}\"}" \ + "$DISCORD_WEBHOOK" + + - name: Notify Discord on failure + if: failure() + env: + IMAGE_VER: ${{ steps.gen_ver.outputs.image_ver }} + run: | + TAG_INFO="Tag: ${IMAGE_VER:-unknown}" + REF_INFO="Ref: ${GITHUB_REF}" + ACTOR_INFO="Actor: ${GITHUB_ACTOR}" + MSG="❌ DockerHub release failed\n${TAG_INFO}\n${REF_INFO}\n${ACTOR_INFO}" + curl -s -H "Content-Type: application/json" \ + -X POST \ + -d "{\"content\":\"${MSG}\"}" \ + "$DISCORD_WEBHOOK" diff --git a/.github/workflows/dockerhub-release-discipline.yaml b/.github/workflows/dockerhub-release-discipline.yaml index 96096a54..f4642bd8 100644 --- a/.github/workflows/dockerhub-release-discipline.yaml +++ b/.github/workflows/dockerhub-release-discipline.yaml @@ -14,6 +14,7 @@ on: env: DOCKERHUB_REGISTRY: docker.io IMAGE_NAME: hrms-api-discipline + DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }} jobs: release-to-dockerhub: @@ -70,3 +71,31 @@ jobs: org.opencontainers.image.description=HRMS Discipline API Service cache-from: type=gha cache-to: type=gha,mode=max + + - name: Notify Discord on success + if: success() + env: + IMAGE_VER: ${{ steps.gen_ver.outputs.image_ver }} + run: | + TAG_INFO="Tag: ${IMAGE_VER:-unknown}" + REF_INFO="Ref: ${GITHUB_REF}" + ACTOR_INFO="Actor: ${GITHUB_ACTOR}" + MSG="✅ DockerHub release succeeded\n${TAG_INFO}\n${REF_INFO}\n${ACTOR_INFO}" + curl -s -H "Content-Type: application/json" \ + -X POST \ + -d "{\"content\":\"${MSG}\"}" \ + "$DISCORD_WEBHOOK" + + - name: Notify Discord on failure + if: failure() + env: + IMAGE_VER: ${{ steps.gen_ver.outputs.image_ver }} + run: | + TAG_INFO="Tag: ${IMAGE_VER:-unknown}" + REF_INFO="Ref: ${GITHUB_REF}" + ACTOR_INFO="Actor: ${GITHUB_ACTOR}" + MSG="❌ DockerHub release failed\n${TAG_INFO}\n${REF_INFO}\n${ACTOR_INFO}" + curl -s -H "Content-Type: application/json" \ + -X POST \ + -d "{\"content\":\"${MSG}\"}" \ + "$DISCORD_WEBHOOK" diff --git a/.github/workflows/dockerhub-release-insignia.yaml b/.github/workflows/dockerhub-release-insignia.yaml index 81dae954..79d89963 100644 --- a/.github/workflows/dockerhub-release-insignia.yaml +++ b/.github/workflows/dockerhub-release-insignia.yaml @@ -14,6 +14,7 @@ on: env: DOCKERHUB_REGISTRY: docker.io IMAGE_NAME: hrms-api-insignia + DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }} jobs: release-to-dockerhub: @@ -70,3 +71,31 @@ jobs: org.opencontainers.image.description=HRMS Insignia API Service cache-from: type=gha cache-to: type=gha,mode=max + + - name: Notify Discord on success + if: success() + env: + IMAGE_VER: ${{ steps.gen_ver.outputs.image_ver }} + run: | + TAG_INFO="Tag: ${IMAGE_VER:-unknown}" + REF_INFO="Ref: ${GITHUB_REF}" + ACTOR_INFO="Actor: ${GITHUB_ACTOR}" + MSG="✅ DockerHub release succeeded\n${TAG_INFO}\n${REF_INFO}\n${ACTOR_INFO}" + curl -s -H "Content-Type: application/json" \ + -X POST \ + -d "{\"content\":\"${MSG}\"}" \ + "$DISCORD_WEBHOOK" + + - name: Notify Discord on failure + if: failure() + env: + IMAGE_VER: ${{ steps.gen_ver.outputs.image_ver }} + run: | + TAG_INFO="Tag: ${IMAGE_VER:-unknown}" + REF_INFO="Ref: ${GITHUB_REF}" + ACTOR_INFO="Actor: ${GITHUB_ACTOR}" + MSG="❌ DockerHub release failed\n${TAG_INFO}\n${REF_INFO}\n${ACTOR_INFO}" + curl -s -H "Content-Type: application/json" \ + -X POST \ + -d "{\"content\":\"${MSG}\"}" \ + "$DISCORD_WEBHOOK" diff --git a/.github/workflows/dockerhub-release-leave.yaml b/.github/workflows/dockerhub-release-leave.yaml index b5a3afe2..f9c2e0ba 100644 --- a/.github/workflows/dockerhub-release-leave.yaml +++ b/.github/workflows/dockerhub-release-leave.yaml @@ -17,6 +17,7 @@ on: env: DOCKERHUB_REGISTRY: docker.io IMAGE_NAME: hrms-api-leave + DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }} jobs: release-to-dockerhub: @@ -88,3 +89,31 @@ jobs: - name: Image digest run: echo "Image pushed with digest ${{ steps.build.outputs.digest }}" + + - name: Notify Discord on success + if: success() + env: + IMAGE_VER: ${{ steps.gen_ver.outputs.image_ver }} + run: | + TAG_INFO="Tag: ${IMAGE_VER:-unknown}" + REF_INFO="Ref: ${GITHUB_REF}" + ACTOR_INFO="Actor: ${GITHUB_ACTOR}" + MSG="✅ DockerHub release succeeded\n${TAG_INFO}\n${REF_INFO}\n${ACTOR_INFO}" + curl -s -H "Content-Type: application/json" \ + -X POST \ + -d "{\"content\":\"${MSG}\"}" \ + "$DISCORD_WEBHOOK" + + - name: Notify Discord on failure + if: failure() + env: + IMAGE_VER: ${{ steps.gen_ver.outputs.image_ver }} + run: | + TAG_INFO="Tag: ${IMAGE_VER:-unknown}" + REF_INFO="Ref: ${GITHUB_REF}" + ACTOR_INFO="Actor: ${GITHUB_ACTOR}" + MSG="❌ DockerHub release failed\n${TAG_INFO}\n${REF_INFO}\n${ACTOR_INFO}" + curl -s -H "Content-Type: application/json" \ + -X POST \ + -d "{\"content\":\"${MSG}\"}" \ + "$DISCORD_WEBHOOK" diff --git a/.github/workflows/dockerhub-release-placement.yaml b/.github/workflows/dockerhub-release-placement.yaml index 74fa4471..d4e3dade 100644 --- a/.github/workflows/dockerhub-release-placement.yaml +++ b/.github/workflows/dockerhub-release-placement.yaml @@ -14,6 +14,7 @@ on: env: DOCKERHUB_REGISTRY: docker.io IMAGE_NAME: hrms-api-placement + DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }} jobs: release-to-dockerhub: @@ -70,3 +71,31 @@ jobs: org.opencontainers.image.description=HRMS Placement API Service cache-from: type=gha cache-to: type=gha,mode=max + + - name: Notify Discord on success + if: success() + env: + IMAGE_VER: ${{ steps.gen_ver.outputs.image_ver }} + run: | + TAG_INFO="Tag: ${IMAGE_VER:-unknown}" + REF_INFO="Ref: ${GITHUB_REF}" + ACTOR_INFO="Actor: ${GITHUB_ACTOR}" + MSG="✅ DockerHub release succeeded\n${TAG_INFO}\n${REF_INFO}\n${ACTOR_INFO}" + curl -s -H "Content-Type: application/json" \ + -X POST \ + -d "{\"content\":\"${MSG}\"}" \ + "$DISCORD_WEBHOOK" + + - name: Notify Discord on failure + if: failure() + env: + IMAGE_VER: ${{ steps.gen_ver.outputs.image_ver }} + run: | + TAG_INFO="Tag: ${IMAGE_VER:-unknown}" + REF_INFO="Ref: ${GITHUB_REF}" + ACTOR_INFO="Actor: ${GITHUB_ACTOR}" + MSG="❌ DockerHub release failed\n${TAG_INFO}\n${REF_INFO}\n${ACTOR_INFO}" + curl -s -H "Content-Type: application/json" \ + -X POST \ + -d "{\"content\":\"${MSG}\"}" \ + "$DISCORD_WEBHOOK" diff --git a/.github/workflows/dockerhub-release-retirement.yaml b/.github/workflows/dockerhub-release-retirement.yaml index 38e107e6..50ace5cd 100644 --- a/.github/workflows/dockerhub-release-retirement.yaml +++ b/.github/workflows/dockerhub-release-retirement.yaml @@ -14,6 +14,7 @@ on: env: DOCKERHUB_REGISTRY: docker.io IMAGE_NAME: hrms-api-retirement + DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }} jobs: release-to-dockerhub: @@ -70,3 +71,31 @@ jobs: org.opencontainers.image.description=HRMS Retirement API Service cache-from: type=gha cache-to: type=gha,mode=max + + - name: Notify Discord on success + if: success() + env: + IMAGE_VER: ${{ steps.gen_ver.outputs.image_ver }} + run: | + TAG_INFO="Tag: ${IMAGE_VER:-unknown}" + REF_INFO="Ref: ${GITHUB_REF}" + ACTOR_INFO="Actor: ${GITHUB_ACTOR}" + MSG="✅ DockerHub release succeeded\n${TAG_INFO}\n${REF_INFO}\n${ACTOR_INFO}" + curl -s -H "Content-Type: application/json" \ + -X POST \ + -d "{\"content\":\"${MSG}\"}" \ + "$DISCORD_WEBHOOK" + + - name: Notify Discord on failure + if: failure() + env: + IMAGE_VER: ${{ steps.gen_ver.outputs.image_ver }} + run: | + TAG_INFO="Tag: ${IMAGE_VER:-unknown}" + REF_INFO="Ref: ${GITHUB_REF}" + ACTOR_INFO="Actor: ${GITHUB_ACTOR}" + MSG="❌ DockerHub release failed\n${TAG_INFO}\n${REF_INFO}\n${ACTOR_INFO}" + curl -s -H "Content-Type: application/json" \ + -X POST \ + -d "{\"content\":\"${MSG}\"}" \ + "$DISCORD_WEBHOOK" From 99accd44e365d80478f1cb7ae36d6c79fb5ff0e3 Mon Sep 17 00:00:00 2001 From: Suphonchai Phoonsawat Date: Fri, 9 Jan 2026 18:57:24 +0700 Subject: [PATCH 020/183] update GetLastEffectRound method to accept effectiveDate parameter and adjust usage in LeaveReportController --- .../Leaves/TimeAttendants/UserDutyTimeRepository.cs | 6 +++--- BMA.EHR.Leave/Controllers/LeaveReportController.cs | 3 ++- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/BMA.EHR.Application/Repositories/Leaves/TimeAttendants/UserDutyTimeRepository.cs b/BMA.EHR.Application/Repositories/Leaves/TimeAttendants/UserDutyTimeRepository.cs index 3eefefe0..5ca823d9 100644 --- a/BMA.EHR.Application/Repositories/Leaves/TimeAttendants/UserDutyTimeRepository.cs +++ b/BMA.EHR.Application/Repositories/Leaves/TimeAttendants/UserDutyTimeRepository.cs @@ -101,12 +101,12 @@ namespace BMA.EHR.Application.Repositories.Leaves.TimeAttendants return data; } - public async Task GetLastEffectRound(Guid profileId) + public async Task GetLastEffectRound(Guid profileId, DateTime? effectiveDate = null) { + effectiveDate ??= DateTime.Now; var data = await _dbContext.Set() .Where(x => x.ProfileId == profileId) - .Where(x => x.IsProcess) - .Where(x => x.EffectiveDate.Value.Date <= DateTime.Now.Date) + .Where(x => x.EffectiveDate.Value.Date <= effectiveDate.Value.Date) .OrderByDescending(x => x.EffectiveDate) .FirstOrDefaultAsync(); diff --git a/BMA.EHR.Leave/Controllers/LeaveReportController.cs b/BMA.EHR.Leave/Controllers/LeaveReportController.cs index 9f88401b..059da265 100644 --- a/BMA.EHR.Leave/Controllers/LeaveReportController.cs +++ b/BMA.EHR.Leave/Controllers/LeaveReportController.cs @@ -1768,7 +1768,8 @@ namespace BMA.EHR.Leave.Service.Controllers // return Error("ไม่พบรอบการลงเวลา Default", StatusCodes.Status404NotFound); //} - var effectiveDate = await _userDutyTimeRepository.GetLastEffectRound(p.Id); + // ให้ใช้วันที่จาก loop date แทน + var effectiveDate = await _userDutyTimeRepository.GetLastEffectRound(p.Id, dd.date); var roundId = effectiveDate != null ? effectiveDate.DutyTimeId : Guid.Empty; var userRound = await _dutyTimeRepository.GetByIdAsync(roundId); From 6e531e4d16c999107e95d0390e50ac1d559694db Mon Sep 17 00:00:00 2001 From: kittapath <> Date: Fri, 9 Jan 2026 18:59:11 +0700 Subject: [PATCH 021/183] export report leave --- .../Repositories/UserProfileRepository.cs | 36 +++++++++++++++++++ .../Controllers/LeaveReportController.cs | 10 +++--- 2 files changed, 41 insertions(+), 5 deletions(-) diff --git a/BMA.EHR.Application/Repositories/UserProfileRepository.cs b/BMA.EHR.Application/Repositories/UserProfileRepository.cs index 7ffdc024..d20cfb12 100644 --- a/BMA.EHR.Application/Repositories/UserProfileRepository.cs +++ b/BMA.EHR.Application/Repositories/UserProfileRepository.cs @@ -594,6 +594,42 @@ namespace BMA.EHR.Application.Repositories } } + public async Task> GetProfileByAdminRolev3(string? accessToken, int? node, string? nodeId, string role, string? revisionId, int? reqNode, string? reqNodeId, DateTime? startDate, DateTime? endDate) + { + try + { + var apiPath = $"{_configuration["API"]}/org/dotnet/officer-by-admin-rolev3"; + var apiKey = _configuration["API_KEY"]; + var body = new + { + node = node, + nodeId = nodeId, + role = role, + // revisionId = revisionId, + reqNode = reqNode, + reqNodeId = reqNodeId, + startDate = startDate, + endDate = endDate + }; + + var profiles = new List(); + + var apiResult = await PostExternalAPIAsync(apiPath, accessToken, body, apiKey); + if (apiResult != null) + { + var raw = JsonConvert.DeserializeObject(apiResult); + if (raw != null) + return raw.Result; + } + + return null; + } + catch + { + throw; + } + } + public async Task> GetProfileWithKeycloakAllOfficerRetireFilterAndRevision(string? accessToken, int? node, string? nodeId, bool isAll, bool? isRetirement, string? revisionId) { try diff --git a/BMA.EHR.Leave/Controllers/LeaveReportController.cs b/BMA.EHR.Leave/Controllers/LeaveReportController.cs index 9f88401b..ff7d3257 100644 --- a/BMA.EHR.Leave/Controllers/LeaveReportController.cs +++ b/BMA.EHR.Leave/Controllers/LeaveReportController.cs @@ -962,10 +962,10 @@ namespace BMA.EHR.Leave.Service.Controllers Type3 = "☐", approve = approveResult, approverComment = !string.IsNullOrEmpty(data.LeaveDirectorComment) - ? data.LeaveDirectorComment.Replace("\r", "").Replace("\n", "").Trim() + ? data.LeaveDirectorComment.Replace("\r", "").Replace("\n", "").Trim() : "......................", - approverUpdatedAt = data.LastUpdatedAt.HasValue - ? data.LastUpdatedAt.Value.ToThaiShortDate().ToThaiNumber() + approverUpdatedAt = data.LastUpdatedAt.HasValue + ? data.LastUpdatedAt.Value.ToThaiShortDate().ToThaiNumber() : "...... /...... /......", leaveStatus = data.LeaveCancelStatus != null && data.LeaveCancelStatus!.ToUpper() == "APPROVE" ? "🗹 อนุญาต ☐ ไม่อนุญาต" @@ -1674,7 +1674,7 @@ namespace BMA.EHR.Leave.Service.Controllers if (type.Trim().ToUpper() == "OFFICER") { - profile = await _userProfileRepository.GetProfileByAdminRolev2(AccessToken, profileAdmin?.Node, nodeId, role, req.revisionId, req.node, req.nodeId, req.StartDate.Date, req.EndDate.Date); + profile = await _userProfileRepository.GetProfileByAdminRolev3(AccessToken, profileAdmin?.Node, nodeId, role, req.revisionId, req.node, req.nodeId, req.StartDate.Date, req.EndDate.Date); } else { @@ -2015,7 +2015,7 @@ namespace BMA.EHR.Leave.Service.Controllers if (type.Trim().ToUpper() == "OFFICER") { - profile = await _userProfileRepository.GetProfileByAdminRolev2(AccessToken, profileAdmin?.Node, nodeId, role, req.revisionId, req.node, req.nodeId, req.StartDate.Date, req.EndDate.Date); + profile = await _userProfileRepository.GetProfileByAdminRolev3(AccessToken, profileAdmin?.Node, nodeId, role, req.revisionId, req.node, req.nodeId, req.StartDate.Date, req.EndDate.Date); } else { From 95cd49ecbcc647df39d35a373bac7647723cd4c7 Mon Sep 17 00:00:00 2001 From: Suphonchai Phoonsawat Date: Fri, 9 Jan 2026 19:10:16 +0700 Subject: [PATCH 022/183] update LeaveReportController to pass date parameter to GetLastEffectRound method --- BMA.EHR.Leave/Controllers/LeaveController.cs | 70 ++++++++++++++++--- .../Controllers/LeaveReportController.cs | 2 +- 2 files changed, 60 insertions(+), 12 deletions(-) diff --git a/BMA.EHR.Leave/Controllers/LeaveController.cs b/BMA.EHR.Leave/Controllers/LeaveController.cs index d4edffb2..2ff01180 100644 --- a/BMA.EHR.Leave/Controllers/LeaveController.cs +++ b/BMA.EHR.Leave/Controllers/LeaveController.cs @@ -877,7 +877,28 @@ namespace BMA.EHR.Leave.Service.Controllers else startTime = duty.StartTimeMorning; - var checkInStatus = DateTime.Parse(currentDate.ToString("yyyy-MM-dd HH:mm")) > + string checkInStatus = "NORMAL"; + var leaveReq = await _leaveRequestRepository.GetLeavePeriodAsync(userId, currentDate.Date); + if (leaveReq != null) + { + var leaveRange = leaveReq.LeaveRange == null ? "" : leaveReq.LeaveRange.ToUpper(); + if (leaveRange == "MORNING") + checkInStatus = "NORMAL"; + else + { + checkInStatus = DateTime.Parse(currentDate.ToString("yyyy-MM-dd HH:mm")) > + DateTime.Parse($"{currentDate.ToString("yyyy-MM-dd")} {startTime}") ? + + DateTime.Parse(currentDate.ToString("yyyy-MM-dd HH:mm")) > + DateTime.Parse($"{currentDate.ToString("yyyy-MM-dd")} {duty.EndTimeMorning}") ? + "ABSENT" : + "LATE" : + "NORMAL"; + } + } + else + { + checkInStatus = DateTime.Parse(currentDate.ToString("yyyy-MM-dd HH:mm")) > DateTime.Parse($"{currentDate.ToString("yyyy-MM-dd")} {startTime}") ? DateTime.Parse(currentDate.ToString("yyyy-MM-dd HH:mm")) > @@ -885,6 +906,8 @@ namespace BMA.EHR.Leave.Service.Controllers "ABSENT" : "LATE" : "NORMAL"; + } + // process - รอทำใน queue var checkin_process = new ProcessUserTimeStamp @@ -959,16 +982,41 @@ namespace BMA.EHR.Leave.Service.Controllers { return Error(new Exception(GlobalMessages.DataNotFound), StatusCodes.Status404NotFound); } - // fix issue : SIT ระบบบันทึกเวลาปฏิบัติงาน>>ลงเวลาเข้า-ออกงาน (กรณีลงเวลาออกอีกวัน) #921 - var checkOutStatus = DateTime.Parse(currentDate.ToString("yyyy-MM-dd HH:mm")) < - DateTime.Parse($"{currentDate.ToString("yyyy-MM-dd")} {duty.EndTimeAfternoon}") ? - // "ABSENT" : - checkout.CheckIn.Date < currentDate.Date ? "NORMAL" : - "ABSENT" : - DateTime.Parse(currentDate.ToString("yyyy-MM-dd HH:mm")) < - DateTime.Parse($"{currentDate.ToString("yyyy-MM-dd")} {duty.EndTimeMorning}") ? - "ABSENT" : - "NORMAL"; + + string checkOutStatus = "NORMAL"; + var leaveReq = await _leaveRequestRepository.GetLeavePeriodAsync(userId, currentDate.Date); + if (leaveReq != null) + { + var leaveRange = leaveReq.LeaveRange == null ? "" : leaveReq.LeaveRange.ToUpper(); + if (leaveRange == "AFTERNOON") + checkOutStatus = "NORMAL"; + else + { + // fix issue : SIT ระบบบันทึกเวลาปฏิบัติงาน>>ลงเวลาเข้า-ออกงาน (กรณีลงเวลาออกอีกวัน) #921 + checkOutStatus = DateTime.Parse(currentDate.ToString("yyyy-MM-dd HH:mm")) < + DateTime.Parse($"{currentDate.ToString("yyyy-MM-dd")} {duty.EndTimeAfternoon}") ? + // "ABSENT" : + checkout.CheckIn.Date < currentDate.Date ? "NORMAL" : + "ABSENT" : + DateTime.Parse(currentDate.ToString("yyyy-MM-dd HH:mm")) < + DateTime.Parse($"{currentDate.ToString("yyyy-MM-dd")} {duty.EndTimeMorning}") ? + "ABSENT" : + "NORMAL"; + } + } + else + { + // fix issue : SIT ระบบบันทึกเวลาปฏิบัติงาน>>ลงเวลาเข้า-ออกงาน (กรณีลงเวลาออกอีกวัน) #921 + checkOutStatus = DateTime.Parse(currentDate.ToString("yyyy-MM-dd HH:mm")) < + DateTime.Parse($"{currentDate.ToString("yyyy-MM-dd")} {duty.EndTimeAfternoon}") ? + // "ABSENT" : + checkout.CheckIn.Date < currentDate.Date ? "NORMAL" : + "ABSENT" : + DateTime.Parse(currentDate.ToString("yyyy-MM-dd HH:mm")) < + DateTime.Parse($"{currentDate.ToString("yyyy-MM-dd")} {duty.EndTimeMorning}") ? + "ABSENT" : + "NORMAL"; + } if (checkout_process != null) { diff --git a/BMA.EHR.Leave/Controllers/LeaveReportController.cs b/BMA.EHR.Leave/Controllers/LeaveReportController.cs index 059da265..ecebb4a0 100644 --- a/BMA.EHR.Leave/Controllers/LeaveReportController.cs +++ b/BMA.EHR.Leave/Controllers/LeaveReportController.cs @@ -2099,7 +2099,7 @@ namespace BMA.EHR.Leave.Service.Controllers var fullName = $"{p.Prefix}{p.FirstName} {p.LastName}"; - var effectiveDate = await _userDutyTimeRepository.GetLastEffectRound(p.Id); + var effectiveDate = await _userDutyTimeRepository.GetLastEffectRound(p.Id, dd.date); var roundId = effectiveDate != null ? effectiveDate.DutyTimeId : Guid.Empty; var userRound = await _dutyTimeRepository.GetByIdAsync(roundId); From 3f13557b31a7cf2f01b2a1489f6badfecf6add04 Mon Sep 17 00:00:00 2001 From: Suphonchai Phoonsawat Date: Fri, 9 Jan 2026 19:11:52 +0700 Subject: [PATCH 023/183] update LeaveController to pass currentDate parameter to GetLastEffectRound method --- BMA.EHR.Leave/Controllers/LeaveController.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BMA.EHR.Leave/Controllers/LeaveController.cs b/BMA.EHR.Leave/Controllers/LeaveController.cs index 2ff01180..ebce7bbe 100644 --- a/BMA.EHR.Leave/Controllers/LeaveController.cs +++ b/BMA.EHR.Leave/Controllers/LeaveController.cs @@ -812,7 +812,7 @@ namespace BMA.EHR.Leave.Service.Controllers return Error("ไม่พบรอบการลงเวลาทำงาน Default", StatusCodes.Status404NotFound); } - var effectiveDate = await _userDutyTimeRepository.GetLastEffectRound(profile.Id); + var effectiveDate = await _userDutyTimeRepository.GetLastEffectRound(profile.Id, currentDate); var roundId = effectiveDate != null ? effectiveDate.DutyTimeId : Guid.Empty; var userRound = await _dutyTimeRepository.GetByIdAsync(roundId); From 7b97cd09a347e4380aae017c11e0513b50fe137e Mon Sep 17 00:00:00 2001 From: Suphonchai Phoonsawat Date: Fri, 9 Jan 2026 20:24:50 +0700 Subject: [PATCH 024/183] update LeaveController to handle additional leave range options for check-in and check-out statuses --- BMA.EHR.Leave/Controllers/LeaveController.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/BMA.EHR.Leave/Controllers/LeaveController.cs b/BMA.EHR.Leave/Controllers/LeaveController.cs index ebce7bbe..9b1773ee 100644 --- a/BMA.EHR.Leave/Controllers/LeaveController.cs +++ b/BMA.EHR.Leave/Controllers/LeaveController.cs @@ -882,7 +882,7 @@ namespace BMA.EHR.Leave.Service.Controllers if (leaveReq != null) { var leaveRange = leaveReq.LeaveRange == null ? "" : leaveReq.LeaveRange.ToUpper(); - if (leaveRange == "MORNING") + if (leaveRange == "MORNING" || leaveRange == "ALL") checkInStatus = "NORMAL"; else { @@ -987,8 +987,8 @@ namespace BMA.EHR.Leave.Service.Controllers var leaveReq = await _leaveRequestRepository.GetLeavePeriodAsync(userId, currentDate.Date); if (leaveReq != null) { - var leaveRange = leaveReq.LeaveRange == null ? "" : leaveReq.LeaveRange.ToUpper(); - if (leaveRange == "AFTERNOON") + var leaveRange = leaveReq.LeaveRangeEnd == null ? "" : leaveReq.LeaveRangeEnd.ToUpper(); + if (leaveRange == "AFTERNOON" || leaveRange == "ALL") checkOutStatus = "NORMAL"; else { From 5cdef791f3ad14370d9ece1dd9e7b15a5baf4b2c Mon Sep 17 00:00:00 2001 From: kittapath <> Date: Sun, 11 Jan 2026 15:17:02 +0700 Subject: [PATCH 025/183] report leave --- BMA.EHR.Leave/Controllers/LeaveReportController.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/BMA.EHR.Leave/Controllers/LeaveReportController.cs b/BMA.EHR.Leave/Controllers/LeaveReportController.cs index ff7d3257..52e33ac8 100644 --- a/BMA.EHR.Leave/Controllers/LeaveReportController.cs +++ b/BMA.EHR.Leave/Controllers/LeaveReportController.cs @@ -1674,7 +1674,7 @@ namespace BMA.EHR.Leave.Service.Controllers if (type.Trim().ToUpper() == "OFFICER") { - profile = await _userProfileRepository.GetProfileByAdminRolev3(AccessToken, profileAdmin?.Node, nodeId, role, req.revisionId, req.node, req.nodeId, req.StartDate.Date, req.EndDate.Date); + profile = await _userProfileRepository.GetProfileByAdminRolev2(AccessToken, profileAdmin?.Node, nodeId, role, req.revisionId, req.node, req.nodeId, req.StartDate.Date, req.EndDate.Date); } else { @@ -2015,7 +2015,7 @@ namespace BMA.EHR.Leave.Service.Controllers if (type.Trim().ToUpper() == "OFFICER") { - profile = await _userProfileRepository.GetProfileByAdminRolev3(AccessToken, profileAdmin?.Node, nodeId, role, req.revisionId, req.node, req.nodeId, req.StartDate.Date, req.EndDate.Date); + profile = await _userProfileRepository.GetProfileByAdminRolev2(AccessToken, profileAdmin?.Node, nodeId, role, req.revisionId, req.node, req.nodeId, req.StartDate.Date, req.EndDate.Date); } else { From 49cb60dee77f478284f105158412babbe9ec8911 Mon Sep 17 00:00:00 2001 From: kittapath <> Date: Sun, 11 Jan 2026 16:35:02 +0700 Subject: [PATCH 026/183] report leave api4 --- .../Repositories/UserProfileRepository.cs | 38 +++++++++++++++++++ .../Controllers/LeaveReportController.cs | 4 +- 2 files changed, 40 insertions(+), 2 deletions(-) diff --git a/BMA.EHR.Application/Repositories/UserProfileRepository.cs b/BMA.EHR.Application/Repositories/UserProfileRepository.cs index d20cfb12..3454ae42 100644 --- a/BMA.EHR.Application/Repositories/UserProfileRepository.cs +++ b/BMA.EHR.Application/Repositories/UserProfileRepository.cs @@ -630,6 +630,44 @@ namespace BMA.EHR.Application.Repositories } } + public async Task> GetProfileByAdminRolev4(string? accessToken, int? node, string? nodeId, string role, string? revisionId, int? reqNode, string? reqNodeId, DateTime? startDate, DateTime? endDate) + { + try + { + var apiPath = $"{_configuration["API"]}/org/dotnet/officer-by-admin-rolev4"; + var apiKey = _configuration["API_KEY"]; + var body = new + { + node = node, + nodeId = nodeId, + role = role, + // revisionId = revisionId, + reqNode = reqNode, + reqNodeId = reqNodeId, + // startDate = startDate, + // endDate = endDate + date = endDate + }; + Console.WriteLine(body); + + var profiles = new List(); + + var apiResult = await PostExternalAPIAsync(apiPath, accessToken, body, apiKey); + if (apiResult != null) + { + var raw = JsonConvert.DeserializeObject(apiResult); + if (raw != null) + return raw.Result; + } + + return new List(); + } + catch + { + throw; + } + } + public async Task> GetProfileWithKeycloakAllOfficerRetireFilterAndRevision(string? accessToken, int? node, string? nodeId, bool isAll, bool? isRetirement, string? revisionId) { try diff --git a/BMA.EHR.Leave/Controllers/LeaveReportController.cs b/BMA.EHR.Leave/Controllers/LeaveReportController.cs index 52e33ac8..b1756bd1 100644 --- a/BMA.EHR.Leave/Controllers/LeaveReportController.cs +++ b/BMA.EHR.Leave/Controllers/LeaveReportController.cs @@ -1674,7 +1674,7 @@ namespace BMA.EHR.Leave.Service.Controllers if (type.Trim().ToUpper() == "OFFICER") { - profile = await _userProfileRepository.GetProfileByAdminRolev2(AccessToken, profileAdmin?.Node, nodeId, role, req.revisionId, req.node, req.nodeId, req.StartDate.Date, req.EndDate.Date); + profile = await _userProfileRepository.GetProfileByAdminRolev4(AccessToken, profileAdmin?.Node, nodeId, role, req.revisionId, req.node, req.nodeId, req.StartDate.Date, req.EndDate.Date); } else { @@ -2015,7 +2015,7 @@ namespace BMA.EHR.Leave.Service.Controllers if (type.Trim().ToUpper() == "OFFICER") { - profile = await _userProfileRepository.GetProfileByAdminRolev2(AccessToken, profileAdmin?.Node, nodeId, role, req.revisionId, req.node, req.nodeId, req.StartDate.Date, req.EndDate.Date); + profile = await _userProfileRepository.GetProfileByAdminRolev4(AccessToken, profileAdmin?.Node, nodeId, role, req.revisionId, req.node, req.nodeId, req.StartDate.Date, req.EndDate.Date); } else { From 398679cbcc9f20d5b0187ffdd4bc00d7565ee889 Mon Sep 17 00:00:00 2001 From: waruneeauy Date: Sun, 11 Jan 2026 21:10:24 +0700 Subject: [PATCH 027/183] rollback --- .forgejo/workflows/build-checkin.yml | 83 +++++++++++++++++++++++++ .forgejo/workflows/build-discipline.yml | 83 +++++++++++++++++++++++++ .forgejo/workflows/build-insignia.yml | 83 +++++++++++++++++++++++++ .forgejo/workflows/build-leave.yml | 83 +++++++++++++++++++++++++ .forgejo/workflows/build-placement.yml | 83 +++++++++++++++++++++++++ .forgejo/workflows/build-retirement.yml | 83 +++++++++++++++++++++++++ 6 files changed, 498 insertions(+) create mode 100644 .forgejo/workflows/build-checkin.yml create mode 100644 .forgejo/workflows/build-discipline.yml create mode 100644 .forgejo/workflows/build-insignia.yml create mode 100644 .forgejo/workflows/build-leave.yml create mode 100644 .forgejo/workflows/build-placement.yml create mode 100644 .forgejo/workflows/build-retirement.yml diff --git a/.forgejo/workflows/build-checkin.yml b/.forgejo/workflows/build-checkin.yml new file mode 100644 index 00000000..3971e9df --- /dev/null +++ b/.forgejo/workflows/build-checkin.yml @@ -0,0 +1,83 @@ +name: Build & Deploy Checkin Service + +on: + push: + tags: + - "checkin-dev[0-9]+.[0-9]+.[0-9]+" + - "checkin-dev[0-9]+.[0-9]+.[0-9]+*" + workflow_dispatch: + +env: + REGISTRY: ${{ vars.CONTAINER_REGISTRY }} + REGISTRY_USERNAME: ${{ vars.CONTAINER_REGISTRY_USERNAME }} + REGISTRY_PASSWORD: ${{ secrets.CONTAINER_REGISTRY_PASSWORD }} + CONTAINER_IMAGE_NAME: ${{ vars.CONTAINER_REGISTRY }}/${{ vars.CONTAINER_IMAGE_OWNER }} + IMAGE_VERSION: latest + SERVICE_NAME: hrms-api-checkin + DISCORD_WEBHOOK: ${{ vars.DISCORD_WEBHOOK }} + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Tag Version + shell: bash + run: | + if [[ "${{ github.event_name }}" == "push" ]]; then + VERSION=$(echo "${{ github.ref_name }}" | sed 's/checkin-dev//g') + echo "IMAGE_VERSION=${VERSION}" >> $GITHUB_ENV + else + echo "IMAGE_VERSION=${{ env.IMAGE_VERSION }}-${{ github.run_number }}" >> $GITHUB_ENV + fi + - name: Login in to registry + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ env.REGISTRY_USERNAME }} + password: ${{ env.REGISTRY_PASSWORD }} + + - name: Build and push docker image + uses: docker/build-push-action@v5 + with: + platforms: linux/amd64 + context: . + file: ./BMA.EHR.CheckInConsumer/Dockerfile + tags: ${{ env.CONTAINER_IMAGE_NAME }}/${{ env.SERVICE_NAME }}:latest,${{ env.CONTAINER_IMAGE_NAME }}/${{ env.SERVICE_NAME }}:${{ env.IMAGE_VERSION }} + push: true + + - name: Remote Deploy + uses: appleboy/ssh-action@v1.2.1 + with: + host: ${{ vars.SSH_DEPLOY_HOST }} + port: ${{ vars.SSH_DEPLOY_PORT }} + username: ${{ secrets.SSH_DEPLOY_USER }} + password: ${{ secrets.SSH_DEPLOY_PASSWORD }} + script: | + cd ~/repo + ./replace-env.sh API_CHECKIN "${{ env.IMAGE_VERSION }}" + ./deploy.sh ${{ env.SERVICE_NAME }} + + - name: Discord Notification + if: always() + run: | + STATUS="${{ job.status == 'success' && '✅ Success' || '❌ Failed' }}" + COLOR="${{ job.status == 'success' && '3066993' || '15158332' }}" + TIMESTAMP=$(date -u +%Y-%m-%dT%H:%M:%SZ) + curl -H "Content-Type: application/json" \ + -X POST \ + -d "{ + \"embeds\": [{ + \"title\": \"$STATUS\", + \"description\": \"**Build & Deploy**\\n- Image: \`${{ env.CONTAINER_IMAGE_NAME }}/${{ env.SERVICE_NAME }}\`\\n- Version: \`${{ env.IMAGE_VERSION }}\`\\n- By: \`${{ gitea.actor }}\`\", + \"color\": $COLOR, + \"footer\": { + \"text\": \"Release Notification\", + \"icon_url\": \"https://example.com/success-icon.png\" + }, + \"timestamp\": \"$TIMESTAMP\" + }] + }" \ + ${{ env.DISCORD_WEBHOOK }} diff --git a/.forgejo/workflows/build-discipline.yml b/.forgejo/workflows/build-discipline.yml new file mode 100644 index 00000000..767d775b --- /dev/null +++ b/.forgejo/workflows/build-discipline.yml @@ -0,0 +1,83 @@ +name: Build & Deploy Discipline Service + +on: + push: + tags: + - "discipline-dev[0-9]+.[0-9]+.[0-9]+" + - "discipline-dev[0-9]+.[0-9]+.[0-9]+*" + workflow_dispatch: + +env: + REGISTRY: ${{ vars.CONTAINER_REGISTRY }} + REGISTRY_USERNAME: ${{ vars.CONTAINER_REGISTRY_USERNAME }} + REGISTRY_PASSWORD: ${{ secrets.CONTAINER_REGISTRY_PASSWORD }} + CONTAINER_IMAGE_NAME: ${{ vars.CONTAINER_REGISTRY }}/${{ vars.CONTAINER_IMAGE_OWNER }} + IMAGE_VERSION: latest + SERVICE_NAME: hrms-api-discipline + DISCORD_WEBHOOK: ${{ vars.DISCORD_WEBHOOK }} + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Tag Version + shell: bash + run: | + if [[ "${{ github.event_name }}" == "push" ]]; then + VERSION=$(echo "${{ github.ref_name }}" | sed 's/discipline-dev//g') + echo "IMAGE_VERSION=${VERSION}" >> $GITHUB_ENV + else + echo "IMAGE_VERSION=${{ env.IMAGE_VERSION }}-${{ github.run_number }}" >> $GITHUB_ENV + fi + - name: Login in to registry + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ env.REGISTRY_USERNAME }} + password: ${{ env.REGISTRY_PASSWORD }} + + - name: Build and push docker image + uses: docker/build-push-action@v5 + with: + platforms: linux/amd64 + context: . + file: ./BMA.EHR.Discipline.Service/Dockerfile + tags: ${{ env.CONTAINER_IMAGE_NAME }}/${{ env.SERVICE_NAME }}:latest,${{ env.CONTAINER_IMAGE_NAME }}/${{ env.SERVICE_NAME }}:${{ env.IMAGE_VERSION }} + push: true + + - name: Remote Deploy + uses: appleboy/ssh-action@v1.2.1 + with: + host: ${{ vars.SSH_DEPLOY_HOST }} + port: ${{ vars.SSH_DEPLOY_PORT }} + username: ${{ secrets.SSH_DEPLOY_USER }} + password: ${{ secrets.SSH_DEPLOY_PASSWORD }} + script: | + cd ~/repo + ./replace-env.sh API_DISCIPLINE "${{ env.IMAGE_VERSION }}" + ./deploy.sh ${{ env.SERVICE_NAME }} + + - name: Discord Notification + if: always() + run: | + STATUS="${{ job.status == 'success' && '✅ Success' || '❌ Failed' }}" + COLOR="${{ job.status == 'success' && '3066993' || '15158332' }}" + TIMESTAMP=$(date -u +%Y-%m-%dT%H:%M:%SZ) + curl -H "Content-Type: application/json" \ + -X POST \ + -d "{ + \"embeds\": [{ + \"title\": \"$STATUS\", + \"description\": \"**Build & Deploy**\\n- Image: \`${{ env.CONTAINER_IMAGE_NAME }}/${{ env.SERVICE_NAME }}\`\\n- Version: \`${{ env.IMAGE_VERSION }}\`\\n- By: \`${{ gitea.actor }}\`\", + \"color\": $COLOR, + \"footer\": { + \"text\": \"Release Notification\", + \"icon_url\": \"https://example.com/success-icon.png\" + }, + \"timestamp\": \"$TIMESTAMP\" + }] + }" \ + ${{ env.DISCORD_WEBHOOK }} diff --git a/.forgejo/workflows/build-insignia.yml b/.forgejo/workflows/build-insignia.yml new file mode 100644 index 00000000..7ddcbdbd --- /dev/null +++ b/.forgejo/workflows/build-insignia.yml @@ -0,0 +1,83 @@ +name: Build & Deploy Insignia Service + +on: + push: + tags: + - "insignia-dev[0-9]+.[0-9]+.[0-9]+" + - "insignia-dev[0-9]+.[0-9]+.[0-9]+*" + workflow_dispatch: + +env: + REGISTRY: ${{ vars.CONTAINER_REGISTRY }} + REGISTRY_USERNAME: ${{ vars.CONTAINER_REGISTRY_USERNAME }} + REGISTRY_PASSWORD: ${{ secrets.CONTAINER_REGISTRY_PASSWORD }} + CONTAINER_IMAGE_NAME: ${{ vars.CONTAINER_REGISTRY }}/${{ vars.CONTAINER_IMAGE_OWNER }} + IMAGE_VERSION: latest + SERVICE_NAME: hrms-api-insignia + DISCORD_WEBHOOK: ${{ vars.DISCORD_WEBHOOK }} + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Tag Version + shell: bash + run: | + if [[ "${{ github.event_name }}" == "push" ]]; then + VERSION=$(echo "${{ github.ref_name }}" | sed 's/insignia-dev//g') + echo "IMAGE_VERSION=${VERSION}" >> $GITHUB_ENV + else + echo "IMAGE_VERSION=${{ env.IMAGE_VERSION }}-${{ github.run_number }}" >> $GITHUB_ENV + fi + - name: Login in to registry + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ env.REGISTRY_USERNAME }} + password: ${{ env.REGISTRY_PASSWORD }} + + - name: Build and push docker image + uses: docker/build-push-action@v5 + with: + platforms: linux/amd64 + context: . + file: ./BMA.EHR.Insignia/Dockerfile + tags: ${{ env.CONTAINER_IMAGE_NAME }}/${{ env.SERVICE_NAME }}:latest,${{ env.CONTAINER_IMAGE_NAME }}/${{ env.SERVICE_NAME }}:${{ env.IMAGE_VERSION }} + push: true + + - name: Remote Deploy + uses: appleboy/ssh-action@v1.2.1 + with: + host: ${{ vars.SSH_DEPLOY_HOST }} + port: ${{ vars.SSH_DEPLOY_PORT }} + username: ${{ secrets.SSH_DEPLOY_USER }} + password: ${{ secrets.SSH_DEPLOY_PASSWORD }} + script: | + cd ~/repo + ./replace-env.sh API_INSIGNIA "${{ env.IMAGE_VERSION }}" + ./deploy.sh ${{ env.SERVICE_NAME }} + + - name: Discord Notification + if: always() + run: | + STATUS="${{ job.status == 'success' && '✅ Success' || '❌ Failed' }}" + COLOR="${{ job.status == 'success' && '3066993' || '15158332' }}" + TIMESTAMP=$(date -u +%Y-%m-%dT%H:%M:%SZ) + curl -H "Content-Type: application/json" \ + -X POST \ + -d "{ + \"embeds\": [{ + \"title\": \"$STATUS\", + \"description\": \"**Build & Deploy**\\n- Image: \`${{ env.CONTAINER_IMAGE_NAME }}/${{ env.SERVICE_NAME }}\`\\n- Version: \`${{ env.IMAGE_VERSION }}\`\\n- By: \`${{ gitea.actor }}\`\", + \"color\": $COLOR, + \"footer\": { + \"text\": \"Release Notification\", + \"icon_url\": \"https://example.com/success-icon.png\" + }, + \"timestamp\": \"$TIMESTAMP\" + }] + }" \ + ${{ env.DISCORD_WEBHOOK }} diff --git a/.forgejo/workflows/build-leave.yml b/.forgejo/workflows/build-leave.yml new file mode 100644 index 00000000..b1c9c168 --- /dev/null +++ b/.forgejo/workflows/build-leave.yml @@ -0,0 +1,83 @@ +name: Build & Deploy Leave Service + +on: + push: + tags: + - "leave-dev[0-9]+.[0-9]+.[0-9]+" + - "leave-dev[0-9]+.[0-9]+.[0-9]+*" + workflow_dispatch: + +env: + REGISTRY: ${{ vars.CONTAINER_REGISTRY }} + REGISTRY_USERNAME: ${{ vars.CONTAINER_REGISTRY_USERNAME }} + REGISTRY_PASSWORD: ${{ secrets.CONTAINER_REGISTRY_PASSWORD }} + CONTAINER_IMAGE_NAME: ${{ vars.CONTAINER_REGISTRY }}/${{ vars.CONTAINER_IMAGE_OWNER }} + IMAGE_VERSION: latest + SERVICE_NAME: hrms-api-leave + DISCORD_WEBHOOK: ${{ vars.DISCORD_WEBHOOK }} + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Tag Version + shell: bash + run: | + if [[ "${{ github.event_name }}" == "push" ]]; then + VERSION=$(echo "${{ github.ref_name }}" | sed 's/leave-dev//g') + echo "IMAGE_VERSION=${VERSION}" >> $GITHUB_ENV + else + echo "IMAGE_VERSION=${{ env.IMAGE_VERSION }}-${{ github.run_number }}" >> $GITHUB_ENV + fi + - name: Login in to registry + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ env.REGISTRY_USERNAME }} + password: ${{ env.REGISTRY_PASSWORD }} + + - name: Build and push docker image + uses: docker/build-push-action@v5 + with: + platforms: linux/amd64 + context: . + file: ./BMA.EHR.Leave/Dockerfile + tags: ${{ env.CONTAINER_IMAGE_NAME }}/${{ env.SERVICE_NAME }}:latest,${{ env.CONTAINER_IMAGE_NAME }}/${{ env.SERVICE_NAME }}:${{ env.IMAGE_VERSION }} + push: true + + - name: Remote Deploy + uses: appleboy/ssh-action@v1.2.1 + with: + host: ${{ vars.SSH_DEPLOY_HOST }} + port: ${{ vars.SSH_DEPLOY_PORT }} + username: ${{ secrets.SSH_DEPLOY_USER }} + password: ${{ secrets.SSH_DEPLOY_PASSWORD }} + script: | + cd ~/repo + ./replace-env.sh API_LEAVE "${{ env.IMAGE_VERSION }}" + ./deploy.sh ${{ env.SERVICE_NAME }} + + - name: Discord Notification + if: always() + run: | + STATUS="${{ job.status == 'success' && '✅ Success' || '❌ Failed' }}" + COLOR="${{ job.status == 'success' && '3066993' || '15158332' }}" + TIMESTAMP=$(date -u +%Y-%m-%dT%H:%M:%SZ) + curl -H "Content-Type: application/json" \ + -X POST \ + -d "{ + \"embeds\": [{ + \"title\": \"$STATUS\", + \"description\": \"**Build & Deploy**\\n- Image: \`${{ env.CONTAINER_IMAGE_NAME }}/${{ env.SERVICE_NAME }}\`\\n- Version: \`${{ env.IMAGE_VERSION }}\`\\n- By: \`${{ gitea.actor }}\`\", + \"color\": $COLOR, + \"footer\": { + \"text\": \"Release Notification\", + \"icon_url\": \"https://example.com/success-icon.png\" + }, + \"timestamp\": \"$TIMESTAMP\" + }] + }" \ + ${{ env.DISCORD_WEBHOOK }} diff --git a/.forgejo/workflows/build-placement.yml b/.forgejo/workflows/build-placement.yml new file mode 100644 index 00000000..351b1e59 --- /dev/null +++ b/.forgejo/workflows/build-placement.yml @@ -0,0 +1,83 @@ +name: Build & Deploy Placement Service + +on: + push: + tags: + - "placement-dev[0-9]+.[0-9]+.[0-9]+" + - "placement-dev[0-9]+.[0-9]+.[0-9]+*" + workflow_dispatch: + +env: + REGISTRY: ${{ vars.CONTAINER_REGISTRY }} + REGISTRY_USERNAME: ${{ vars.CONTAINER_REGISTRY_USERNAME }} + REGISTRY_PASSWORD: ${{ secrets.CONTAINER_REGISTRY_PASSWORD }} + CONTAINER_IMAGE_NAME: ${{ vars.CONTAINER_REGISTRY }}/${{ vars.CONTAINER_IMAGE_OWNER }} + IMAGE_VERSION: latest + SERVICE_NAME: hrms-api-placement + DISCORD_WEBHOOK: ${{ vars.DISCORD_WEBHOOK }} + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Tag Version + shell: bash + run: | + if [[ "${{ github.event_name }}" == "push" ]]; then + VERSION=$(echo "${{ github.ref_name }}" | sed 's/placement-dev//g') + echo "IMAGE_VERSION=${VERSION}" >> $GITHUB_ENV + else + echo "IMAGE_VERSION=${{ env.IMAGE_VERSION }}-${{ github.run_number }}" >> $GITHUB_ENV + fi + - name: Login in to registry + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ env.REGISTRY_USERNAME }} + password: ${{ env.REGISTRY_PASSWORD }} + + - name: Build and push docker image + uses: docker/build-push-action@v5 + with: + platforms: linux/amd64 + context: . + file: ./BMA.EHR.Placement.Service/Dockerfile + tags: ${{ env.CONTAINER_IMAGE_NAME }}/${{ env.SERVICE_NAME }}:latest,${{ env.CONTAINER_IMAGE_NAME }}/${{ env.SERVICE_NAME }}:${{ env.IMAGE_VERSION }} + push: true + + - name: Remote Deploy + uses: appleboy/ssh-action@v1.2.1 + with: + host: ${{ vars.SSH_DEPLOY_HOST }} + port: ${{ vars.SSH_DEPLOY_PORT }} + username: ${{ secrets.SSH_DEPLOY_USER }} + password: ${{ secrets.SSH_DEPLOY_PASSWORD }} + script: | + cd ~/repo + ./replace-env.sh API_PLACEMENT "${{ env.IMAGE_VERSION }}" + ./deploy.sh ${{ env.SERVICE_NAME }} + + - name: Discord Notification + if: always() + run: | + STATUS="${{ job.status == 'success' && '✅ Success' || '❌ Failed' }}" + COLOR="${{ job.status == 'success' && '3066993' || '15158332' }}" + TIMESTAMP=$(date -u +%Y-%m-%dT%H:%M:%SZ) + curl -H "Content-Type: application/json" \ + -X POST \ + -d "{ + \"embeds\": [{ + \"title\": \"$STATUS\", + \"description\": \"**Build & Deploy**\\n- Image: \`${{ env.CONTAINER_IMAGE_NAME }}/${{ env.SERVICE_NAME }}\`\\n- Version: \`${{ env.IMAGE_VERSION }}\`\\n- By: \`${{ gitea.actor }}\`\", + \"color\": $COLOR, + \"footer\": { + \"text\": \"Release Notification\", + \"icon_url\": \"https://example.com/success-icon.png\" + }, + \"timestamp\": \"$TIMESTAMP\" + }] + }" \ + ${{ env.DISCORD_WEBHOOK }} diff --git a/.forgejo/workflows/build-retirement.yml b/.forgejo/workflows/build-retirement.yml new file mode 100644 index 00000000..257f68f2 --- /dev/null +++ b/.forgejo/workflows/build-retirement.yml @@ -0,0 +1,83 @@ +name: Build & Deploy Retirement Service + +on: + push: + tags: + - "retirement-dev[0-9]+.[0-9]+.[0-9]+" + - "retirement-dev[0-9]+.[0-9]+.[0-9]+*" + workflow_dispatch: + +env: + REGISTRY: ${{ vars.CONTAINER_REGISTRY }} + REGISTRY_USERNAME: ${{ vars.CONTAINER_REGISTRY_USERNAME }} + REGISTRY_PASSWORD: ${{ secrets.CONTAINER_REGISTRY_PASSWORD }} + CONTAINER_IMAGE_NAME: ${{ vars.CONTAINER_REGISTRY }}/${{ vars.CONTAINER_IMAGE_OWNER }} + IMAGE_VERSION: latest + SERVICE_NAME: hrms-api-retirement + DISCORD_WEBHOOK: ${{ vars.DISCORD_WEBHOOK }} + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Tag Version + shell: bash + run: | + if [[ "${{ github.event_name }}" == "push" ]]; then + VERSION=$(echo "${{ github.ref_name }}" | sed 's/retirement-dev//g') + echo "IMAGE_VERSION=${VERSION}" >> $GITHUB_ENV + else + echo "IMAGE_VERSION=${{ env.IMAGE_VERSION }}-${{ github.run_number }}" >> $GITHUB_ENV + fi + - name: Login in to registry + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ env.REGISTRY_USERNAME }} + password: ${{ env.REGISTRY_PASSWORD }} + + - name: Build and push docker image + uses: docker/build-push-action@v5 + with: + platforms: linux/amd64 + context: . + file: ./BMA.EHR.Retirement.Service/Dockerfile + tags: ${{ env.CONTAINER_IMAGE_NAME }}/${{ env.SERVICE_NAME }}:latest,${{ env.CONTAINER_IMAGE_NAME }}/${{ env.SERVICE_NAME }}:${{ env.IMAGE_VERSION }} + push: true + + - name: Remote Deploy + uses: appleboy/ssh-action@v1.2.1 + with: + host: ${{ vars.SSH_DEPLOY_HOST }} + port: ${{ vars.SSH_DEPLOY_PORT }} + username: ${{ secrets.SSH_DEPLOY_USER }} + password: ${{ secrets.SSH_DEPLOY_PASSWORD }} + script: | + cd ~/repo + ./replace-env.sh API_RETIREMENT "${{ env.IMAGE_VERSION }}" + ./deploy.sh ${{ env.SERVICE_NAME }} + + - name: Discord Notification + if: always() + run: | + STATUS="${{ job.status == 'success' && '✅ Success' || '❌ Failed' }}" + COLOR="${{ job.status == 'success' && '3066993' || '15158332' }}" + TIMESTAMP=$(date -u +%Y-%m-%dT%H:%M:%SZ) + curl -H "Content-Type: application/json" \ + -X POST \ + -d "{ + \"embeds\": [{ + \"title\": \"$STATUS\", + \"description\": \"**Build & Deploy**\\n- Image: \`${{ env.CONTAINER_IMAGE_NAME }}/${{ env.SERVICE_NAME }}\`\\n- Version: \`${{ env.IMAGE_VERSION }}\`\\n- By: \`${{ gitea.actor }}\`\", + \"color\": $COLOR, + \"footer\": { + \"text\": \"Release Notification\", + \"icon_url\": \"https://example.com/success-icon.png\" + }, + \"timestamp\": \"$TIMESTAMP\" + }] + }" \ + ${{ env.DISCORD_WEBHOOK }} From 1c3ce46bcb843d19a77b6b1612d09769ff0c3d45 Mon Sep 17 00:00:00 2001 From: Suphonchai Phoonsawat Date: Mon, 12 Jan 2026 13:41:23 +0700 Subject: [PATCH 028/183] update LeaveController to determine status based on leave request and range #2187 --- BMA.EHR.Leave/Controllers/LeaveController.cs | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/BMA.EHR.Leave/Controllers/LeaveController.cs b/BMA.EHR.Leave/Controllers/LeaveController.cs index 9b1773ee..7dafb605 100644 --- a/BMA.EHR.Leave/Controllers/LeaveController.cs +++ b/BMA.EHR.Leave/Controllers/LeaveController.cs @@ -2437,7 +2437,23 @@ namespace BMA.EHR.Leave.Service.Controllers { if (time < endTime) { - status = "ABSENT"; + + //string checkOutStatus = "NORMAL"; + var leaveReq = await _leaveRequestRepository.GetLeavePeriodAsync(userId, time.Date); + if (leaveReq != null) + { + var leaveRange = leaveReq.LeaveRangeEnd == null ? "" : leaveReq.LeaveRangeEnd.ToUpper(); + if (leaveRange == "AFTERNOON" || leaveRange == "ALL") + status = "NORMAL"; + else + { + status = "ABSENT"; + } + } + else + { + status = "ABSENT"; + } } else { From 90ea98683181036707f48e797a8121f70a427784 Mon Sep 17 00:00:00 2001 From: harid Date: Mon, 12 Jan 2026 17:23:08 +0700 Subject: [PATCH 029/183] =?UTF-8?q?fix=20=E0=B8=A3=E0=B8=B2=E0=B8=A2?= =?UTF-8?q?=E0=B8=87=E0=B8=B2=E0=B8=99=E0=B9=83=E0=B8=9A=E0=B8=A5=E0=B8=B2?= =?UTF-8?q?=20=E0=B8=82=E0=B9=89=E0=B8=AD=E0=B8=A1=E0=B8=B9=E0=B8=A5?= =?UTF-8?q?=E0=B9=81=E0=B8=AA=E0=B8=94=E0=B8=87=E0=B9=83=E0=B8=99=E0=B8=A3?= =?UTF-8?q?=E0=B8=B2=E0=B8=A2=E0=B8=87=E0=B8=B2=E0=B8=99=E0=B9=81=E0=B8=AA?= =?UTF-8?q?=E0=B8=94=E0=B8=87=E0=B9=84=E0=B8=A1=E0=B9=88=E0=B8=84=E0=B8=A3?= =?UTF-8?q?=E0=B8=9A=20#2184?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- BMA.EHR.Leave/Controllers/LeaveReportController.cs | 7 +++---- BMA.EHR.Leave/Controllers/LeaveRequestController.cs | 8 ++++---- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/BMA.EHR.Leave/Controllers/LeaveReportController.cs b/BMA.EHR.Leave/Controllers/LeaveReportController.cs index 55cb2378..e839af77 100644 --- a/BMA.EHR.Leave/Controllers/LeaveReportController.cs +++ b/BMA.EHR.Leave/Controllers/LeaveReportController.cs @@ -2618,10 +2618,9 @@ namespace BMA.EHR.Leave.Service.Controllers updatedAt = x.LastUpdatedAt.HasValue ? x.LastUpdatedAt.Value.Date.ToThaiShortDate().ToThaiNumber() : "...... /...... /......", - comment = (x.Comment ?? "") - .Replace("\r", "") - .Replace("\n", "") - .Trim(), + comment = !string.IsNullOrEmpty(x.Comment) + ? x.Comment.Replace("\r", "").Replace("\n", "").Trim() + : "......................", approveType = (x.ApproveType ?? "").Trim().ToUpper() }) .ToList(); diff --git a/BMA.EHR.Leave/Controllers/LeaveRequestController.cs b/BMA.EHR.Leave/Controllers/LeaveRequestController.cs index 9233b374..cf17535f 100644 --- a/BMA.EHR.Leave/Controllers/LeaveRequestController.cs +++ b/BMA.EHR.Leave/Controllers/LeaveRequestController.cs @@ -2027,10 +2027,10 @@ namespace BMA.EHR.Leave.Service.Controllers { new LeaveRequestApprover { - Prefix = profile.Prefix, - FirstName = profile.FirstName, - LastName = profile.LastName, - PositionName = profile.Position, + Prefix = profile.Prefix ?? "", + FirstName = profile.FirstName ?? "", + LastName = profile.LastName ?? "", + PositionName = $"{profile.Position ?? ""}{profile.PositionLeaveName ?? ""}", ProfileId = profile.Id, KeycloakId = Guid.Parse(UserId!), ApproveType = "SENDER", From 0233d929319584388c2401876f49cde4946b7b60 Mon Sep 17 00:00:00 2001 From: harid Date: Tue, 13 Jan 2026 12:05:52 +0700 Subject: [PATCH 030/183] Change function call Org Service --- BMA.EHR.Application/Repositories/UserProfileRepository.cs | 4 ++-- BMA.EHR.Leave/Controllers/LeaveReportController.cs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/BMA.EHR.Application/Repositories/UserProfileRepository.cs b/BMA.EHR.Application/Repositories/UserProfileRepository.cs index 3454ae42..1c9d5596 100644 --- a/BMA.EHR.Application/Repositories/UserProfileRepository.cs +++ b/BMA.EHR.Application/Repositories/UserProfileRepository.cs @@ -534,8 +534,8 @@ namespace BMA.EHR.Application.Repositories revisionId = revisionId, reqNode = reqNode, reqNodeId = reqNodeId, - startDate = startDate, - endDate = endDate + //startDate = startDate, + //endDate = endDate }; var profiles = new List(); diff --git a/BMA.EHR.Leave/Controllers/LeaveReportController.cs b/BMA.EHR.Leave/Controllers/LeaveReportController.cs index e839af77..50974f31 100644 --- a/BMA.EHR.Leave/Controllers/LeaveReportController.cs +++ b/BMA.EHR.Leave/Controllers/LeaveReportController.cs @@ -1045,7 +1045,7 @@ namespace BMA.EHR.Leave.Service.Controllers if (type.Trim().ToUpper() == "OFFICER") { - profile = await _userProfileRepository.GetProfileByAdminRole(AccessToken, profileAdmin?.Node, nodeId, role, req.revisionId, req.node, req.nodeId, req.StartDate.Date, req.EndDate.Date); + profile = await _userProfileRepository.GetProfileByAdminRolev4(AccessToken, profileAdmin?.Node, nodeId, role, req.revisionId, req.node, req.nodeId, req.StartDate.Date, req.EndDate.Date); } else { From 6907607a06afbd07a425e21a4b497177b57c7a55 Mon Sep 17 00:00:00 2001 From: Suphonchai Phoonsawat Date: Tue, 13 Jan 2026 17:01:20 +0700 Subject: [PATCH 031/183] refactor LeaveController to improve sorting and pagination logic #2192 --- .vscode/launch.json | 68 +++---- BMA.EHR.Leave/Controllers/LeaveController.cs | 191 ++++++++++++------- 2 files changed, 158 insertions(+), 101 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index fd9cf53a..205e817c 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -1,35 +1,35 @@ { - "version": "0.2.0", - "configurations": [ - { - // Use IntelliSense to find out which attributes exist for C# debugging - // Use hover for the description of the existing attributes - // For further information visit https://github.com/dotnet/vscode-csharp/blob/main/debugger-launchjson.md. - "name": ".NET Core Launch (web)", - "type": "coreclr", - "request": "launch", - "preLaunchTask": "build", - // If you have changed target frameworks, make sure to update the program path. - "program": "${workspaceFolder}/BMA.EHR.Leave.Service/bin/Debug/net7.0/BMA.EHR.Leave.Service.dll", - "args": [], - "cwd": "${workspaceFolder}/BMA.EHR.Leave.Service", - "stopAtEntry": false, - // Enable launching a web browser when ASP.NET Core starts. For more information: https://aka.ms/VSCode-CS-LaunchJson-WebBrowser - "serverReadyAction": { - "action": "openExternally", - "pattern": "\\bNow listening on:\\s+(https?://\\S+)" - }, - "env": { - "ASPNETCORE_ENVIRONMENT": "Development" - }, - "sourceFileMap": { - "/Views": "${workspaceFolder}/Views" - } - }, - { - "name": ".NET Core Attach", - "type": "coreclr", - "request": "attach" - } - ] -} \ No newline at end of file + "version": "0.2.0", + "configurations": [ + { + // Use IntelliSense to find out which attributes exist for C# debugging + // Use hover for the description of the existing attributes + // For further information visit https://github.com/dotnet/vscode-csharp/blob/main/debugger-launchjson.md. + "name": ".NET Core Launch (web)", + "type": "coreclr", + "request": "launch", + "preLaunchTask": "build", + // If you have changed target frameworks, make sure to update the program path. + "program": "${workspaceFolder}/BMA.EHR.Leave/bin/Debug/net7.0/BMA.EHR.Leave.dll", + "args": [], + "cwd": "${workspaceFolder}/BMA.EHR.Leave", + "stopAtEntry": false, + // Enable launching a web browser when ASP.NET Core starts. For more information: https://aka.ms/VSCode-CS-LaunchJson-WebBrowser + "serverReadyAction": { + "action": "openExternally", + "pattern": "\\bNow listening on:\\s+(https?://\\S+)" + }, + "env": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "sourceFileMap": { + "/Views": "${workspaceFolder}/Views" + } + }, + { + "name": ".NET Core Attach", + "type": "coreclr", + "request": "attach" + } + ] +} diff --git a/BMA.EHR.Leave/Controllers/LeaveController.cs b/BMA.EHR.Leave/Controllers/LeaveController.cs index 7dafb605..3e0c1d54 100644 --- a/BMA.EHR.Leave/Controllers/LeaveController.cs +++ b/BMA.EHR.Leave/Controllers/LeaveController.cs @@ -2591,6 +2591,8 @@ namespace BMA.EHR.Leave.Service.Controllers //var rawData = await _additionalCheckRequestRepository.GetAdditionalCheckRequests(year, month); var rawData = await _additionalCheckRequestRepository.GetAdditionalCheckRequestsByAdminRole(year, month, role, nodeId, profileAdmin?.Node); + var total = rawData.Count; + var getDefaultRound = await _dutyTimeRepository.GetDefaultAsync(); if (getDefaultRound == null) { @@ -2599,7 +2601,65 @@ namespace BMA.EHR.Leave.Service.Controllers var result = new List(); - foreach (var data in rawData) + + if (!string.IsNullOrWhiteSpace(sortBy)) + { + switch (sortBy.ToUpper()) + { + case "FULLNAME": + if (descending == true) + rawData = rawData.OrderByDescending(x => x.Prefix) + .ThenByDescending(x => x.FirstName) + .ThenByDescending(x => x.LastName) + .ToList(); + else + rawData = rawData.OrderBy(x => x.Prefix) + .ThenBy(x => x.FirstName) + .ThenBy(x => x.LastName) + .ToList(); + break; + case "CREATEDAT": + if (descending == true) + rawData = rawData.OrderByDescending(x => x.CreatedAt).ToList(); + else + rawData = rawData.OrderBy(x => x.CreatedAt).ToList(); + break; + case "CHECKDATE": + if (descending == true) + rawData = rawData.OrderByDescending(x => x.CheckDate).ToList(); + else + rawData = rawData.OrderBy(x => x.CheckDate).ToList(); + break; + // case "STARTTIMEMORNING": + // if (descending == true) + // rawData = rawData.OrderByDescending(x => x.StartTimeMorning).ToList(); + // else + // result = result.OrderBy(x => x.StartTimeMorning).ToList(); + // break; + // case "STARTTIMEAFTERNOON": + // if (descending == true) + // result = result.OrderByDescending(x => x.StartTimeAfternoon).ToList(); + // else + // result = result.OrderBy(x => x.StartTimeAfternoon).ToList(); + // break; + case "DESCRIPTION": + if (descending == true) + rawData = rawData.OrderByDescending(x => x.Description).ToList(); + else + rawData = rawData.OrderBy(x => x.Description).ToList(); + break; + default: + rawData = rawData.OrderBy(x => + x.Status.Trim().ToLower() == "pending" ? 1 : + x.Status.Trim().ToLower() == "approve" ? 2 : 3).ToList(); + break; + } + } + + var rawDataPaged = rawData.Skip((page - 1) * pageSize).Take(pageSize) + .ToList(); + + foreach (var data in rawDataPaged) { var profile = await _userProfileRepository.GetProfileByKeycloakIdAsync(data.KeycloakUserId, AccessToken); if (profile == null) @@ -2678,69 +2738,69 @@ namespace BMA.EHR.Leave.Service.Controllers result.Add(resObj); } - if (keyword != "") - { - result = result.Where(x => x.FullName.Contains(keyword)).ToList(); - } - if (string.IsNullOrWhiteSpace(sortBy)) - { - sortBy = "default"; - } - if (!string.IsNullOrWhiteSpace(sortBy)) - { - switch (sortBy.ToUpper()) - { - case "FULLNAME": - if (descending == true) - result = result.OrderByDescending(x => x.Prefix) - .ThenByDescending(x => x.FirstName) - .ThenByDescending(x => x.LastName) - .ToList(); - else - result = result.OrderBy(x => x.Prefix) - .ThenBy(x => x.FirstName) - .ThenBy(x => x.LastName) - .ToList(); - break; - case "CREATEDAT": - if (descending == true) - result = result.OrderByDescending(x => x.CreatedAt).ToList(); - else - result = result.OrderBy(x => x.CreatedAt).ToList(); - break; - case "CHECKDATE": - if (descending == true) - result = result.OrderByDescending(x => x.CheckDate).ToList(); - else - result = result.OrderBy(x => x.CheckDate).ToList(); - break; - case "STARTTIMEMORNING": - if (descending == true) - result = result.OrderByDescending(x => x.StartTimeMorning).ToList(); - else - result = result.OrderBy(x => x.StartTimeMorning).ToList(); - break; - case "STARTTIMEAFTERNOON": - if (descending == true) - result = result.OrderByDescending(x => x.StartTimeAfternoon).ToList(); - else - result = result.OrderBy(x => x.StartTimeAfternoon).ToList(); - break; - case "DESCRIPTION": - if (descending == true) - result = result.OrderByDescending(x => x.Description).ToList(); - else - result = result.OrderBy(x => x.Description).ToList(); - break; - default: - result = result.OrderBy(x => x.StatusSort).ToList(); - break; - } - } - var pageResult = result.Skip((page - 1) * pageSize).Take(pageSize) - .ToList(); + // if (keyword != "") + // { + // result = result.Where(x => x.FullName.Contains(keyword)).ToList(); + // } + // if (string.IsNullOrWhiteSpace(sortBy)) + // { + // sortBy = "default"; + // } + // if (!string.IsNullOrWhiteSpace(sortBy)) + // { + // switch (sortBy.ToUpper()) + // { + // case "FULLNAME": + // if (descending == true) + // result = result.OrderByDescending(x => x.Prefix) + // .ThenByDescending(x => x.FirstName) + // .ThenByDescending(x => x.LastName) + // .ToList(); + // else + // result = result.OrderBy(x => x.Prefix) + // .ThenBy(x => x.FirstName) + // .ThenBy(x => x.LastName) + // .ToList(); + // break; + // case "CREATEDAT": + // if (descending == true) + // result = result.OrderByDescending(x => x.CreatedAt).ToList(); + // else + // result = result.OrderBy(x => x.CreatedAt).ToList(); + // break; + // case "CHECKDATE": + // if (descending == true) + // result = result.OrderByDescending(x => x.CheckDate).ToList(); + // else + // result = result.OrderBy(x => x.CheckDate).ToList(); + // break; + // case "STARTTIMEMORNING": + // if (descending == true) + // result = result.OrderByDescending(x => x.StartTimeMorning).ToList(); + // else + // result = result.OrderBy(x => x.StartTimeMorning).ToList(); + // break; + // case "STARTTIMEAFTERNOON": + // if (descending == true) + // result = result.OrderByDescending(x => x.StartTimeAfternoon).ToList(); + // else + // result = result.OrderBy(x => x.StartTimeAfternoon).ToList(); + // break; + // case "DESCRIPTION": + // if (descending == true) + // result = result.OrderByDescending(x => x.Description).ToList(); + // else + // result = result.OrderBy(x => x.Description).ToList(); + // break; + // default: + // result = result.OrderBy(x => x.StatusSort).ToList(); + // break; + // } + // } + // var pageResult = result.Skip((page - 1) * pageSize).Take(pageSize) + // .ToList(); - return Success(new { data = pageResult, total = result.Count }); + return Success(new { data = result, total = total }); } /// @@ -3170,10 +3230,7 @@ namespace BMA.EHR.Leave.Service.Controllers result.Add(resObj); } - if (keyword != "") - { - result = result.Where(x => x.EditReason!.Contains(keyword) || x.CheckInLocation!.Contains(keyword) || x.CheckOutLocation!.Contains(keyword)).ToList(); - } + var pageResult = result.Skip((page - 1) * pageSize).Take(pageSize) .ToList(); From 86790cf9f379d07166beffef59e5924750d8181e Mon Sep 17 00:00:00 2001 From: Suphonchai Phoonsawat Date: Wed, 14 Jan 2026 10:16:42 +0700 Subject: [PATCH 032/183] refactor LeaveReportController to enhance employee sorting by remark and check-in/check-out times #2193 --- BMA.EHR.Leave/Controllers/LeaveReportController.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/BMA.EHR.Leave/Controllers/LeaveReportController.cs b/BMA.EHR.Leave/Controllers/LeaveReportController.cs index 50974f31..6a09e2b9 100644 --- a/BMA.EHR.Leave/Controllers/LeaveReportController.cs +++ b/BMA.EHR.Leave/Controllers/LeaveReportController.cs @@ -2250,7 +2250,11 @@ namespace BMA.EHR.Leave.Service.Controllers } } //employees = employees.OrderBy(x => x.checkInDate).ThenBy(x => x.checkInTimeRaw ?? DateTime.MaxValue).ThenBy(x => x.checkOutTimeRaw ?? DateTime.MaxValue).ToList(); - employees = employees.OrderBy(x => x.checkInTimeRaw ?? DateTime.MaxValue).ThenBy(x => x.checkOutTimeRaw ?? DateTime.MaxValue).ToList(); + employees = employees + .OrderBy(x => x.remark.Trim() == "" ? 0 : 1) // ข้อมูลที่ไม่มี remark ให้ขึ้นก่อน + .ThenBy(x => x.checkInTimeRaw ?? DateTime.MaxValue).ThenBy(x => x.checkOutTimeRaw ?? DateTime.MaxValue) + .ThenBy(x => x.remark) // จากนั้นจัดเรียงตาม remark + .ToList(); for (int i = 0; i < employees.Count; i++) { employees[i].no = i + 1; From 5415019b3cd70ded219a4534349aeb739ab26f41 Mon Sep 17 00:00:00 2001 From: Suphonchai Phoonsawat Date: Wed, 14 Jan 2026 10:25:31 +0700 Subject: [PATCH 033/183] refactor LeaveController to update image URL handling for check-in and check-out #2188 --- BMA.EHR.Leave/Controllers/LeaveController.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/BMA.EHR.Leave/Controllers/LeaveController.cs b/BMA.EHR.Leave/Controllers/LeaveController.cs index 3e0c1d54..e730a2a9 100644 --- a/BMA.EHR.Leave/Controllers/LeaveController.cs +++ b/BMA.EHR.Leave/Controllers/LeaveController.cs @@ -3069,7 +3069,8 @@ namespace BMA.EHR.Leave.Service.Controllers CheckInPOI = d.CheckInPOI, CheckInLat = d.CheckInLat, CheckInLon = d.CheckInLon, - CheckInImg = $"{imgUrl}/{d.CheckInImageUrl}", + // CheckInImg = $"{imgUrl}/{d.CheckInImageUrl}", + CheckInImg = await _minIOService.ImagesPathByName(d.CheckInImageUrl), CheckInStatus = DateTime.Parse(d.CheckIn.ToString("yyyy-MM-dd HH:mm")) > DateTime.Parse($"{d.CheckIn.Date.ToString("yyyy-MM-dd")} {duty.StartTimeMorning}") ? @@ -3086,7 +3087,7 @@ namespace BMA.EHR.Leave.Service.Controllers CheckOutPOI = d.CheckOut == null ? "" : d.CheckOutPOI, CheckOutLat = d.CheckOut == null ? null : d.CheckOutLat, CheckOutLon = d.CheckOut == null ? null : d.CheckOutLon, - CheckOutImg = d.CheckOut == null ? "" : $"{imgUrl}/{d.CheckOutImageUrl}", + CheckOutImg = d.CheckOut == null ? "" : await _minIOService.ImagesPathByName(d.CheckOutImageUrl), CheckOutStatus = d.CheckOut == null ? null : DateTime.Parse(d.CheckOut.Value.ToString("yyyy-MM-dd HH:mm")) < From 3a6e4501fd08a6e6168ff401be0d5d2a7dd2a043 Mon Sep 17 00:00:00 2001 From: Suphonchai Phoonsawat Date: Wed, 14 Jan 2026 16:38:55 +0700 Subject: [PATCH 034/183] refactor LeaveController to update start and end time logic for check-in and check-out #2199 --- BMA.EHR.Leave/Controllers/LeaveController.cs | 22 +++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/BMA.EHR.Leave/Controllers/LeaveController.cs b/BMA.EHR.Leave/Controllers/LeaveController.cs index e730a2a9..ab0e61b9 100644 --- a/BMA.EHR.Leave/Controllers/LeaveController.cs +++ b/BMA.EHR.Leave/Controllers/LeaveController.cs @@ -872,7 +872,8 @@ namespace BMA.EHR.Leave.Service.Controllers var startTime = ""; if (!data.IsLocation && data.LocationName == "ไปประชุม / อบรม / สัมมนา") { - startTime = "09:30"; + //startTime = "09:30"; + startTime = "10:30"; } else startTime = duty.StartTimeMorning; @@ -888,7 +889,6 @@ namespace BMA.EHR.Leave.Service.Controllers { checkInStatus = DateTime.Parse(currentDate.ToString("yyyy-MM-dd HH:mm")) > DateTime.Parse($"{currentDate.ToString("yyyy-MM-dd")} {startTime}") ? - DateTime.Parse(currentDate.ToString("yyyy-MM-dd HH:mm")) > DateTime.Parse($"{currentDate.ToString("yyyy-MM-dd")} {duty.EndTimeMorning}") ? "ABSENT" : @@ -900,7 +900,6 @@ namespace BMA.EHR.Leave.Service.Controllers { checkInStatus = DateTime.Parse(currentDate.ToString("yyyy-MM-dd HH:mm")) > DateTime.Parse($"{currentDate.ToString("yyyy-MM-dd")} {startTime}") ? - DateTime.Parse(currentDate.ToString("yyyy-MM-dd HH:mm")) > DateTime.Parse($"{currentDate.ToString("yyyy-MM-dd")} {duty.EndTimeMorning}") ? "ABSENT" : @@ -956,8 +955,6 @@ namespace BMA.EHR.Leave.Service.Controllers } else { - - var checkout = await _userTimeStampRepository.GetByIdAsync(data.CheckInId.Value); var currentCheckInProcess = await _processUserTimeStampRepository.GetTimestampByDateAsync(userId, checkout.CheckIn.Date); @@ -983,6 +980,15 @@ namespace BMA.EHR.Leave.Service.Controllers return Error(new Exception(GlobalMessages.DataNotFound), StatusCodes.Status404NotFound); } + var endTime = ""; + if (!data.IsLocation && data.LocationName == "ไปประชุม / อบรม / สัมมนา") + { + //startTime = "09:30"; + endTime = "14:30"; + } + else + endTime = duty.EndTimeAfternoon; + string checkOutStatus = "NORMAL"; var leaveReq = await _leaveRequestRepository.GetLeavePeriodAsync(userId, currentDate.Date); if (leaveReq != null) @@ -997,6 +1003,9 @@ namespace BMA.EHR.Leave.Service.Controllers DateTime.Parse($"{currentDate.ToString("yyyy-MM-dd")} {duty.EndTimeAfternoon}") ? // "ABSENT" : checkout.CheckIn.Date < currentDate.Date ? "NORMAL" : + DateTime.Parse(currentDate.ToString("yyyy-MM-dd HH:mm")) >= + DateTime.Parse($"{currentDate.ToString("yyyy-MM-dd")} {endTime}") ? + "NORMAL" : "ABSENT" : DateTime.Parse(currentDate.ToString("yyyy-MM-dd HH:mm")) < DateTime.Parse($"{currentDate.ToString("yyyy-MM-dd")} {duty.EndTimeMorning}") ? @@ -1011,6 +1020,9 @@ namespace BMA.EHR.Leave.Service.Controllers DateTime.Parse($"{currentDate.ToString("yyyy-MM-dd")} {duty.EndTimeAfternoon}") ? // "ABSENT" : checkout.CheckIn.Date < currentDate.Date ? "NORMAL" : + DateTime.Parse(currentDate.ToString("yyyy-MM-dd HH:mm")) >= + DateTime.Parse($"{currentDate.ToString("yyyy-MM-dd")} {endTime}") ? + "NORMAL" : "ABSENT" : DateTime.Parse(currentDate.ToString("yyyy-MM-dd HH:mm")) < DateTime.Parse($"{currentDate.ToString("yyyy-MM-dd")} {duty.EndTimeMorning}") ? From 3e8c3d998e048c7dd21a0ceca1f21b0a27614d22 Mon Sep 17 00:00:00 2001 From: Suphonchai Phoonsawat Date: Wed, 14 Jan 2026 16:42:36 +0700 Subject: [PATCH 035/183] refactor LeaveController to modify checkout-check endpoint for seminar handling #2199 --- BMA.EHR.Leave/Controllers/LeaveController.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/BMA.EHR.Leave/Controllers/LeaveController.cs b/BMA.EHR.Leave/Controllers/LeaveController.cs index ab0e61b9..6bc682a2 100644 --- a/BMA.EHR.Leave/Controllers/LeaveController.cs +++ b/BMA.EHR.Leave/Controllers/LeaveController.cs @@ -2397,11 +2397,11 @@ namespace BMA.EHR.Leave.Service.Controllers /// เมื่อทำรายการสำเร็จ /// ไม่ได้ Login เข้าระบบ /// เมื่อเกิดข้อผิดพลาดในการทำงาน - [HttpGet("user/checkout-check")] + [HttpGet("user/checkout-check/{isSeminar:bool}")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status401Unauthorized)] [ProducesResponseType(StatusCodes.Status500InternalServerError)] - public async Task> CheckoutCheckAsync() + public async Task> CheckoutCheckAsync(bool isSeminar = false) { var time = DateTime.Now; @@ -2435,7 +2435,9 @@ namespace BMA.EHR.Leave.Service.Controllers //var endTime = DateTimeOffset.Parse($"{DateTime.Now.Date.ToString("yyyy-MM-dd")}T{duty.EndTimeAfternoon}:00.0000000+07:00").ToLocalTime().DateTime;  //var endTime = DateTime.Parse($"{DateTime.Now.Date.ToString("yyyy-MM-dd")}T{duty.EndTimeAfternoon}:00.0000000+07:00"); - var endTime = DateTime.Parse($"{DateTime.Now.Date.ToString("yyyy-MM-dd")} {duty.EndTimeAfternoon}"); + var endTime = isSeminar + ? DateTime.Parse($"{DateTime.Now.Date.ToString("yyyy-MM-dd")} {14:30}") + : DateTime.Parse($"{DateTime.Now.Date.ToString("yyyy-MM-dd")} {duty.EndTimeAfternoon}"); var status = string.Empty; if(lastCheckIn == null) { From 9e529ed19b2239d1cd3c587c91ca27a26339d82a Mon Sep 17 00:00:00 2001 From: Suphonchai Phoonsawat Date: Wed, 14 Jan 2026 16:43:35 +0700 Subject: [PATCH 036/183] refactor LeaveController to update checkout-check endpoint to accept string for seminar handling #2199 --- BMA.EHR.Leave/Controllers/LeaveController.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/BMA.EHR.Leave/Controllers/LeaveController.cs b/BMA.EHR.Leave/Controllers/LeaveController.cs index 6bc682a2..580252e9 100644 --- a/BMA.EHR.Leave/Controllers/LeaveController.cs +++ b/BMA.EHR.Leave/Controllers/LeaveController.cs @@ -2397,11 +2397,11 @@ namespace BMA.EHR.Leave.Service.Controllers /// เมื่อทำรายการสำเร็จ /// ไม่ได้ Login เข้าระบบ /// เมื่อเกิดข้อผิดพลาดในการทำงาน - [HttpGet("user/checkout-check/{isSeminar:bool}")] + [HttpGet("user/checkout-check/{isSeminar}")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status401Unauthorized)] [ProducesResponseType(StatusCodes.Status500InternalServerError)] - public async Task> CheckoutCheckAsync(bool isSeminar = false) + public async Task> CheckoutCheckAsync(string isSeminar = "N") { var time = DateTime.Now; @@ -2435,7 +2435,7 @@ namespace BMA.EHR.Leave.Service.Controllers //var endTime = DateTimeOffset.Parse($"{DateTime.Now.Date.ToString("yyyy-MM-dd")}T{duty.EndTimeAfternoon}:00.0000000+07:00").ToLocalTime().DateTime;  //var endTime = DateTime.Parse($"{DateTime.Now.Date.ToString("yyyy-MM-dd")}T{duty.EndTimeAfternoon}:00.0000000+07:00"); - var endTime = isSeminar + var endTime = isSeminar.Trim().ToUpper() == "Y" ? DateTime.Parse($"{DateTime.Now.Date.ToString("yyyy-MM-dd")} {14:30}") : DateTime.Parse($"{DateTime.Now.Date.ToString("yyyy-MM-dd")} {duty.EndTimeAfternoon}"); var status = string.Empty; From 5ec7933b3c7dfb90a9ac3adb2a8790c3915b81b4 Mon Sep 17 00:00:00 2001 From: Suphonchai Phoonsawat Date: Wed, 14 Jan 2026 16:51:21 +0700 Subject: [PATCH 037/183] refactor LeaveController to correct end time parsing for seminar handling #2199 --- BMA.EHR.Leave/Controllers/LeaveController.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BMA.EHR.Leave/Controllers/LeaveController.cs b/BMA.EHR.Leave/Controllers/LeaveController.cs index 580252e9..26e3372d 100644 --- a/BMA.EHR.Leave/Controllers/LeaveController.cs +++ b/BMA.EHR.Leave/Controllers/LeaveController.cs @@ -2436,7 +2436,7 @@ namespace BMA.EHR.Leave.Service.Controllers  //var endTime = DateTime.Parse($"{DateTime.Now.Date.ToString("yyyy-MM-dd")}T{duty.EndTimeAfternoon}:00.0000000+07:00"); var endTime = isSeminar.Trim().ToUpper() == "Y" - ? DateTime.Parse($"{DateTime.Now.Date.ToString("yyyy-MM-dd")} {14:30}") + ? DateTime.Parse($"{DateTime.Now.Date.ToString("yyyy-MM-dd")} 14:30") : DateTime.Parse($"{DateTime.Now.Date.ToString("yyyy-MM-dd")} {duty.EndTimeAfternoon}"); var status = string.Empty; if(lastCheckIn == null) From 64c1021c529958393c9a840c135c0c22c2b58522 Mon Sep 17 00:00:00 2001 From: harid Date: Fri, 16 Jan 2026 09:25:22 +0700 Subject: [PATCH 038/183] =?UTF-8?q?Migrate=20=E0=B9=80=E0=B8=81=E0=B9=87?= =?UTF-8?q?=E0=B8=9A=E0=B8=9F=E0=B8=B4=E0=B8=A5=E0=B8=94=E0=B9=8C=E0=B9=83?= =?UTF-8?q?=E0=B8=8A=E0=B9=89=E0=B9=81=E0=B8=AA=E0=B8=94=E0=B8=87=E0=B9=83?= =?UTF-8?q?=E0=B8=99=E0=B8=A3=E0=B8=B2=E0=B8=A2=E0=B8=87=E0=B8=B2=E0=B8=99?= =?UTF-8?q?=E0=B8=A5=E0=B8=B2=E0=B9=80=E0=B8=9E=E0=B8=B4=E0=B9=88=E0=B8=A1?= =?UTF-8?q?=20#2195?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Profiles/GetProfileByKeycloakIdDto.cs | 2 + .../Leave/Requests/LeaveRequestApprover.cs | 9 + ...fields_table_eaveequestpprover.Designer.cs | 1612 +++++++++++++++++ ...0500_add_fields_table_eaveequestpprover.cs | 54 + .../LeaveDb/LeaveDbContextModelSnapshot.cs | 15 + .../Controllers/LeaveRequestController.cs | 6 + .../LeaveRequest/LeaveRequestApproverDto.cs | 10 + 7 files changed, 1708 insertions(+) create mode 100644 BMA.EHR.Infrastructure/Migrations/LeaveDb/20260115140500_add_fields_table_eaveequestpprover.Designer.cs create mode 100644 BMA.EHR.Infrastructure/Migrations/LeaveDb/20260115140500_add_fields_table_eaveequestpprover.cs diff --git a/BMA.EHR.Application/Responses/Profiles/GetProfileByKeycloakIdDto.cs b/BMA.EHR.Application/Responses/Profiles/GetProfileByKeycloakIdDto.cs index bb377f3e..b2e71129 100644 --- a/BMA.EHR.Application/Responses/Profiles/GetProfileByKeycloakIdDto.cs +++ b/BMA.EHR.Application/Responses/Profiles/GetProfileByKeycloakIdDto.cs @@ -80,6 +80,8 @@ namespace BMA.EHR.Application.Responses.Profiles public string? CurrentZipCode { get; set; } public string? PositionLeaveName { get; set; } + + public string? PosExecutiveName { get; set; } public string? CommanderPositionName { get; set; } = string.Empty; diff --git a/BMA.EHR.Domain/Models/Leave/Requests/LeaveRequestApprover.cs b/BMA.EHR.Domain/Models/Leave/Requests/LeaveRequestApprover.cs index 10e7175d..f974d004 100644 --- a/BMA.EHR.Domain/Models/Leave/Requests/LeaveRequestApprover.cs +++ b/BMA.EHR.Domain/Models/Leave/Requests/LeaveRequestApprover.cs @@ -17,6 +17,15 @@ namespace BMA.EHR.Domain.Models.Leave.Requests public string PositionName { get; set; } = string.Empty; + [Comment("ประเภทระดับตำแหน่ง")] + public string PositionLevelName { get; set; } = string.Empty; + + [Comment("ตำแหน่งทางการบริหาร")] + public string PosExecutiveName { get; set; } = string.Empty; + + [Comment("สังกัด")] + public string OrganizationName { get; set; } = string.Empty; + [Comment("ตำแหน่งใต้ลายเช็นต์")] public string? PositionSign { get; set; } = string.Empty; diff --git a/BMA.EHR.Infrastructure/Migrations/LeaveDb/20260115140500_add_fields_table_eaveequestpprover.Designer.cs b/BMA.EHR.Infrastructure/Migrations/LeaveDb/20260115140500_add_fields_table_eaveequestpprover.Designer.cs new file mode 100644 index 00000000..8a505bde --- /dev/null +++ b/BMA.EHR.Infrastructure/Migrations/LeaveDb/20260115140500_add_fields_table_eaveequestpprover.Designer.cs @@ -0,0 +1,1612 @@ +// +using System; +using BMA.EHR.Infrastructure.Persistence; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace BMA.EHR.Infrastructure.Migrations.LeaveDb +{ + [DbContext(typeof(LeaveDbContext))] + [Migration("20260115140500_add_fields_table_eaveequestpprover")] + partial class add_fields_table_eaveequestpprover + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "7.0.9") + .HasAnnotation("Relational:MaxIdentifierLength", 64); + + modelBuilder.Entity("BMA.EHR.Domain.Models.Documents.Document", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CreatedDate") + .HasColumnType("datetime(6)"); + + b.Property("Detail") + .IsRequired() + .HasColumnType("text"); + + b.Property("FileName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("varchar(255)"); + + b.Property("FileSize") + .HasColumnType("int"); + + b.Property("FileType") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("varchar(128)"); + + b.Property("ObjectRefId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.ToTable("Document"); + }); + + modelBuilder.Entity("BMA.EHR.Domain.Models.Leave.Commons.LeaveType", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)") + .HasColumnOrder(0) + .HasComment("PrimaryKey") + .HasAnnotation("Relational:JsonPropertyName", "id"); + + b.Property("Code") + .IsRequired() + .HasColumnType("longtext") + .HasComment("รหัสประเภทการลา"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)") + .HasColumnOrder(100) + .HasComment("สร้างข้อมูลเมื่อ"); + + b.Property("CreatedFullName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)") + .HasColumnOrder(104) + .HasComment("ชื่อ User ที่สร้างข้อมูล"); + + b.Property("CreatedUserId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("varchar(40)") + .HasColumnOrder(101) + .HasComment("User Id ที่สร้างข้อมูล"); + + b.Property("LastUpdateFullName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)") + .HasColumnOrder(105) + .HasComment("ชื่อ User ที่แก้ไขข้อมูลล่าสุด"); + + b.Property("LastUpdateUserId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("varchar(40)") + .HasColumnOrder(103) + .HasComment("User Id ที่แก้ไขข้อมูลล่าสุด"); + + b.Property("LastUpdatedAt") + .HasColumnType("datetime(6)") + .HasColumnOrder(102) + .HasComment("แก้ไขข้อมูลล่าสุดเมื่อ"); + + b.Property("Limit") + .HasColumnType("int") + .HasComment("จำนวนวันลาสูงสุดประจำปี"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext") + .HasComment("ชื่อประเภทการลา"); + + b.HasKey("Id"); + + b.ToTable("LeaveTypes"); + }); + + modelBuilder.Entity("BMA.EHR.Domain.Models.Leave.Requests.LeaveBeginning", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)") + .HasColumnOrder(0) + .HasComment("PrimaryKey") + .HasAnnotation("Relational:JsonPropertyName", "id"); + + b.Property("Child1DnaId") + .HasColumnType("char(36)"); + + b.Property("Child2DnaId") + .HasColumnType("char(36)"); + + b.Property("Child3DnaId") + .HasColumnType("char(36)"); + + b.Property("Child4DnaId") + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)") + .HasColumnOrder(100) + .HasComment("สร้างข้อมูลเมื่อ"); + + b.Property("CreatedFullName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)") + .HasColumnOrder(104) + .HasComment("ชื่อ User ที่สร้างข้อมูล"); + + b.Property("CreatedUserId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("varchar(40)") + .HasColumnOrder(101) + .HasComment("User Id ที่สร้างข้อมูล"); + + b.Property("FirstName") + .HasColumnType("longtext"); + + b.Property("LastName") + .HasColumnType("longtext"); + + b.Property("LastUpdateFullName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)") + .HasColumnOrder(105) + .HasComment("ชื่อ User ที่แก้ไขข้อมูลล่าสุด"); + + b.Property("LastUpdateUserId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("varchar(40)") + .HasColumnOrder(103) + .HasComment("User Id ที่แก้ไขข้อมูลล่าสุด"); + + b.Property("LastUpdatedAt") + .HasColumnType("datetime(6)") + .HasColumnOrder(102) + .HasComment("แก้ไขข้อมูลล่าสุดเมื่อ"); + + b.Property("LeaveDays") + .HasColumnType("double") + .HasComment("จำนวนวันลายกมา"); + + b.Property("LeaveDaysUsed") + .HasColumnType("double") + .HasComment("จำนวนวันลาที่ใช้ไป"); + + b.Property("LeaveTypeId") + .HasColumnType("char(36)") + .HasComment("รหัสประเภทการลา"); + + b.Property("LeaveYear") + .HasColumnType("int") + .HasComment("ปีงบประมาณ"); + + b.Property("Prefix") + .HasColumnType("longtext"); + + b.Property("ProfileId") + .HasColumnType("char(36)") + .HasComment("รหัส Profile ในระบบทะเบียนประวัติ"); + + b.Property("RootDnaId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("LeaveTypeId"); + + b.ToTable("LeaveBeginnings"); + }); + + modelBuilder.Entity("BMA.EHR.Domain.Models.Leave.Requests.LeaveDocument", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)") + .HasColumnOrder(0) + .HasComment("PrimaryKey") + .HasAnnotation("Relational:JsonPropertyName", "id"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)") + .HasColumnOrder(100) + .HasComment("สร้างข้อมูลเมื่อ"); + + b.Property("CreatedFullName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)") + .HasColumnOrder(104) + .HasComment("ชื่อ User ที่สร้างข้อมูล"); + + b.Property("CreatedUserId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("varchar(40)") + .HasColumnOrder(101) + .HasComment("User Id ที่สร้างข้อมูล"); + + b.Property("DocumentId") + .HasColumnType("char(36)"); + + b.Property("LastUpdateFullName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)") + .HasColumnOrder(105) + .HasComment("ชื่อ User ที่แก้ไขข้อมูลล่าสุด"); + + b.Property("LastUpdateUserId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("varchar(40)") + .HasColumnOrder(103) + .HasComment("User Id ที่แก้ไขข้อมูลล่าสุด"); + + b.Property("LastUpdatedAt") + .HasColumnType("datetime(6)") + .HasColumnOrder(102) + .HasComment("แก้ไขข้อมูลล่าสุดเมื่อ"); + + b.Property("LeaveRequestId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("DocumentId"); + + b.HasIndex("LeaveRequestId"); + + b.ToTable("LeaveDocuments"); + }); + + modelBuilder.Entity("BMA.EHR.Domain.Models.Leave.Requests.LeaveRequest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)") + .HasColumnOrder(0) + .HasComment("PrimaryKey") + .HasAnnotation("Relational:JsonPropertyName", "id"); + + b.Property("AbsentDayAt") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("AbsentDayGetIn") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("AbsentDayLocation") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("AbsentDayRegistorDate") + .HasColumnType("datetime(6)"); + + b.Property("AbsentDaySummon") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Amount") + .HasColumnType("double"); + + b.Property("ApproveStep") + .HasColumnType("longtext") + .HasComment("step การอนุมัติ st1 = จทน.อนุมัตื,st2 = ผู้บังคับบัญชา อนุมัติ "); + + b.Property("BirthDate") + .HasColumnType("datetime(6)"); + + b.Property("CancelLeaveWrote") + .HasColumnType("longtext") + .HasComment("เขียนที่ (ขอยกเลิก)"); + + b.Property("Child1") + .HasColumnType("longtext"); + + b.Property("Child1DnaId") + .HasColumnType("char(36)"); + + b.Property("Child1Id") + .HasColumnType("char(36)"); + + b.Property("Child2") + .HasColumnType("longtext"); + + b.Property("Child2DnaId") + .HasColumnType("char(36)"); + + b.Property("Child2Id") + .HasColumnType("char(36)"); + + b.Property("Child3") + .HasColumnType("longtext"); + + b.Property("Child3DnaId") + .HasColumnType("char(36)"); + + b.Property("Child3Id") + .HasColumnType("char(36)"); + + b.Property("Child4") + .HasColumnType("longtext"); + + b.Property("Child4DnaId") + .HasColumnType("char(36)"); + + b.Property("Child4Id") + .HasColumnType("char(36)"); + + b.Property("CitizenId") + .HasColumnType("longtext"); + + b.Property("CommanderPosition") + .HasColumnType("longtext"); + + b.Property("CoupleDayCountryHistory") + .HasColumnType("longtext"); + + b.Property("CoupleDayEndDateHistory") + .HasColumnType("datetime(6)"); + + b.Property("CoupleDayLevel") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("CoupleDayLevelCountry") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("CoupleDayName") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("CoupleDayPosition") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("CoupleDayStartDateHistory") + .HasColumnType("datetime(6)"); + + b.Property("CoupleDaySumTotalHistory") + .HasColumnType("longtext"); + + b.Property("CoupleDayTotalHistory") + .HasColumnType("longtext"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)") + .HasColumnOrder(100) + .HasComment("สร้างข้อมูลเมื่อ"); + + b.Property("CreatedFullName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)") + .HasColumnOrder(104) + .HasComment("ชื่อ User ที่สร้างข้อมูล"); + + b.Property("CreatedUserId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("varchar(40)") + .HasColumnOrder(101) + .HasComment("User Id ที่สร้างข้อมูล"); + + b.Property("DateAppoint") + .HasColumnType("datetime(6)"); + + b.Property("Dear") + .HasColumnType("longtext") + .HasComment("เรียนใคร"); + + b.Property("FirstName") + .HasColumnType("longtext"); + + b.Property("Gender") + .HasColumnType("longtext"); + + b.Property("HajjDayStatus") + .HasColumnType("tinyint(1)"); + + b.Property("KeycloakUserId") + .HasColumnType("char(36)"); + + b.Property("LastName") + .HasColumnType("longtext"); + + b.Property("LastUpdateFullName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)") + .HasColumnOrder(105) + .HasComment("ชื่อ User ที่แก้ไขข้อมูลล่าสุด"); + + b.Property("LastUpdateUserId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("varchar(40)") + .HasColumnOrder(103) + .HasComment("User Id ที่แก้ไขข้อมูลล่าสุด"); + + b.Property("LastUpdatedAt") + .HasColumnType("datetime(6)") + .HasColumnOrder(102) + .HasComment("แก้ไขข้อมูลล่าสุดเมื่อ"); + + b.Property("LeaveAddress") + .IsRequired() + .HasColumnType("longtext") + .HasComment("สถานที่ติดต่อขณะลา"); + + b.Property("LeaveBirthDate") + .HasColumnType("datetime(6)"); + + b.Property("LeaveCancelComment") + .HasColumnType("longtext") + .HasComment("เหตุผลในการขอยกเลิก"); + + b.Property("LeaveCancelDocumentId") + .HasColumnType("char(36)"); + + b.Property("LeaveCancelStatus") + .HasColumnType("longtext") + .HasComment("สถานะของคำขอยกเลิก"); + + b.Property("LeaveComment") + .HasColumnType("longtext") + .HasComment("ความเห็นของผู้บังคับบัญชา"); + + b.Property("LeaveDetail") + .IsRequired() + .HasColumnType("longtext") + .HasComment("รายละเอียดการลา"); + + b.Property("LeaveDirectorComment") + .HasColumnType("longtext") + .HasComment("ความเห็นของผู้อำนวยการสำนัก"); + + b.Property("LeaveDraftDocumentId") + .HasColumnType("char(36)"); + + b.Property("LeaveEndDate") + .HasColumnType("datetime(6)") + .HasComment("วัน เดือน ปีสิ้นสุดลา"); + + b.Property("LeaveGovernmentDate") + .HasColumnType("datetime(6)"); + + b.Property("LeaveLast") + .HasColumnType("datetime(6)"); + + b.Property("LeaveNumber") + .IsRequired() + .HasColumnType("longtext") + .HasComment("หมายเลขที่ติดต่อขณะลา"); + + b.Property("LeaveRange") + .HasColumnType("longtext") + .HasComment("ช่วงของการลาของวันเริ่ม เช่น ลาทั้งวัน ครึ่งวันเช้า ครึ่งวันบ่าย"); + + b.Property("LeaveRangeEnd") + .HasColumnType("longtext") + .HasComment("ช่วงของการลาของวันสิ้นสุด เช่น ลาทั้งวัน ครึ่งวันเช้า ครึ่งวันบ่าย"); + + b.Property("LeaveSalary") + .HasColumnType("int"); + + b.Property("LeaveSalaryText") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("LeaveStartDate") + .HasColumnType("datetime(6)") + .HasComment("วัน เดือน ปีเริ่มต้นลา"); + + b.Property("LeaveStatus") + .IsRequired() + .HasColumnType("longtext") + .HasComment("สถานะของคำร้อง"); + + b.Property("LeaveSubTypeName") + .HasColumnType("longtext"); + + b.Property("LeaveTotal") + .HasColumnType("double"); + + b.Property("LeaveTypeCode") + .HasColumnType("longtext") + .HasComment("code ของประเภทการลา"); + + b.Property("LeaveWrote") + .IsRequired() + .HasColumnType("longtext") + .HasComment("เขียนที่"); + + b.Property("OrdainDayBuddhistLentAddress") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("OrdainDayBuddhistLentName") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("OrdainDayLocationAddress") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("OrdainDayLocationName") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("OrdainDayLocationNumber") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("OrdainDayOrdination") + .HasColumnType("datetime(6)"); + + b.Property("OrdainDayStatus") + .HasColumnType("tinyint(1)"); + + b.Property("OrganizationName") + .HasColumnType("longtext") + .HasComment("สังกัดผู้ยื่นขอ"); + + b.Property("PositionLevelName") + .HasColumnType("longtext") + .HasComment("ระดับผู้ยื่นขอ"); + + b.Property("PositionName") + .HasColumnType("longtext") + .HasComment("ตำแหน่งผู้ยื่นขอ"); + + b.Property("Prefix") + .HasColumnType("longtext"); + + b.Property("ProfileId") + .HasColumnType("char(36)"); + + b.Property("ProfileType") + .HasColumnType("longtext"); + + b.Property("RestDayCurrentTotal") + .HasColumnType("double"); + + b.Property("RestDayOldTotal") + .HasColumnType("double"); + + b.Property("Root") + .HasColumnType("longtext"); + + b.Property("RootDnaId") + .HasColumnType("char(36)"); + + b.Property("RootId") + .HasColumnType("char(36)"); + + b.Property("StudyDayCountry") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("StudyDayDegreeLevel") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("StudyDayScholarship") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("StudyDaySubject") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("StudyDayTrainingName") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("StudyDayTrainingSubject") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("StudyDayUniversityName") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("TypeId") + .HasColumnType("char(36)"); + + b.Property("WifeDayDateBorn") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("WifeDayName") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("LeaveCancelDocumentId"); + + b.HasIndex("LeaveDraftDocumentId"); + + b.HasIndex("TypeId"); + + b.ToTable("LeaveRequests"); + }); + + modelBuilder.Entity("BMA.EHR.Domain.Models.Leave.Requests.LeaveRequestApprover", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)") + .HasColumnOrder(0) + .HasComment("PrimaryKey") + .HasAnnotation("Relational:JsonPropertyName", "id"); + + b.Property("ApproveStatus") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("ApproveType") + .HasColumnType("longtext"); + + b.Property("Comment") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)") + .HasColumnOrder(100) + .HasComment("สร้างข้อมูลเมื่อ"); + + b.Property("CreatedFullName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)") + .HasColumnOrder(104) + .HasComment("ชื่อ User ที่สร้างข้อมูล"); + + b.Property("CreatedUserId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("varchar(40)") + .HasColumnOrder(101) + .HasComment("User Id ที่สร้างข้อมูล"); + + b.Property("FirstName") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("KeycloakId") + .HasColumnType("char(36)"); + + b.Property("LastName") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("LastUpdateFullName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)") + .HasColumnOrder(105) + .HasComment("ชื่อ User ที่แก้ไขข้อมูลล่าสุด"); + + b.Property("LastUpdateUserId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("varchar(40)") + .HasColumnOrder(103) + .HasComment("User Id ที่แก้ไขข้อมูลล่าสุด"); + + b.Property("LastUpdatedAt") + .HasColumnType("datetime(6)") + .HasColumnOrder(102) + .HasComment("แก้ไขข้อมูลล่าสุดเมื่อ"); + + b.Property("LeaveRequestId") + .HasColumnType("char(36)"); + + b.Property("OrganizationName") + .IsRequired() + .HasColumnType("longtext") + .HasComment("สังกัด"); + + b.Property("PosExecutiveName") + .IsRequired() + .HasColumnType("longtext") + .HasComment("ตำแหน่งทางการบริหาร"); + + b.Property("PositionLevelName") + .IsRequired() + .HasColumnType("longtext") + .HasComment("ประเภทระดับตำแหน่ง"); + + b.Property("PositionName") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("PositionSign") + .HasColumnType("longtext") + .HasComment("ตำแหน่งใต้ลายเช็นต์"); + + b.Property("Prefix") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("ProfileId") + .HasColumnType("char(36)"); + + b.Property("Seq") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("LeaveRequestId"); + + b.ToTable("LeaveRequestApprovers"); + }); + + modelBuilder.Entity("BMA.EHR.Domain.Models.Leave.TimeAttendants.AdditionalCheckRequest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)") + .HasColumnOrder(0) + .HasComment("PrimaryKey") + .HasAnnotation("Relational:JsonPropertyName", "id"); + + b.Property("CheckDate") + .HasColumnType("datetime(6)") + .HasComment("*วันที่ลงเวลา"); + + b.Property("CheckInEdit") + .HasColumnType("tinyint(1)") + .HasComment("*ขอลงเวลาช่วงเช้า"); + + b.Property("CheckOutEdit") + .HasColumnType("tinyint(1)") + .HasComment("*ขอลงเวลาช่วงบ่าย"); + + b.Property("Child1DnaId") + .HasColumnType("char(36)"); + + b.Property("Child2DnaId") + .HasColumnType("char(36)"); + + b.Property("Child3DnaId") + .HasColumnType("char(36)"); + + b.Property("Child4DnaId") + .HasColumnType("char(36)"); + + b.Property("Comment") + .HasColumnType("longtext") + .HasComment("หมายเหตุในการการอนุมัติ/ไม่อนุมัติ"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)") + .HasColumnOrder(100) + .HasComment("สร้างข้อมูลเมื่อ"); + + b.Property("CreatedFullName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)") + .HasColumnOrder(104) + .HasComment("ชื่อ User ที่สร้างข้อมูล"); + + b.Property("CreatedUserId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("varchar(40)") + .HasColumnOrder(101) + .HasComment("User Id ที่สร้างข้อมูล"); + + b.Property("Description") + .IsRequired() + .HasColumnType("longtext") + .HasComment("*หมายเหตุขอลงเวลาพิเศษ"); + + b.Property("FirstName") + .HasColumnType("longtext"); + + b.Property("KeycloakUserId") + .HasColumnType("char(36)") + .HasComment("รหัส User ของ Keycloak ที่ร้องขอ"); + + b.Property("LastName") + .HasColumnType("longtext"); + + b.Property("LastUpdateFullName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)") + .HasColumnOrder(105) + .HasComment("ชื่อ User ที่แก้ไขข้อมูลล่าสุด"); + + b.Property("LastUpdateUserId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("varchar(40)") + .HasColumnOrder(103) + .HasComment("User Id ที่แก้ไขข้อมูลล่าสุด"); + + b.Property("LastUpdatedAt") + .HasColumnType("datetime(6)") + .HasColumnOrder(102) + .HasComment("แก้ไขข้อมูลล่าสุดเมื่อ"); + + b.Property("Latitude") + .HasColumnType("double"); + + b.Property("Longitude") + .HasColumnType("double"); + + b.Property("POI") + .HasColumnType("longtext"); + + b.Property("Prefix") + .HasColumnType("longtext"); + + b.Property("RootDnaId") + .HasColumnType("char(36)"); + + b.Property("Status") + .IsRequired() + .HasColumnType("longtext") + .HasComment("สถานะการอนุมัติ"); + + b.HasKey("Id"); + + b.ToTable("AdditionalCheckRequests"); + }); + + modelBuilder.Entity("BMA.EHR.Domain.Models.Leave.TimeAttendants.DutyTime", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)") + .HasColumnOrder(0) + .HasComment("PrimaryKey") + .HasAnnotation("Relational:JsonPropertyName", "id"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)") + .HasColumnOrder(100) + .HasComment("สร้างข้อมูลเมื่อ"); + + b.Property("CreatedFullName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)") + .HasColumnOrder(104) + .HasComment("ชื่อ User ที่สร้างข้อมูล"); + + b.Property("CreatedUserId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("varchar(40)") + .HasColumnOrder(101) + .HasComment("User Id ที่สร้างข้อมูล"); + + b.Property("Description") + .IsRequired() + .HasColumnType("longtext") + .HasComment("คำอธิบาย"); + + b.Property("EndTimeAfternoon") + .IsRequired() + .HasColumnType("longtext") + .HasComment("เวลาออกงานช่วงบ่าย"); + + b.Property("EndTimeMorning") + .IsRequired() + .HasColumnType("longtext") + .HasComment("เวลาออกงานช่วงเช้า"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)") + .HasComment("สถานะการเปิดใช้งาน (เปิด/ปิด)"); + + b.Property("IsDefault") + .HasColumnType("tinyint(1)") + .HasComment("สถานะว่ารอบใดเป็นค่า Default ของข้าราชการ (สำหรับทุกคนที่ยังไม่ได้ทำการเลือกรอบ)"); + + b.Property("LastUpdateFullName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)") + .HasColumnOrder(105) + .HasComment("ชื่อ User ที่แก้ไขข้อมูลล่าสุด"); + + b.Property("LastUpdateUserId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("varchar(40)") + .HasColumnOrder(103) + .HasComment("User Id ที่แก้ไขข้อมูลล่าสุด"); + + b.Property("LastUpdatedAt") + .HasColumnType("datetime(6)") + .HasColumnOrder(102) + .HasComment("แก้ไขข้อมูลล่าสุดเมื่อ"); + + b.Property("StartTimeAfternoon") + .IsRequired() + .HasColumnType("longtext") + .HasComment("เวลาเข้างานช่วงบ่าย"); + + b.Property("StartTimeMorning") + .IsRequired() + .HasColumnType("longtext") + .HasComment("เวลาเข้างานช่วงเช้า"); + + b.HasKey("Id"); + + b.ToTable("DutyTimes"); + }); + + modelBuilder.Entity("BMA.EHR.Domain.Models.Leave.TimeAttendants.ProcessUserTimeStamp", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)") + .HasColumnOrder(0) + .HasComment("PrimaryKey") + .HasAnnotation("Relational:JsonPropertyName", "id"); + + b.Property("CheckIn") + .HasColumnType("datetime(6)") + .HasComment("วัน เวลา เข้างาน"); + + b.Property("CheckInImageUrl") + .IsRequired() + .HasColumnType("longtext") + .HasComment("รูปถ่ายสถานที่ Check-In"); + + b.Property("CheckInLat") + .HasColumnType("double") + .HasComment("พิกัดละติจูด Check-In"); + + b.Property("CheckInLocationName") + .HasColumnType("longtext") + .HasComment("กรณีเลือกนอกสถานที่ตั้ง ต้องระบุข้อมูลชื่อสถานะที่ Check-In"); + + b.Property("CheckInLon") + .HasColumnType("double") + .HasComment("พิกัดลองจิจูด Check-In"); + + b.Property("CheckInPOI") + .IsRequired() + .HasColumnType("longtext") + .HasComment("ชื่อสถานที่ ได้มาจากระบบ ArcGis ของกองสารสนเทศภูมิศาสตร์ Check-In"); + + b.Property("CheckInRemark") + .HasColumnType("longtext") + .HasComment("ข้อความหมายเหตุที่ต้องการระบุเพิ่ม(มีเผื่อไว้อาจไม่ได้ใช้) Check-In"); + + b.Property("CheckInStatus") + .IsRequired() + .HasColumnType("longtext") + .HasComment("สถานะ Check-In"); + + b.Property("CheckOut") + .HasColumnType("datetime(6)") + .HasComment("วัน เวลา ออกงาน"); + + b.Property("CheckOutImageUrl") + .IsRequired() + .HasColumnType("longtext") + .HasComment("รูปถ่ายสถานที่ Check-Out"); + + b.Property("CheckOutLat") + .HasColumnType("double") + .HasComment("พิกัดละติจูด Check-Out"); + + b.Property("CheckOutLocationName") + .HasColumnType("longtext") + .HasComment("กรณีเลือกนอกสถานที่ตั้ง ต้องระบุข้อมูลชื่อสถานะที่ Check-Out"); + + b.Property("CheckOutLon") + .HasColumnType("double") + .HasComment("พิกัดลองจิจูด Check-Out"); + + b.Property("CheckOutPOI") + .IsRequired() + .HasColumnType("longtext") + .HasComment("ชื่อสถานที่ ได้มาจากระบบ ArcGis ของกองสารสนเทศภูมิศาสตร์ Check-Out"); + + b.Property("CheckOutRemark") + .HasColumnType("longtext") + .HasComment("ข้อความหมายเหตุที่ต้องการระบุเพิ่ม(มีเผื่อไว้อาจไม่ได้ใช้) Check-Out"); + + b.Property("CheckOutStatus") + .HasColumnType("longtext") + .HasComment("สถานะ Check-Out"); + + b.Property("Child1") + .HasColumnType("longtext"); + + b.Property("Child1DnaId") + .HasColumnType("char(36)"); + + b.Property("Child1Id") + .HasColumnType("char(36)"); + + b.Property("Child2") + .HasColumnType("longtext"); + + b.Property("Child2DnaId") + .HasColumnType("char(36)"); + + b.Property("Child2Id") + .HasColumnType("char(36)"); + + b.Property("Child3") + .HasColumnType("longtext"); + + b.Property("Child3DnaId") + .HasColumnType("char(36)"); + + b.Property("Child3Id") + .HasColumnType("char(36)"); + + b.Property("Child4") + .HasColumnType("longtext"); + + b.Property("Child4DnaId") + .HasColumnType("char(36)"); + + b.Property("Child4Id") + .HasColumnType("char(36)"); + + b.Property("CitizenId") + .HasColumnType("longtext"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)") + .HasColumnOrder(100) + .HasComment("สร้างข้อมูลเมื่อ"); + + b.Property("CreatedFullName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)") + .HasColumnOrder(104) + .HasComment("ชื่อ User ที่สร้างข้อมูล"); + + b.Property("CreatedUserId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("varchar(40)") + .HasColumnOrder(101) + .HasComment("User Id ที่สร้างข้อมูล"); + + b.Property("EditReason") + .HasColumnType("longtext") + .HasComment("เหตุผลการอนุมัติ/ไม่อนุมัติขอลงเวลาพิเศษ"); + + b.Property("EditStatus") + .HasColumnType("longtext") + .HasComment("สถานะการของลงเวลาพิเศษ"); + + b.Property("FirstName") + .HasColumnType("longtext"); + + b.Property("Gender") + .HasColumnType("longtext"); + + b.Property("IsLocationCheckIn") + .HasColumnType("tinyint(1)") + .HasComment("true คือ ณ สถานที่ตั้ง, false คือ นอกสถานที่ตั้ง Check-In"); + + b.Property("IsLocationCheckOut") + .HasColumnType("tinyint(1)") + .HasComment("true คือ ณ สถานที่ตั้ง, false คือ นอกสถานที่ตั้ง Check-Out"); + + b.Property("IsProcess") + .HasColumnType("tinyint(1)") + .HasComment("นำไปประมวลผลแล้วหรือยัง"); + + b.Property("KeycloakUserId") + .HasColumnType("char(36)") + .HasComment("รหัส User ของ Keycloak"); + + b.Property("LastName") + .HasColumnType("longtext"); + + b.Property("LastUpdateFullName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)") + .HasColumnOrder(105) + .HasComment("ชื่อ User ที่แก้ไขข้อมูลล่าสุด"); + + b.Property("LastUpdateUserId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("varchar(40)") + .HasColumnOrder(103) + .HasComment("User Id ที่แก้ไขข้อมูลล่าสุด"); + + b.Property("LastUpdatedAt") + .HasColumnType("datetime(6)") + .HasColumnOrder(102) + .HasComment("แก้ไขข้อมูลล่าสุดเมื่อ"); + + b.Property("Prefix") + .HasColumnType("longtext"); + + b.Property("ProfileId") + .HasColumnType("char(36)"); + + b.Property("ProfileType") + .HasColumnType("longtext"); + + b.Property("Root") + .HasColumnType("longtext"); + + b.Property("RootDnaId") + .HasColumnType("char(36)"); + + b.Property("RootId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.ToTable("ProcessUserTimeStamps"); + }); + + modelBuilder.Entity("BMA.EHR.Domain.Models.Leave.TimeAttendants.UserCalendar", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)") + .HasColumnOrder(0) + .HasComment("PrimaryKey") + .HasAnnotation("Relational:JsonPropertyName", "id"); + + b.Property("Calendar") + .IsRequired() + .HasColumnType("longtext") + .HasComment("ปฏิทินการทำงานของ ขรก ปกติ หรือ 6 วันต่อสัปดาห์"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)") + .HasColumnOrder(100) + .HasComment("สร้างข้อมูลเมื่อ"); + + b.Property("CreatedFullName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)") + .HasColumnOrder(104) + .HasComment("ชื่อ User ที่สร้างข้อมูล"); + + b.Property("CreatedUserId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("varchar(40)") + .HasColumnOrder(101) + .HasComment("User Id ที่สร้างข้อมูล"); + + b.Property("LastUpdateFullName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)") + .HasColumnOrder(105) + .HasComment("ชื่อ User ที่แก้ไขข้อมูลล่าสุด"); + + b.Property("LastUpdateUserId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("varchar(40)") + .HasColumnOrder(103) + .HasComment("User Id ที่แก้ไขข้อมูลล่าสุด"); + + b.Property("LastUpdatedAt") + .HasColumnType("datetime(6)") + .HasColumnOrder(102) + .HasComment("แก้ไขข้อมูลล่าสุดเมื่อ"); + + b.Property("ProfileId") + .HasColumnType("char(36)") + .HasComment("รหัส Profile ในระบบทะเบียนประวัติ"); + + b.HasKey("Id"); + + b.ToTable("UserCalendars"); + }); + + modelBuilder.Entity("BMA.EHR.Domain.Models.Leave.TimeAttendants.UserDutyTime", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)") + .HasColumnOrder(0) + .HasComment("PrimaryKey") + .HasAnnotation("Relational:JsonPropertyName", "id"); + + b.Property("Child1DnaId") + .HasColumnType("char(36)"); + + b.Property("Child2DnaId") + .HasColumnType("char(36)"); + + b.Property("Child3DnaId") + .HasColumnType("char(36)"); + + b.Property("Child4DnaId") + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)") + .HasColumnOrder(100) + .HasComment("สร้างข้อมูลเมื่อ"); + + b.Property("CreatedFullName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)") + .HasColumnOrder(104) + .HasComment("ชื่อ User ที่สร้างข้อมูล"); + + b.Property("CreatedUserId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("varchar(40)") + .HasColumnOrder(101) + .HasComment("User Id ที่สร้างข้อมูล"); + + b.Property("DutyTimeId") + .HasColumnType("char(36)") + .HasComment("รหัสรอบการลงเวลา"); + + b.Property("EffectiveDate") + .HasColumnType("datetime(6)") + .HasComment("วันที่มีผล"); + + b.Property("IsProcess") + .HasColumnType("tinyint(1)") + .HasComment("ทำการประมวลผลแล้วหรือยัง"); + + b.Property("LastUpdateFullName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)") + .HasColumnOrder(105) + .HasComment("ชื่อ User ที่แก้ไขข้อมูลล่าสุด"); + + b.Property("LastUpdateUserId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("varchar(40)") + .HasColumnOrder(103) + .HasComment("User Id ที่แก้ไขข้อมูลล่าสุด"); + + b.Property("LastUpdatedAt") + .HasColumnType("datetime(6)") + .HasColumnOrder(102) + .HasComment("แก้ไขข้อมูลล่าสุดเมื่อ"); + + b.Property("ProfileId") + .HasColumnType("char(36)") + .HasComment("รหัส Profile ในระบบทะเบียนประวัติ"); + + b.Property("Remark") + .HasColumnType("longtext") + .HasComment("หมายเหตุ"); + + b.Property("RootDnaId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("DutyTimeId"); + + b.ToTable("UserDutyTimes"); + }); + + modelBuilder.Entity("BMA.EHR.Domain.Models.Leave.TimeAttendants.UserTimeStamp", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)") + .HasColumnOrder(0) + .HasComment("PrimaryKey") + .HasAnnotation("Relational:JsonPropertyName", "id"); + + b.Property("CheckIn") + .HasColumnType("datetime(6)") + .HasComment("วัน เวลา เข้างาน"); + + b.Property("CheckInImageUrl") + .IsRequired() + .HasColumnType("longtext") + .HasComment("รูปถ่ายสถานที่ Check-In"); + + b.Property("CheckInLat") + .HasColumnType("double") + .HasComment("พิกัดละติจูด Check-In"); + + b.Property("CheckInLocationName") + .HasColumnType("longtext") + .HasComment("กรณีเลือกนอกสถานที่ตั้ง ต้องระบุข้อมูลชื่อสถานะที่ Check-In"); + + b.Property("CheckInLon") + .HasColumnType("double") + .HasComment("พิกัดลองจิจูด Check-In"); + + b.Property("CheckInPOI") + .IsRequired() + .HasColumnType("longtext") + .HasComment("ชื่อสถานที่ ได้มาจากระบบ ArcGis ของกองสารสนเทศภูมิศาสตร์ Check-In"); + + b.Property("CheckInRemark") + .HasColumnType("longtext") + .HasComment("ข้อความหมายเหตุที่ต้องการระบุเพิ่ม(มีเผื่อไว้อาจไม่ได้ใช้) Check-In"); + + b.Property("CheckOut") + .HasColumnType("datetime(6)") + .HasComment("วัน เวลา ออกงาน"); + + b.Property("CheckOutImageUrl") + .IsRequired() + .HasColumnType("longtext") + .HasComment("รูปถ่ายสถานที่ Check-Out"); + + b.Property("CheckOutLat") + .HasColumnType("double") + .HasComment("พิกัดละติจูด Check-Out"); + + b.Property("CheckOutLocationName") + .HasColumnType("longtext") + .HasComment("กรณีเลือกนอกสถานที่ตั้ง ต้องระบุข้อมูลชื่อสถานะที่ Check-Out"); + + b.Property("CheckOutLon") + .HasColumnType("double") + .HasComment("พิกัดลองจิจูด Check-Out"); + + b.Property("CheckOutPOI") + .IsRequired() + .HasColumnType("longtext") + .HasComment("ชื่อสถานที่ ได้มาจากระบบ ArcGis ของกองสารสนเทศภูมิศาสตร์ Check-Out"); + + b.Property("CheckOutRemark") + .HasColumnType("longtext") + .HasComment("ข้อความหมายเหตุที่ต้องการระบุเพิ่ม(มีเผื่อไว้อาจไม่ได้ใช้) Check-Out"); + + b.Property("Child1") + .HasColumnType("longtext"); + + b.Property("Child1DnaId") + .HasColumnType("char(36)"); + + b.Property("Child1Id") + .HasColumnType("char(36)"); + + b.Property("Child2") + .HasColumnType("longtext"); + + b.Property("Child2DnaId") + .HasColumnType("char(36)"); + + b.Property("Child2Id") + .HasColumnType("char(36)"); + + b.Property("Child3") + .HasColumnType("longtext"); + + b.Property("Child3DnaId") + .HasColumnType("char(36)"); + + b.Property("Child3Id") + .HasColumnType("char(36)"); + + b.Property("Child4") + .HasColumnType("longtext"); + + b.Property("Child4DnaId") + .HasColumnType("char(36)"); + + b.Property("Child4Id") + .HasColumnType("char(36)"); + + b.Property("CitizenId") + .HasColumnType("longtext"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)") + .HasColumnOrder(100) + .HasComment("สร้างข้อมูลเมื่อ"); + + b.Property("CreatedFullName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)") + .HasColumnOrder(104) + .HasComment("ชื่อ User ที่สร้างข้อมูล"); + + b.Property("CreatedUserId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("varchar(40)") + .HasColumnOrder(101) + .HasComment("User Id ที่สร้างข้อมูล"); + + b.Property("FirstName") + .HasColumnType("longtext"); + + b.Property("Gender") + .HasColumnType("longtext"); + + b.Property("IsLocationCheckIn") + .HasColumnType("tinyint(1)") + .HasComment("true คือ ณ สถานที่ตั้ง, false คือ นอกสถานที่ตั้ง Check-In"); + + b.Property("IsLocationCheckOut") + .HasColumnType("tinyint(1)") + .HasComment("true คือ ณ สถานที่ตั้ง, false คือ นอกสถานที่ตั้ง Check-Out"); + + b.Property("IsProcess") + .HasColumnType("tinyint(1)") + .HasComment("นำไปประมวลผลแล้วหรือยัง"); + + b.Property("KeycloakUserId") + .HasColumnType("char(36)") + .HasComment("รหัส User ของ Keycloak"); + + b.Property("LastName") + .HasColumnType("longtext"); + + b.Property("LastUpdateFullName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)") + .HasColumnOrder(105) + .HasComment("ชื่อ User ที่แก้ไขข้อมูลล่าสุด"); + + b.Property("LastUpdateUserId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("varchar(40)") + .HasColumnOrder(103) + .HasComment("User Id ที่แก้ไขข้อมูลล่าสุด"); + + b.Property("LastUpdatedAt") + .HasColumnType("datetime(6)") + .HasColumnOrder(102) + .HasComment("แก้ไขข้อมูลล่าสุดเมื่อ"); + + b.Property("Prefix") + .HasColumnType("longtext"); + + b.Property("ProfileId") + .HasColumnType("char(36)"); + + b.Property("ProfileType") + .HasColumnType("longtext"); + + b.Property("Root") + .HasColumnType("longtext"); + + b.Property("RootDnaId") + .HasColumnType("char(36)"); + + b.Property("RootId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.ToTable("UserTimeStamps"); + }); + + modelBuilder.Entity("BMA.EHR.Domain.Models.Leave.Requests.LeaveBeginning", b => + { + b.HasOne("BMA.EHR.Domain.Models.Leave.Commons.LeaveType", "LeaveType") + .WithMany() + .HasForeignKey("LeaveTypeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("LeaveType"); + }); + + modelBuilder.Entity("BMA.EHR.Domain.Models.Leave.Requests.LeaveDocument", b => + { + b.HasOne("BMA.EHR.Domain.Models.Documents.Document", "Document") + .WithMany() + .HasForeignKey("DocumentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("BMA.EHR.Domain.Models.Leave.Requests.LeaveRequest", "LeaveRequest") + .WithMany("LeaveDocument") + .HasForeignKey("LeaveRequestId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Document"); + + b.Navigation("LeaveRequest"); + }); + + modelBuilder.Entity("BMA.EHR.Domain.Models.Leave.Requests.LeaveRequest", b => + { + b.HasOne("BMA.EHR.Domain.Models.Documents.Document", "LeaveCancelDocument") + .WithMany() + .HasForeignKey("LeaveCancelDocumentId"); + + b.HasOne("BMA.EHR.Domain.Models.Documents.Document", "LeaveDraftDocument") + .WithMany() + .HasForeignKey("LeaveDraftDocumentId"); + + b.HasOne("BMA.EHR.Domain.Models.Leave.Commons.LeaveType", "Type") + .WithMany() + .HasForeignKey("TypeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("LeaveCancelDocument"); + + b.Navigation("LeaveDraftDocument"); + + b.Navigation("Type"); + }); + + modelBuilder.Entity("BMA.EHR.Domain.Models.Leave.Requests.LeaveRequestApprover", b => + { + b.HasOne("BMA.EHR.Domain.Models.Leave.Requests.LeaveRequest", "LeaveRequest") + .WithMany("Approvers") + .HasForeignKey("LeaveRequestId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("LeaveRequest"); + }); + + modelBuilder.Entity("BMA.EHR.Domain.Models.Leave.TimeAttendants.UserDutyTime", b => + { + b.HasOne("BMA.EHR.Domain.Models.Leave.TimeAttendants.DutyTime", "DutyTime") + .WithMany() + .HasForeignKey("DutyTimeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("DutyTime"); + }); + + modelBuilder.Entity("BMA.EHR.Domain.Models.Leave.Requests.LeaveRequest", b => + { + b.Navigation("Approvers"); + + b.Navigation("LeaveDocument"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/BMA.EHR.Infrastructure/Migrations/LeaveDb/20260115140500_add_fields_table_eaveequestpprover.cs b/BMA.EHR.Infrastructure/Migrations/LeaveDb/20260115140500_add_fields_table_eaveequestpprover.cs new file mode 100644 index 00000000..02aee038 --- /dev/null +++ b/BMA.EHR.Infrastructure/Migrations/LeaveDb/20260115140500_add_fields_table_eaveequestpprover.cs @@ -0,0 +1,54 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace BMA.EHR.Infrastructure.Migrations.LeaveDb +{ + /// + public partial class add_fields_table_eaveequestpprover : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "OrganizationName", + table: "LeaveRequestApprovers", + type: "longtext", + nullable: false, + comment: "สังกัด") + .Annotation("MySql:CharSet", "utf8mb4"); + + migrationBuilder.AddColumn( + name: "PosExecutiveName", + table: "LeaveRequestApprovers", + type: "longtext", + nullable: false, + comment: "ตำแหน่งทางการบริหาร") + .Annotation("MySql:CharSet", "utf8mb4"); + + migrationBuilder.AddColumn( + name: "PositionLevelName", + table: "LeaveRequestApprovers", + type: "longtext", + nullable: false, + comment: "ประเภทระดับตำแหน่ง") + .Annotation("MySql:CharSet", "utf8mb4"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "OrganizationName", + table: "LeaveRequestApprovers"); + + migrationBuilder.DropColumn( + name: "PosExecutiveName", + table: "LeaveRequestApprovers"); + + migrationBuilder.DropColumn( + name: "PositionLevelName", + table: "LeaveRequestApprovers"); + } + } +} diff --git a/BMA.EHR.Infrastructure/Migrations/LeaveDb/LeaveDbContextModelSnapshot.cs b/BMA.EHR.Infrastructure/Migrations/LeaveDb/LeaveDbContextModelSnapshot.cs index c0a77c10..14e45995 100644 --- a/BMA.EHR.Infrastructure/Migrations/LeaveDb/LeaveDbContextModelSnapshot.cs +++ b/BMA.EHR.Infrastructure/Migrations/LeaveDb/LeaveDbContextModelSnapshot.cs @@ -727,6 +727,21 @@ namespace BMA.EHR.Infrastructure.Migrations.LeaveDb b.Property("LeaveRequestId") .HasColumnType("char(36)"); + b.Property("OrganizationName") + .IsRequired() + .HasColumnType("longtext") + .HasComment("สังกัด"); + + b.Property("PosExecutiveName") + .IsRequired() + .HasColumnType("longtext") + .HasComment("ตำแหน่งทางการบริหาร"); + + b.Property("PositionLevelName") + .IsRequired() + .HasColumnType("longtext") + .HasComment("ประเภทระดับตำแหน่ง"); + b.Property("PositionName") .IsRequired() .HasColumnType("longtext"); diff --git a/BMA.EHR.Leave/Controllers/LeaveRequestController.cs b/BMA.EHR.Leave/Controllers/LeaveRequestController.cs index cf17535f..4db84d53 100644 --- a/BMA.EHR.Leave/Controllers/LeaveRequestController.cs +++ b/BMA.EHR.Leave/Controllers/LeaveRequestController.cs @@ -160,6 +160,9 @@ namespace BMA.EHR.Leave.Service.Controllers LastName = r.LastName, PositionName = r.PositionName, PositionSign = r.PositionSign, + PosExecutiveName = r.PosExecutiveName, + PositionLevelName = r.PositionLeaveName, + OrganizationName = r.OrganizationName, ProfileId = r.ProfileId, KeycloakId = r.KeycloakId, ApproveStatus = "PENDING", @@ -2034,6 +2037,9 @@ namespace BMA.EHR.Leave.Service.Controllers ProfileId = profile.Id, KeycloakId = Guid.Parse(UserId!), ApproveType = "SENDER", + PositionLevelName = profile.PositionLeaveName ?? "", + PosExecutiveName = profile.PosExecutiveName ?? "", + OrganizationName = profile.Oc ?? "", CreatedFullName = FullName ?? "", CreatedUserId = UserId!, diff --git a/BMA.EHR.Leave/DTOs/LeaveRequest/LeaveRequestApproverDto.cs b/BMA.EHR.Leave/DTOs/LeaveRequest/LeaveRequestApproverDto.cs index 21c2a216..b5ff27a5 100644 --- a/BMA.EHR.Leave/DTOs/LeaveRequest/LeaveRequestApproverDto.cs +++ b/BMA.EHR.Leave/DTOs/LeaveRequest/LeaveRequestApproverDto.cs @@ -27,5 +27,15 @@ namespace BMA.EHR.Leave.Service.DTOs.LeaveRequest [JsonProperty("keycloakId")] public Guid KeycloakId { get; set; } = Guid.Empty; + + [JsonProperty("positionLeaveName")] + public string PositionLeaveName { get; set; } = string.Empty; + + [JsonProperty("posExecutiveName")] + public string PosExecutiveName { get; set; } = string.Empty; + + [JsonProperty("organizationName")] + public string OrganizationName { get; set; } = string.Empty; + } } From 094789bfb174db57f20b9a3d9892f5b57cda5938 Mon Sep 17 00:00:00 2001 From: harid Date: Fri, 16 Jan 2026 15:43:55 +0700 Subject: [PATCH 039/183] =?UTF-8?q?API=20=E0=B8=82=E0=B8=AD=E0=B8=A5?= =?UTF-8?q?=E0=B8=B2=E0=B8=AD=E0=B8=AD=E0=B8=81=E0=B9=81=E0=B8=A5=E0=B8=B0?= =?UTF-8?q?=E0=B9=82=E0=B8=AD=E0=B8=99=E0=B8=AD=E0=B8=AD=E0=B8=81=20?= =?UTF-8?q?=E0=B8=9D=E0=B8=B1=E0=B9=88=E0=B8=87=20admin=20#2196?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../PlacementTransferController.cs | 134 ++++++++++++++++ .../Requests/PlacementTransferRequest.cs | 9 ++ .../Controllers/RetirementResignController.cs | 149 ++++++++++++++++++ .../RetirementResignEmployeeController.cs | 142 +++++++++++++++++ .../RetirementResignEmployeeRequest.cs | 17 ++ .../Requests/RetirementResignRequest.cs | 17 ++ 6 files changed, 468 insertions(+) diff --git a/BMA.EHR.Placement.Service/Controllers/PlacementTransferController.cs b/BMA.EHR.Placement.Service/Controllers/PlacementTransferController.cs index 566b799b..e27b4ad3 100644 --- a/BMA.EHR.Placement.Service/Controllers/PlacementTransferController.cs +++ b/BMA.EHR.Placement.Service/Controllers/PlacementTransferController.cs @@ -683,6 +683,140 @@ namespace BMA.EHR.Placement.Service.Controllers return Success(placementTransfer.Id); } + /// + /// สร้างคำขอโอน โดย admin + /// + /// + /// + /// ค่าตัวแปรที่ส่งมาไม่ถูกต้อง + /// ไม่ได้ Login เข้าระบบ + /// เมื่อเกิดข้อผิดพลาดในการทำงาน + [HttpPost("admin")] + public async Task> PostAdmin([FromForm] PlacementTransferAdminRequest req) + { + var placementTransfer = new PlacementTransfer + { + Organization = req.Organization, + Reason = req.Reason, + Status = "APPROVE", + CreatedFullName = FullName ?? "System Administrator", + CreatedUserId = UserId ?? "", + CreatedAt = DateTime.Now, + LastUpdateFullName = FullName ?? "System Administrator", + LastUpdateUserId = UserId ?? "", + LastUpdatedAt = DateTime.Now, + }; + var apiUrl = $"{_configuration["API"]}/org/profile/profileid/position/{req.ProfileId}"; + using (var client = new HttpClient()) + { + client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token.Replace("Bearer ", "")); + client.DefaultRequestHeaders.Add("api-key", _configuration["API_KEY"]); + var _req = new HttpRequestMessage(HttpMethod.Get, apiUrl); + var _res = await client.SendAsync(_req); + var _result = await _res.Content.ReadAsStringAsync(); + + var org = JsonConvert.DeserializeObject(_result); + + if (org == null || org.result == null) + return Error("ไม่พบหน่วยงานของผู้ใช้งานคนนี้", 404); + + placementTransfer.profileId = org.result.profileId; + placementTransfer.prefix = org.result.prefix; + placementTransfer.firstName = org.result.firstName; + placementTransfer.lastName = org.result.lastName; + placementTransfer.citizenId = org.result.citizenId; + placementTransfer.rootOld = org.result.root; + placementTransfer.rootOldId = org.result.rootId; + placementTransfer.rootDnaOldId = org.result.rootDnaId; + placementTransfer.rootShortNameOld = org.result.rootShortName; + placementTransfer.child1Old = org.result.child1; + placementTransfer.child1OldId = org.result.child1Id; + placementTransfer.child1DnaOldId = org.result.child1DnaId; + placementTransfer.child1ShortNameOld = org.result.child1ShortName; + placementTransfer.child2Old = org.result.child2; + placementTransfer.child2OldId = org.result.child2Id; + placementTransfer.child2DnaOldId = org.result.child2DnaId; + placementTransfer.child2ShortNameOld = org.result.child2ShortName; + placementTransfer.child3Old = org.result.child3; + placementTransfer.child3OldId = org.result.child3Id; + placementTransfer.child3DnaOldId = org.result.child3DnaId; + placementTransfer.child3ShortNameOld = org.result.child3ShortName; + placementTransfer.child4Old = org.result.child4; + placementTransfer.child4OldId = org.result.child4Id; + placementTransfer.child4DnaOldId = org.result.child4DnaId; + placementTransfer.child4ShortNameOld = org.result.child4ShortName; + placementTransfer.posMasterNoOld = org.result.posMasterNo; + placementTransfer.posTypeOldId = org.result.posTypeId; + placementTransfer.posTypeNameOld = org.result.posTypeName; + placementTransfer.posLevelOldId = org.result.posLevelId; + placementTransfer.posLevelNameOld = org.result.posLevelName; + placementTransfer.AmountOld = org.result.salary; + placementTransfer.PositionOld = org.result.position; + placementTransfer.PositionExecutiveOld = org.result.posExecutiveName; + placementTransfer.positionExecutiveFieldOld = org.result.positionExecutiveField; + placementTransfer.positionAreaOld = org.result.positionArea; + placementTransfer.PositionLevelOld = org.result.posLevelName; + placementTransfer.PositionTypeOld = org.result.posTypeName; + placementTransfer.PositionNumberOld = org.result.nodeShortName + " " + org.result.posMasterNo; + placementTransfer.OrganizationOld = (org.result.child4 == null ? "" : org.result.child4 + "\n") + + (org.result.child3 == null ? "" : org.result.child3 + "\n") + + (org.result.child2 == null ? "" : org.result.child2 + "\n") + + (org.result.child1 == null ? "" : org.result.child1 + "\n") + + (org.result.root == null ? "" : org.result.root); + placementTransfer.OrganizationPositionOld = org.result.position + "\n" + + (placementTransfer.PositionExecutiveOld == null ? "" : (placementTransfer.positionExecutiveField == null ? placementTransfer.PositionExecutiveOld + "\n" : placementTransfer.PositionExecutiveOld + "(" + placementTransfer.positionExecutiveField + ")" + "\n")) + + placementTransfer.OrganizationOld; + } + await _context.PlacementTransfers.AddAsync(placementTransfer); + await _context.SaveChangesAsync(); + if (Request.Form.Files != null && Request.Form.Files.Count != 0) + { + foreach (var file in Request.Form.Files) + { + var fileExtension = Path.GetExtension(file.FileName); + + var doc = await _documentService.UploadFileAsync(file, file.FileName); + var _doc = await _context.Documents.AsQueryable() + .FirstOrDefaultAsync(x => x.Id == doc.Id); + if (_doc != null) + { + var placementTransferDoc = new PlacementTransferDoc + { + PlacementTransfer = placementTransfer, + Document = _doc, + CreatedFullName = FullName ?? "System Administrator", + CreatedUserId = UserId ?? "", + CreatedAt = DateTime.Now, + LastUpdateFullName = FullName ?? "System Administrator", + LastUpdateUserId = UserId ?? "", + LastUpdatedAt = DateTime.Now, + }; + await _context.PlacementTransferDocs.AddAsync(placementTransferDoc); + + } + } + } + + //var baseAPIOrg = _configuration["API"]; + //var apiUrlOrg = $"{baseAPIOrg}/org/workflow/add-workflow"; + //using (var client = new HttpClient()) + //{ + // client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token.Replace("Bearer ", "")); + // client.DefaultRequestHeaders.Add("api-key", _configuration["API_KEY"]); + // var _res = await client.PostAsJsonAsync(apiUrlOrg, new + // { + // refId = placementTransfer.Id, + // sysName = "SYS_TRANSFER_REQ", + // posLevelName = placementTransfer.posLevelNameOld, + // posTypeName = placementTransfer.posTypeNameOld, + // fullName = $"{placementTransfer.prefix}{placementTransfer.firstName} {placementTransfer.lastName}" + // }); + //} + await _context.SaveChangesAsync(); + + return Success(placementTransfer.Id); + } + /// /// แก้ไขคำขอโอน /// diff --git a/BMA.EHR.Placement.Service/Requests/PlacementTransferRequest.cs b/BMA.EHR.Placement.Service/Requests/PlacementTransferRequest.cs index 7f740043..6611f92c 100644 --- a/BMA.EHR.Placement.Service/Requests/PlacementTransferRequest.cs +++ b/BMA.EHR.Placement.Service/Requests/PlacementTransferRequest.cs @@ -10,4 +10,13 @@ namespace BMA.EHR.Placement.Service.Requests public DateTime? Date { get; set; } public List? File { get; set; } } + + public class PlacementTransferAdminRequest + { + public string ProfileId { get; set; } + public string Organization { get; set; } + public string Reason { get; set; } + public DateTime? Date { get; set; } + public List? File { get; set; } + } } diff --git a/BMA.EHR.Retirement.Service/Controllers/RetirementResignController.cs b/BMA.EHR.Retirement.Service/Controllers/RetirementResignController.cs index d33b945b..2caa44a1 100644 --- a/BMA.EHR.Retirement.Service/Controllers/RetirementResignController.cs +++ b/BMA.EHR.Retirement.Service/Controllers/RetirementResignController.cs @@ -1343,6 +1343,155 @@ namespace BMA.EHR.Retirement.Service.Controllers return Success(retirementResign); } + /// + /// สร้างการลาออก โดย admin + /// + /// + /// + /// ค่าตัวแปรที่ส่งมาไม่ถูกต้อง + /// ไม่ได้ Login เข้าระบบ + /// เมื่อเกิดข้อผิดพลาดในการทำงาน + [HttpPost("admin")] + public async Task> PostAdmin([FromForm] RetirementResignAdminRequest req) + { + var Remark = req.Remark; + if (req.Reason != null) + { + switch (req.Reason.Trim().ToUpper()) + { + case "CAREER": Remark = $"ประกอบอาชีพอื่น" + (req.Remark == null || req.Remark == "" ? $"{req.Remark}" : ""); break; + case "MOVE": Remark = $"รับราชการสังกัดอื่น" + (req.Remark == null || req.Remark == "" ? $"{req.Remark}" : ""); break; + case "FAMILY": Remark = $"ดูแลบิดามารดา" + (req.Remark == null || req.Remark == "" ? $"{req.Remark}" : ""); break; + case "EDUCATION": Remark = $"ศึกษาต่อ" + (req.Remark == null || req.Remark == "" ? $"{req.Remark}" : ""); break; + case "OTHER": Remark = $"อื่น ๆ" + (req.Remark == null || req.Remark == "" ? $"{req.Remark}" : ""); break; + } + } + + var retirementResign = new RetirementResign + { + ApproveStep = "st1", + Location = req.Location, + SendDate = DateTime.Now, + ActiveDate = req.ActiveDate, + Reason = req.Reason, + ReasonResign = Remark, + Remark = req.Remark, + Status = "APPROVE", + IsActive = true, + CreatedFullName = FullName ?? "System Administrator", + CreatedUserId = UserId ?? "", + CreatedAt = DateTime.Now, + LastUpdateFullName = FullName ?? "System Administrator", + LastUpdateUserId = UserId ?? "", + LastUpdatedAt = DateTime.Now, + }; + + var apiUrl = $"{_configuration["API"]}/org/profile/profileid/position/{req.ProfileId}"; + using (var client = new HttpClient()) + { + client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token.Replace("Bearer ", "")); + client.DefaultRequestHeaders.Add("api-key", _configuration["API_KEY"]); + var _req = new HttpRequestMessage(HttpMethod.Get, apiUrl); + var _res = await client.SendAsync(_req); + var _result = await _res.Content.ReadAsStringAsync(); + + var org = JsonConvert.DeserializeObject(_result); + + if (org == null || org.result == null) + return Error("ไม่พบหน่วยงานของผู้ใช้งานคนนี้", 404); + + retirementResign.profileId = org.result.profileId; + retirementResign.prefix = org.result.prefix; + retirementResign.firstName = org.result.firstName; + retirementResign.lastName = org.result.lastName; + retirementResign.citizenId = org.result.citizenId; + retirementResign.rootOld = org.result.root; + retirementResign.rootOldId = org.result.rootId; + retirementResign.rootDnaOldId = org.result.rootDnaId; + retirementResign.rootShortNameOld = org.result.rootShortName; + retirementResign.child1Old = org.result.child1; + retirementResign.child1OldId = org.result.child1Id; + retirementResign.child1DnaOldId = org.result.child1DnaId; + retirementResign.child1ShortNameOld = org.result.child1ShortName; + retirementResign.child2Old = org.result.child2; + retirementResign.child2OldId = org.result.child2Id; + retirementResign.child2DnaOldId = org.result.child2DnaId; + retirementResign.child2ShortNameOld = org.result.child2ShortName; + retirementResign.child3Old = org.result.child3; + retirementResign.child3OldId = org.result.child3Id; + retirementResign.child3DnaOldId = org.result.child3DnaId; + retirementResign.child3ShortNameOld = org.result.child3ShortName; + retirementResign.child4Old = org.result.child4; + retirementResign.child4OldId = org.result.child4Id; + retirementResign.child4DnaOldId = org.result.child4DnaId; + retirementResign.child4ShortNameOld = org.result.child4ShortName; + retirementResign.posMasterNoOld = org.result.posMasterNo; + retirementResign.posTypeOldId = org.result.posTypeId; + retirementResign.posTypeNameOld = org.result.posTypeName; + retirementResign.posLevelOldId = org.result.posLevelId; + retirementResign.posLevelNameOld = org.result.posLevelName; + retirementResign.AmountOld = org.result.salary; + retirementResign.PositionOld = org.result.position; + retirementResign.PositionExecutiveOld = org.result.posExecutiveName; + retirementResign.positionExecutiveFieldOld = org.result.positionExecutiveField; + retirementResign.positionAreaOld = org.result.positionArea; + retirementResign.PositionLevelOld = org.result.posLevelName; + retirementResign.PositionTypeOld = org.result.posTypeName; + retirementResign.PositionNumberOld = org.result.nodeShortName + " " + org.result.posMasterNo; + retirementResign.OrganizationOld = (org.result.child4 == null ? "" : org.result.child4 + "\n") + + (org.result.child3 == null ? "" : org.result.child3 + "\n") + + (org.result.child2 == null ? "" : org.result.child2 + "\n") + + (org.result.child1 == null ? "" : org.result.child1 + "\n") + + (org.result.root == null ? "" : org.result.root); + retirementResign.OrganizationPositionOld = org.result.position + "\n" + + (retirementResign.PositionExecutiveOld == null ? "" : (retirementResign.positionExecutiveField == null ? retirementResign.PositionExecutiveOld + "\n" : retirementResign.PositionExecutiveOld + "(" + retirementResign.positionExecutiveField + ")" + "\n")) + + retirementResign.OrganizationOld; + + if ((retirementResign.posTypeNameOld == "ทั่วไป" && retirementResign.posLevelNameOld == "ชำนาญงาน") || (retirementResign.posTypeNameOld == "ทั่วไป" && retirementResign.posLevelNameOld == "ปฏิบัติงาน") || (retirementResign.posTypeNameOld == "วิชาการ" && retirementResign.posLevelNameOld == "ปฏิบัติการ") || (retirementResign.posTypeNameOld == "วิชาการ" && retirementResign.posLevelNameOld == "ชำนาญการ")) + { + retirementResign.Group = "1.1"; + } + else if ((retirementResign.posTypeNameOld == "ทั่วไป" && retirementResign.posLevelNameOld == "อาวุโส") || (retirementResign.posTypeNameOld == "วิชาการ" && retirementResign.posLevelNameOld == "ชำนาญการพิเศษ") || (retirementResign.posTypeNameOld == "อำนวยการ" && retirementResign.posLevelNameOld == "ต้น")) + { + retirementResign.Group = "1.2"; + } + else if ((retirementResign.posTypeNameOld == "ทั่วไป" && retirementResign.posLevelNameOld == "ทักษะพิเศษ") || (retirementResign.posTypeNameOld == "วิชาการ" && retirementResign.posLevelNameOld == "เชี่ยวชาญ") || (retirementResign.posTypeNameOld == "วิชาการ" && retirementResign.posLevelNameOld == "ทรงคุณวุฒิ") || (retirementResign.posTypeNameOld == "อำนวยการ" && retirementResign.posLevelNameOld == "สูง") || (retirementResign.posTypeNameOld == "บริหาร" && retirementResign.posLevelNameOld == "ต้น") || (retirementResign.posTypeNameOld == "บริหาร" && retirementResign.posLevelNameOld == "สูง")) + { + retirementResign.Group = "2"; + } + } + await _context.RetirementResigns.AddAsync(retirementResign); + await _context.SaveChangesAsync(); + if (Request.Form.Files != null && Request.Form.Files.Count != 0) + { + foreach (var file in Request.Form.Files) + { + var fileExtension = Path.GetExtension(file.FileName); + + var doc = await _documentService.UploadFileAsync(file, file.FileName); + var _doc = await _context.Documents.AsQueryable() + .FirstOrDefaultAsync(x => x.Id == doc.Id); + if (_doc != null) + { + var retirementResignDoc = new RetirementResignDoc + { + RetirementResign = retirementResign, + Document = _doc, + CreatedFullName = FullName ?? "System Administrator", + CreatedUserId = UserId ?? "", + CreatedAt = DateTime.Now, + LastUpdateFullName = FullName ?? "System Administrator", + LastUpdateUserId = UserId ?? "", + LastUpdatedAt = DateTime.Now, + }; + await _context.RetirementResignDocs.AddAsync(retirementResignDoc); + } + } + } + await _context.SaveChangesAsync(); + return Success(retirementResign); + } + /// /// แก้ไขการลาออก /// diff --git a/BMA.EHR.Retirement.Service/Controllers/RetirementResignEmployeeController.cs b/BMA.EHR.Retirement.Service/Controllers/RetirementResignEmployeeController.cs index bfd8ab9f..8bc6c975 100644 --- a/BMA.EHR.Retirement.Service/Controllers/RetirementResignEmployeeController.cs +++ b/BMA.EHR.Retirement.Service/Controllers/RetirementResignEmployeeController.cs @@ -1291,6 +1291,148 @@ namespace BMA.EHR.Retirement.Service.Controllers return Success(retirementResignEmployee); } + /// + /// สร้างการลาออก โดย admin + /// + /// + /// + /// ค่าตัวแปรที่ส่งมาไม่ถูกต้อง + /// ไม่ได้ Login เข้าระบบ + /// เมื่อเกิดข้อผิดพลาดในการทำงาน + [HttpPost("admin")] + public async Task> PostAdmin([FromForm] RetirementResignEmployeeAdminRequest req) + { + var Remark = req.Remark; + if (req.Reason != null) + { + switch (req.Reason.Trim().ToUpper()) + { + case "CAREER": Remark = $"ประกอบอาชีพอื่น" + (req.Remark == null || req.Remark == "" ? $"{req.Remark}" : ""); break; + case "MOVE": Remark = $"รับราชการสังกัดอื่น" + (req.Remark == null || req.Remark == "" ? $"{req.Remark}" : ""); break; + case "FAMILY": Remark = $"ดูแลบิดามารดา" + (req.Remark == null || req.Remark == "" ? $"{req.Remark}" : ""); break; + case "EDUCATION": Remark = $"ศึกษาต่อ" + (req.Remark == null || req.Remark == "" ? $"{req.Remark}" : ""); break; + case "OTHER": Remark = $"อื่น ๆ" + (req.Remark == null || req.Remark == "" ? $"{req.Remark}" : ""); break; + } + } + var retirementResignEmployee = new RetirementResignEmployee + { + ApproveStep = "st1", + Location = req.Location, + SendDate = DateTime.Now, + ActiveDate = req.ActiveDate, + Reason = req.Reason, + ReasonResign = Remark, + Remark = req.Remark, + Status = "APPROVE", + IsActive = true, + CreatedFullName = FullName ?? "System Administrator", + CreatedUserId = UserId ?? "", + CreatedAt = DateTime.Now, + LastUpdateFullName = FullName ?? "System Administrator", + LastUpdateUserId = UserId ?? "", + LastUpdatedAt = DateTime.Now, + }; + + var apiUrl = $"{_configuration["API"]}/org/profile-employee/profileid/position/{req.ProfileId}"; + using (var client = new HttpClient()) + { + client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token.Replace("Bearer ", "")); + client.DefaultRequestHeaders.Add("api-key", _configuration["API_KEY"]); + var _req = new HttpRequestMessage(HttpMethod.Get, apiUrl); + var _res = await client.SendAsync(_req); + var _result = await _res.Content.ReadAsStringAsync(); + + var org = JsonConvert.DeserializeObject(_result); + + if (org == null || org.result == null) + return Error("ไม่พบหน่วยงานของผู้ใช้งานคนนี้", 404); + + retirementResignEmployee.profileId = org.result.profileId; + retirementResignEmployee.prefix = org.result.prefix; + retirementResignEmployee.firstName = org.result.firstName; + retirementResignEmployee.lastName = org.result.lastName; + retirementResignEmployee.citizenId = org.result.citizenId; + retirementResignEmployee.rootOld = org.result.root; + retirementResignEmployee.rootOldId = org.result.rootId; + retirementResignEmployee.rootDnaOldId = org.result.rootDnaId; + retirementResignEmployee.rootShortNameOld = org.result.rootShortName; + retirementResignEmployee.child1Old = org.result.child1; + retirementResignEmployee.child1OldId = org.result.child1Id; + retirementResignEmployee.child1DnaOldId = org.result.child1DnaId; + retirementResignEmployee.child1ShortNameOld = org.result.child1ShortName; + retirementResignEmployee.child2Old = org.result.child2; + retirementResignEmployee.child2OldId = org.result.child2Id; + retirementResignEmployee.child2DnaOldId = org.result.child2DnaId; + retirementResignEmployee.child2ShortNameOld = org.result.child2ShortName; + retirementResignEmployee.child3Old = org.result.child3; + retirementResignEmployee.child3OldId = org.result.child3Id; + retirementResignEmployee.child3DnaOldId = org.result.child3DnaId; + retirementResignEmployee.child3ShortNameOld = org.result.child3ShortName; + retirementResignEmployee.child4Old = org.result.child4; + retirementResignEmployee.child4OldId = org.result.child4Id; + retirementResignEmployee.child4DnaOldId = org.result.child4DnaId; + retirementResignEmployee.child4ShortNameOld = org.result.child4ShortName; + retirementResignEmployee.posMasterNoOld = org.result.posMasterNo; + retirementResignEmployee.posTypeOldId = org.result.posTypeId; + retirementResignEmployee.posTypeNameOld = org.result.posTypeName; + retirementResignEmployee.posLevelOldId = org.result.posLevelId; + retirementResignEmployee.posLevelNameOld = org.result.posLevelName; + retirementResignEmployee.AmountOld = org.result.salary; + retirementResignEmployee.PositionOld = org.result.position; + retirementResignEmployee.PositionLevelOld = org.result.posLevelName; + retirementResignEmployee.PositionTypeOld = org.result.posTypeName; + retirementResignEmployee.PositionNumberOld = org.result.nodeShortName + " " + org.result.posMasterNo; + retirementResignEmployee.OrganizationOld = (org.result.child4 == null ? "" : org.result.child4 + "\n") + + (org.result.child3 == null ? "" : org.result.child3 + "\n") + + (org.result.child2 == null ? "" : org.result.child2 + "\n") + + (org.result.child1 == null ? "" : org.result.child1 + "\n") + + (org.result.root == null ? "" : org.result.root); + retirementResignEmployee.OrganizationPositionOld = org.result.position + "\n" + retirementResignEmployee.OrganizationOld; + if ((retirementResignEmployee.posTypeNameOld == "ทั่วไป" && retirementResignEmployee.posLevelNameOld == "ชำนาญงาน") || (retirementResignEmployee.posTypeNameOld == "ทั่วไป" && retirementResignEmployee.posLevelNameOld == "ปฏิบัติงาน") || (retirementResignEmployee.posTypeNameOld == "วิชาการ" && retirementResignEmployee.posLevelNameOld == "ปฏิบัติการ") || (retirementResignEmployee.posTypeNameOld == "วิชาการ" && retirementResignEmployee.posLevelNameOld == "ชำนาญการ")) + { + retirementResignEmployee.Group = "1.1"; + } + else if ((retirementResignEmployee.posTypeNameOld == "ทั่วไป" && retirementResignEmployee.posLevelNameOld == "อาวุโส") || (retirementResignEmployee.posTypeNameOld == "วิชาการ" && retirementResignEmployee.posLevelNameOld == "ชำนาญการพิเศษ") || (retirementResignEmployee.posTypeNameOld == "อำนวยการ" && retirementResignEmployee.posLevelNameOld == "ต้น")) + { + retirementResignEmployee.Group = "1.2"; + } + else if ((retirementResignEmployee.posTypeNameOld == "ทั่วไป" && retirementResignEmployee.posLevelNameOld == "ทักษะพิเศษ") || (retirementResignEmployee.posTypeNameOld == "วิชาการ" && retirementResignEmployee.posLevelNameOld == "เชี่ยวชาญ") || (retirementResignEmployee.posTypeNameOld == "วิชาการ" && retirementResignEmployee.posLevelNameOld == "ทรงคุณวุฒิ") || (retirementResignEmployee.posTypeNameOld == "อำนวยการ" && retirementResignEmployee.posLevelNameOld == "สูง") || (retirementResignEmployee.posTypeNameOld == "บริหาร" && retirementResignEmployee.posLevelNameOld == "ต้น") || (retirementResignEmployee.posTypeNameOld == "บริหาร" && retirementResignEmployee.posLevelNameOld == "สูง")) + { + retirementResignEmployee.Group = "2"; + } + } + await _context.RetirementResignEmployees.AddAsync(retirementResignEmployee); + await _context.SaveChangesAsync(); + if (Request.Form.Files != null && Request.Form.Files.Count != 0) + { + foreach (var file in Request.Form.Files) + { + var fileExtension = Path.GetExtension(file.FileName); + + var doc = await _documentService.UploadFileAsync(file, file.FileName); + var _doc = await _context.Documents.AsQueryable() + .FirstOrDefaultAsync(x => x.Id == doc.Id); + if (_doc != null) + { + var retirementResignEmployeeDoc = new RetirementResignEmployeeDoc + { + RetirementResignEmployee = retirementResignEmployee, + Document = _doc, + CreatedFullName = FullName ?? "System Administrator", + CreatedUserId = UserId ?? "", + CreatedAt = DateTime.Now, + LastUpdateFullName = FullName ?? "System Administrator", + LastUpdateUserId = UserId ?? "", + LastUpdatedAt = DateTime.Now, + }; + await _context.RetirementResignEmployeeDocs.AddAsync(retirementResignEmployeeDoc); + } + } + } + await _context.SaveChangesAsync(); + return Success(retirementResignEmployee); + } + /// /// แก้ไขการลาออก /// diff --git a/BMA.EHR.Retirement.Service/Requests/RetirementResignEmployeeRequest.cs b/BMA.EHR.Retirement.Service/Requests/RetirementResignEmployeeRequest.cs index 409bc185..dd883d0d 100644 --- a/BMA.EHR.Retirement.Service/Requests/RetirementResignEmployeeRequest.cs +++ b/BMA.EHR.Retirement.Service/Requests/RetirementResignEmployeeRequest.cs @@ -19,4 +19,21 @@ namespace BMA.EHR.Retirement.Service.Requests public double? AmountOld { get; set; } public List? File { get; set; } } + + public class RetirementResignEmployeeAdminRequest + { + public string ProfileId { get; set; } + public string? Location { get; set; } + public DateTime? ActiveDate { get; set; } + public string? Reason { get; set; } + public string? Remark { get; set; } + public string? ReasonResign { get; set; } + public string? OrganizationPositionOld { get; set; } + public string? PositionTypeOld { get; set; } + public string? PositionLevelOld { get; set; } + public string? PositionNumberOld { get; set; } + public string? RemarkHorizontal { get; set; } + public double? AmountOld { get; set; } + public List? File { get; set; } + } } diff --git a/BMA.EHR.Retirement.Service/Requests/RetirementResignRequest.cs b/BMA.EHR.Retirement.Service/Requests/RetirementResignRequest.cs index 7f1bf500..acef05aa 100644 --- a/BMA.EHR.Retirement.Service/Requests/RetirementResignRequest.cs +++ b/BMA.EHR.Retirement.Service/Requests/RetirementResignRequest.cs @@ -20,4 +20,21 @@ namespace BMA.EHR.Retirement.Service.Requests public double? AmountOld { get; set; } public List? File { get; set; } } + public class RetirementResignAdminRequest + { + public string ProfileId { get; set; } + public string? Location { get; set; } + public DateTime? ActiveDate { get; set; } + public string? Reason { get; set; } + public string? Remark { get; set; } + public string? ReasonResign { get; set; } + public string? OrganizationPositionOld { get; set; } + public string? PositionExecutiveOld { get; set; } + public string? PositionTypeOld { get; set; } + public string? PositionLevelOld { get; set; } + public string? PositionNumberOld { get; set; } + public string? RemarkHorizontal { get; set; } + public double? AmountOld { get; set; } + public List? File { get; set; } + } } From 83a915f92c08ea31c49a14eb8be21fa70edc2fa5 Mon Sep 17 00:00:00 2001 From: Suphonchai Phoonsawat Date: Fri, 16 Jan 2026 16:01:07 +0700 Subject: [PATCH 040/183] refactor LeaveReportController to sort employees by check-in time and remark #2193 --- BMA.EHR.Leave/Controllers/LeaveReportController.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/BMA.EHR.Leave/Controllers/LeaveReportController.cs b/BMA.EHR.Leave/Controllers/LeaveReportController.cs index 6a09e2b9..a86059fb 100644 --- a/BMA.EHR.Leave/Controllers/LeaveReportController.cs +++ b/BMA.EHR.Leave/Controllers/LeaveReportController.cs @@ -2251,7 +2251,8 @@ namespace BMA.EHR.Leave.Service.Controllers } //employees = employees.OrderBy(x => x.checkInDate).ThenBy(x => x.checkInTimeRaw ?? DateTime.MaxValue).ThenBy(x => x.checkOutTimeRaw ?? DateTime.MaxValue).ToList(); employees = employees - .OrderBy(x => x.remark.Trim() == "" ? 0 : 1) // ข้อมูลที่ไม่มี remark ให้ขึ้นก่อน + .OrderBy(x => x.checkInTime.Trim() == "" ? 1 : 0) // เรียงตามวันที่ลงเวลา + .ThenBy(x => x.remark.Trim() == "" ? 0 : 1) // ข้อมูลที่ไม่มี remark ให้ขึ้นก่อน .ThenBy(x => x.checkInTimeRaw ?? DateTime.MaxValue).ThenBy(x => x.checkOutTimeRaw ?? DateTime.MaxValue) .ThenBy(x => x.remark) // จากนั้นจัดเรียงตาม remark .ToList(); From 510f1cd78ab3cc99696c4aed3fcec5ef916d4c2b Mon Sep 17 00:00:00 2001 From: Suphonchai Phoonsawat Date: Fri, 16 Jan 2026 16:09:23 +0700 Subject: [PATCH 041/183] fix sort order condition --- BMA.EHR.Leave/Controllers/LeaveReportController.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BMA.EHR.Leave/Controllers/LeaveReportController.cs b/BMA.EHR.Leave/Controllers/LeaveReportController.cs index a86059fb..8625497b 100644 --- a/BMA.EHR.Leave/Controllers/LeaveReportController.cs +++ b/BMA.EHR.Leave/Controllers/LeaveReportController.cs @@ -2251,7 +2251,7 @@ namespace BMA.EHR.Leave.Service.Controllers } //employees = employees.OrderBy(x => x.checkInDate).ThenBy(x => x.checkInTimeRaw ?? DateTime.MaxValue).ThenBy(x => x.checkOutTimeRaw ?? DateTime.MaxValue).ToList(); employees = employees - .OrderBy(x => x.checkInTime.Trim() == "" ? 1 : 0) // เรียงตามวันที่ลงเวลา + .OrderBy(x => x.checkInTime.Trim() != "" ? 0 : 1) // เรียงตามวันที่ลงเวลา .ThenBy(x => x.remark.Trim() == "" ? 0 : 1) // ข้อมูลที่ไม่มี remark ให้ขึ้นก่อน .ThenBy(x => x.checkInTimeRaw ?? DateTime.MaxValue).ThenBy(x => x.checkOutTimeRaw ?? DateTime.MaxValue) .ThenBy(x => x.remark) // จากนั้นจัดเรียงตาม remark From b0715e3da6a7cf972dd28bb87ed308884a26c28d Mon Sep 17 00:00:00 2001 From: Suphonchai Phoonsawat Date: Fri, 16 Jan 2026 16:19:49 +0700 Subject: [PATCH 042/183] refactor LeaveController to update check-out status logic based on morning end time #2187 --- BMA.EHR.Leave/Controllers/LeaveController.cs | 24 ++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/BMA.EHR.Leave/Controllers/LeaveController.cs b/BMA.EHR.Leave/Controllers/LeaveController.cs index 26e3372d..b4206777 100644 --- a/BMA.EHR.Leave/Controllers/LeaveController.cs +++ b/BMA.EHR.Leave/Controllers/LeaveController.cs @@ -989,13 +989,21 @@ namespace BMA.EHR.Leave.Service.Controllers else endTime = duty.EndTimeAfternoon; + var endTimeMorning = duty.EndTimeMorning; + string checkOutStatus = "NORMAL"; var leaveReq = await _leaveRequestRepository.GetLeavePeriodAsync(userId, currentDate.Date); if (leaveReq != null) { var leaveRange = leaveReq.LeaveRangeEnd == null ? "" : leaveReq.LeaveRangeEnd.ToUpper(); if (leaveRange == "AFTERNOON" || leaveRange == "ALL") - checkOutStatus = "NORMAL"; + { + if(DateTime.Parse(currentDate.ToString("yyyy-MM-dd HH:mm")) < + DateTime.Parse($"{currentDate.ToString("yyyy-MM-dd")} {endTimeMorning}")) + checkOutStatus = "ABSENT"; + else + checkOutStatus = "NORMAL"; + } else { // fix issue : SIT ระบบบันทึกเวลาปฏิบัติงาน>>ลงเวลาเข้า-ออกงาน (กรณีลงเวลาออกอีกวัน) #921 @@ -2438,6 +2446,9 @@ namespace BMA.EHR.Leave.Service.Controllers var endTime = isSeminar.Trim().ToUpper() == "Y" ? DateTime.Parse($"{DateTime.Now.Date.ToString("yyyy-MM-dd")} 14:30") : DateTime.Parse($"{DateTime.Now.Date.ToString("yyyy-MM-dd")} {duty.EndTimeAfternoon}"); + + var endTimeMorning = DateTime.Parse($"{DateTime.Now.Date.ToString("yyyy-MM-dd")} {duty.EndTimeMorning}"); + var status = string.Empty; if(lastCheckIn == null) { @@ -2458,7 +2469,16 @@ namespace BMA.EHR.Leave.Service.Controllers { var leaveRange = leaveReq.LeaveRangeEnd == null ? "" : leaveReq.LeaveRangeEnd.ToUpper(); if (leaveRange == "AFTERNOON" || leaveRange == "ALL") - status = "NORMAL"; + { + if(time < endTimeMorning) + { + status = "ABSENT"; + } + else + { + status = "NORMAL"; + } + } else { status = "ABSENT"; From b5c82f42431912fa7facc5cd0e0da78e779e1c34 Mon Sep 17 00:00:00 2001 From: harid Date: Mon, 19 Jan 2026 12:13:45 +0700 Subject: [PATCH 043/183] =?UTF-8?q?=E0=B8=9B=E0=B8=A3=E0=B8=B1=E0=B8=9A?= =?UTF-8?q?=E0=B8=A3=E0=B8=B2=E0=B8=A2=E0=B8=87=E0=B8=B2=E0=B8=99=E0=B8=A5?= =?UTF-8?q?=E0=B8=B2=20#2195?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Controllers/LeaveReportController.cs | 283 +++++++++++++++--- .../Controllers/LeaveRequestController.cs | 11 +- 2 files changed, 258 insertions(+), 36 deletions(-) diff --git a/BMA.EHR.Leave/Controllers/LeaveReportController.cs b/BMA.EHR.Leave/Controllers/LeaveReportController.cs index 8625497b..0dec80c6 100644 --- a/BMA.EHR.Leave/Controllers/LeaveReportController.cs +++ b/BMA.EHR.Leave/Controllers/LeaveReportController.cs @@ -165,9 +165,28 @@ namespace BMA.EHR.Leave.Service.Controllers leaveSubTypeName = data.LeaveSubTypeName != null ? data.LeaveSubTypeName.ToThaiNumber() : "", dear = data.CommanderPosition == null ? data.Dear : data.CommanderPosition.ToThaiNumber(), fullname = fullName, - positionName = profile!.Position == null ? "-" : profile!.Position.ToThaiNumber(), + position = string.IsNullOrEmpty(profile.Position) ? "-" : profile.Position, + positionName = (!string.IsNullOrEmpty(profile.PositionLeaveName) && + (profile.PositionLeaveName.Contains("อำนวยการ") || profile.PositionLeaveName.Contains("บริหาร")) + ? string.IsNullOrEmpty(profile.PosExecutiveName) + ? string.IsNullOrEmpty(profile.Position) + ? "-" + : profile.Position + : profile.PosExecutiveName + : string.IsNullOrEmpty(profile.Position) + ? "-" + : string.IsNullOrEmpty(profile.PositionLeaveName) + ? profile.Position + : $"{profile.Position}{profile.PositionLeaveName}").ToThaiNumber(), positionLeaveName = profile!.PositionLeaveName == null ? "-" : profile!.PositionLeaveName.ToThaiNumber(), + posExecutiveName = profile.PosExecutiveName, organizationName = profile!.Oc!.ToThaiNumber(), + posExOrg = !string.IsNullOrEmpty(profile.PositionLeaveName) && + (profile.PositionLeaveName.Contains("อำนวยการ") || profile.PositionLeaveName.Contains("บริหาร")) + ? new[] { (profile!.Oc!.ToThaiNumber()) } + : !string.IsNullOrEmpty(profile.PosExecutiveName) + ? new[] { (profile.PosExecutiveName.ToThaiNumber()), (profile!.Oc!.ToThaiNumber()) } + : new[] { (profile!.Oc!.ToThaiNumber()) }, leaveDetail = data.LeaveDetail.ToThaiNumber(), leaveDateStart = data.LeaveStartDate.Date.ToThaiShortDate().ToThaiNumber(), leaveDateEnd = data.LeaveEndDate.Date.ToThaiShortDate().ToThaiNumber(), @@ -228,9 +247,28 @@ namespace BMA.EHR.Leave.Service.Controllers leaveSubTypeName = data.LeaveSubTypeName != null ? data.LeaveSubTypeName.ToThaiNumber() : "", dear = data.CommanderPosition == null ? data.Dear : data.CommanderPosition.ToThaiNumber(), fullname = fullName, - positionName = profile!.Position == null ? "-" : profile!.Position.ToThaiNumber(), + position = string.IsNullOrEmpty(profile.Position) ? "-" : profile.Position, + positionName = (!string.IsNullOrEmpty(profile.PositionLeaveName) && + (profile.PositionLeaveName.Contains("อำนวยการ") || profile.PositionLeaveName.Contains("บริหาร")) + ? string.IsNullOrEmpty(profile.PosExecutiveName) + ? string.IsNullOrEmpty(profile.Position) + ? "-" + : profile.Position + : profile.PosExecutiveName + : string.IsNullOrEmpty(profile.Position) + ? "-" + : string.IsNullOrEmpty(profile.PositionLeaveName) + ? profile.Position + : $"{profile.Position}{profile.PositionLeaveName}").ToThaiNumber(), positionLeaveName = profile!.PositionLeaveName == null ? "-" : profile!.PositionLeaveName.ToThaiNumber(), - organizationName = profile!.Oc!.ToThaiNumber() ?? "", + posExecutiveName = profile.PosExecutiveName, + organizationName = profile!.Oc!.ToThaiNumber(), + posExOrg = !string.IsNullOrEmpty(profile.PositionLeaveName) && + (profile.PositionLeaveName.Contains("อำนวยการ") || profile.PositionLeaveName.Contains("บริหาร")) + ? new[] { (profile!.Oc!.ToThaiNumber()) } + : !string.IsNullOrEmpty(profile.PosExecutiveName) + ? new[] { (profile.PosExecutiveName.ToThaiNumber()), (profile!.Oc!.ToThaiNumber()) } + : new[] { (profile!.Oc!.ToThaiNumber()) }, wifeDayName = data.WifeDayName ?? "", wifeDayDateBorn = data.WifeDayDateBorn == null || data.WifeDayDateBorn == "" ? "" : DateTime.Parse(data.WifeDayDateBorn).ToThaiShortDate().ToThaiNumber(), leaveDateStart = data.LeaveStartDate.Date.ToThaiShortDate().ToThaiNumber(), @@ -289,9 +327,28 @@ namespace BMA.EHR.Leave.Service.Controllers leaveSubTypeName = data.LeaveSubTypeName != null ? data.LeaveSubTypeName.ToThaiNumber() : "", dear = data.CommanderPosition == null ? data.Dear : data.CommanderPosition.ToThaiNumber(), fullname = fullName, - positionName = profile!.Position == null ? "-" : profile!.Position.ToThaiNumber(), + position = string.IsNullOrEmpty(profile.Position) ? "-" : profile.Position, + positionName = (!string.IsNullOrEmpty(profile.PositionLeaveName) && + (profile.PositionLeaveName.Contains("อำนวยการ") || profile.PositionLeaveName.Contains("บริหาร")) + ? string.IsNullOrEmpty(profile.PosExecutiveName) + ? string.IsNullOrEmpty(profile.Position) + ? "-" + : profile.Position + : profile.PosExecutiveName + : string.IsNullOrEmpty(profile.Position) + ? "-" + : string.IsNullOrEmpty(profile.PositionLeaveName) + ? profile.Position + : $"{profile.Position}{profile.PositionLeaveName}").ToThaiNumber(), positionLeaveName = profile!.PositionLeaveName == null ? "-" : profile!.PositionLeaveName.ToThaiNumber(), - organizationName = profile!.Oc!.ToThaiNumber() ?? "", + posExecutiveName = profile.PosExecutiveName, + organizationName = profile!.Oc!.ToThaiNumber(), + posExOrg = !string.IsNullOrEmpty(profile.PositionLeaveName) && + (profile.PositionLeaveName.Contains("อำนวยการ") || profile.PositionLeaveName.Contains("บริหาร")) + ? new[] { (profile!.Oc!.ToThaiNumber()) } + : !string.IsNullOrEmpty(profile.PosExecutiveName) + ? new[] { (profile.PosExecutiveName.ToThaiNumber()), (profile!.Oc!.ToThaiNumber()) } + : new[] { (profile!.Oc!.ToThaiNumber()) }, restDayOldTotal = extendLeave.ToString().ToThaiNumber(), restDayCurrentTotal = (10).ToString().ToThaiNumber(), @@ -357,9 +414,28 @@ namespace BMA.EHR.Leave.Service.Controllers leaveSubTypeName = data.LeaveSubTypeName != null ? data.LeaveSubTypeName.ToThaiNumber() : "", dear = data.CommanderPosition == null ? data.Dear : data.CommanderPosition.ToThaiNumber(), fullname = fullName, - positionName = profile!.Position == null ? "-" : profile!.Position.ToThaiNumber(), + position = string.IsNullOrEmpty(profile.Position) ? "-" : profile.Position, + positionName = (!string.IsNullOrEmpty(profile.PositionLeaveName) && + (profile.PositionLeaveName.Contains("อำนวยการ") || profile.PositionLeaveName.Contains("บริหาร")) + ? string.IsNullOrEmpty(profile.PosExecutiveName) + ? string.IsNullOrEmpty(profile.Position) + ? "-" + : profile.Position + : profile.PosExecutiveName + : string.IsNullOrEmpty(profile.Position) + ? "-" + : string.IsNullOrEmpty(profile.PositionLeaveName) + ? profile.Position + : $"{profile.Position}{profile.PositionLeaveName}").ToThaiNumber(), positionLeaveName = profile!.PositionLeaveName == null ? "-" : profile!.PositionLeaveName.ToThaiNumber(), - organizationName = profile!.Oc!.ToThaiNumber() ?? "", + posExecutiveName = profile.PosExecutiveName, + organizationName = profile!.Oc!.ToThaiNumber(), + posExOrg = !string.IsNullOrEmpty(profile.PositionLeaveName) && + (profile.PositionLeaveName.Contains("อำนวยการ") || profile.PositionLeaveName.Contains("บริหาร")) + ? new[] { (profile!.Oc!.ToThaiNumber()) } + : !string.IsNullOrEmpty(profile.PosExecutiveName) + ? new[] { (profile.PosExecutiveName.ToThaiNumber()), (profile!.Oc!.ToThaiNumber()) } + : new[] { (profile!.Oc!.ToThaiNumber()) }, leavegovernmentDate = data.LeaveGovernmentDate == null ? "" : data.LeaveGovernmentDate.Value.Date.ToThaiShortDate().ToThaiNumber(), @@ -393,9 +469,28 @@ namespace BMA.EHR.Leave.Service.Controllers leaveSubTypeName = data.LeaveSubTypeName != null ? data.LeaveSubTypeName.ToThaiNumber() : "", dear = data.CommanderPosition == null ? data.Dear : data.CommanderPosition.ToThaiNumber(), fullname = fullName, - positionName = profile!.Position == null ? "-" : profile!.Position.ToThaiNumber(), + position = string.IsNullOrEmpty(profile.Position) ? "-" : profile.Position, + positionName = (!string.IsNullOrEmpty(profile.PositionLeaveName) && + (profile.PositionLeaveName.Contains("อำนวยการ") || profile.PositionLeaveName.Contains("บริหาร")) + ? string.IsNullOrEmpty(profile.PosExecutiveName) + ? string.IsNullOrEmpty(profile.Position) + ? "-" + : profile.Position + : profile.PosExecutiveName + : string.IsNullOrEmpty(profile.Position) + ? "-" + : string.IsNullOrEmpty(profile.PositionLeaveName) + ? profile.Position + : $"{profile.Position}{profile.PositionLeaveName}").ToThaiNumber(), positionLeaveName = profile!.PositionLeaveName == null ? "-" : profile!.PositionLeaveName.ToThaiNumber(), - organizationName = profile!.Oc!.ToThaiNumber() ?? "", + posExecutiveName = profile.PosExecutiveName, + organizationName = profile!.Oc!.ToThaiNumber(), + posExOrg = !string.IsNullOrEmpty(profile.PositionLeaveName) && + (profile.PositionLeaveName.Contains("อำนวยการ") || profile.PositionLeaveName.Contains("บริหาร")) + ? new[] { (profile!.Oc!.ToThaiNumber()) } + : !string.IsNullOrEmpty(profile.PosExecutiveName) + ? new[] { (profile.PosExecutiveName.ToThaiNumber()), (profile!.Oc!.ToThaiNumber()) } + : new[] { (profile!.Oc!.ToThaiNumber()) }, leavebirthDate = data.LeaveBirthDate == null ? "" : data.LeaveBirthDate.Value.Date.ToThaiShortDate().ToThaiNumber(), leavegovernmentDate = data.LeaveGovernmentDate == null ? "" : data.LeaveGovernmentDate.Value.Date.ToThaiShortDate().ToThaiNumber(), @@ -454,9 +549,28 @@ namespace BMA.EHR.Leave.Service.Controllers leaveSubTypeName = data.LeaveSubTypeName != null ? data.LeaveSubTypeName.ToThaiNumber() : "", dear = data.CommanderPosition == null ? data.Dear : data.CommanderPosition.ToThaiNumber(), fullname = fullName, - positionName = profile!.Position == null ? "-" : profile!.Position.ToThaiNumber(), + position = string.IsNullOrEmpty(profile.Position) ? "-" : profile.Position, + positionName = (!string.IsNullOrEmpty(profile.PositionLeaveName) && + (profile.PositionLeaveName.Contains("อำนวยการ") || profile.PositionLeaveName.Contains("บริหาร")) + ? string.IsNullOrEmpty(profile.PosExecutiveName) + ? string.IsNullOrEmpty(profile.Position) + ? "-" + : profile.Position + : profile.PosExecutiveName + : string.IsNullOrEmpty(profile.Position) + ? "-" + : string.IsNullOrEmpty(profile.PositionLeaveName) + ? profile.Position + : $"{profile.Position}{profile.PositionLeaveName}").ToThaiNumber(), positionLeaveName = profile!.PositionLeaveName == null ? "-" : profile!.PositionLeaveName.ToThaiNumber(), - organizationName = profile!.Oc!.ToThaiNumber() ?? "", + posExecutiveName = profile.PosExecutiveName, + organizationName = profile!.Oc!.ToThaiNumber(), + posExOrg = !string.IsNullOrEmpty(profile.PositionLeaveName) && + (profile.PositionLeaveName.Contains("อำนวยการ") || profile.PositionLeaveName.Contains("บริหาร")) + ? new[] { (profile!.Oc!.ToThaiNumber()) } + : !string.IsNullOrEmpty(profile.PosExecutiveName) + ? new[] { (profile.PosExecutiveName.ToThaiNumber()), (profile!.Oc!.ToThaiNumber()) } + : new[] { (profile!.Oc!.ToThaiNumber()) }, absentDaySummon = data.AbsentDaySummon.ToThaiNumber(), absentDayLocation = data.AbsentDayLocation.ToThaiNumber(), @@ -509,9 +623,28 @@ namespace BMA.EHR.Leave.Service.Controllers leaveSubTypeName = data.LeaveSubTypeName != null ? data.LeaveSubTypeName.ToThaiNumber() : "", dear = data.CommanderPosition == null ? data.Dear : data.CommanderPosition.ToThaiNumber(), fullname = fullName, - positionName = profile!.Position == null ? "-" : profile!.Position.ToThaiNumber(), + position = string.IsNullOrEmpty(profile.Position) ? "-" : profile.Position, + positionName = (!string.IsNullOrEmpty(profile.PositionLeaveName) && + (profile.PositionLeaveName.Contains("อำนวยการ") || profile.PositionLeaveName.Contains("บริหาร")) + ? string.IsNullOrEmpty(profile.PosExecutiveName) + ? string.IsNullOrEmpty(profile.Position) + ? "-" + : profile.Position + : profile.PosExecutiveName + : string.IsNullOrEmpty(profile.Position) + ? "-" + : string.IsNullOrEmpty(profile.PositionLeaveName) + ? profile.Position + : $"{profile.Position}{profile.PositionLeaveName}").ToThaiNumber(), positionLeaveName = profile!.PositionLeaveName == null ? "-" : profile!.PositionLeaveName.ToThaiNumber(), - organizationName = profile!.Oc!.ToThaiNumber() ?? "", + posExecutiveName = profile.PosExecutiveName, + organizationName = profile!.Oc!.ToThaiNumber(), + posExOrg = !string.IsNullOrEmpty(profile.PositionLeaveName) && + (profile.PositionLeaveName.Contains("อำนวยการ") || profile.PositionLeaveName.Contains("บริหาร")) + ? new[] { (profile!.Oc!.ToThaiNumber()) } + : !string.IsNullOrEmpty(profile.PosExecutiveName) + ? new[] { (profile.PosExecutiveName.ToThaiNumber()), (profile!.Oc!.ToThaiNumber()) } + : new[] { (profile!.Oc!.ToThaiNumber()) }, leavebirthDate = data.LeaveBirthDate == null ? "" : data.LeaveBirthDate.Value.Date.ToThaiShortDate().ToThaiNumber(), leavegovernmentDate = data.LeaveGovernmentDate == null ? "" : data.LeaveGovernmentDate.Value.Date.ToThaiShortDate().ToThaiNumber(), @@ -587,9 +720,28 @@ namespace BMA.EHR.Leave.Service.Controllers dear = data.CommanderPosition == null ? data.Dear : data.CommanderPosition.ToThaiNumber(), fullname = fullName, fullnameEng = "", - positionName = profile!.Position == null ? "-" : profile!.Position.ToThaiNumber(), + position = string.IsNullOrEmpty(profile.Position) ? "-" : profile.Position, + positionName = (!string.IsNullOrEmpty(profile.PositionLeaveName) && + (profile.PositionLeaveName.Contains("อำนวยการ") || profile.PositionLeaveName.Contains("บริหาร")) + ? string.IsNullOrEmpty(profile.PosExecutiveName) + ? string.IsNullOrEmpty(profile.Position) + ? "-" + : profile.Position + : profile.PosExecutiveName + : string.IsNullOrEmpty(profile.Position) + ? "-" + : string.IsNullOrEmpty(profile.PositionLeaveName) + ? profile.Position + : $"{profile.Position}{profile.PositionLeaveName}").ToThaiNumber(), positionLeaveName = profile!.PositionLeaveName == null ? "-" : profile!.PositionLeaveName.ToThaiNumber(), - organizationName = profile!.Oc!.ToThaiNumber() ?? "", + posExecutiveName = profile.PosExecutiveName, + organizationName = profile!.Oc!.ToThaiNumber(), + posExOrg = !string.IsNullOrEmpty(profile.PositionLeaveName) && + (profile.PositionLeaveName.Contains("อำนวยการ") || profile.PositionLeaveName.Contains("บริหาร")) + ? new[] { (profile!.Oc!.ToThaiNumber()) } + : !string.IsNullOrEmpty(profile.PosExecutiveName) + ? new[] { (profile.PosExecutiveName.ToThaiNumber()), (profile!.Oc!.ToThaiNumber()) } + : new[] { (profile!.Oc!.ToThaiNumber()) }, leaveDateStart = data.LeaveStartDate.Date.ToThaiShortDate().ToThaiNumber(), leaveDateEnd = data.LeaveEndDate.Date.ToThaiShortDate().ToThaiNumber(), @@ -679,9 +831,28 @@ namespace BMA.EHR.Leave.Service.Controllers leaveSubTypeName = data.LeaveSubTypeName != null ? data.LeaveSubTypeName.ToThaiNumber() : "", dear = data.CommanderPosition == null ? data.Dear : data.CommanderPosition.ToThaiNumber(), fullname = fullName, - positionName = profile!.Position == null ? "-" : profile!.Position.ToThaiNumber(), + position = string.IsNullOrEmpty(profile.Position) ? "-" : profile.Position, + positionName = (!string.IsNullOrEmpty(profile.PositionLeaveName) && + (profile.PositionLeaveName.Contains("อำนวยการ") || profile.PositionLeaveName.Contains("บริหาร")) + ? string.IsNullOrEmpty(profile.PosExecutiveName) + ? string.IsNullOrEmpty(profile.Position) + ? "-" + : profile.Position + : profile.PosExecutiveName + : string.IsNullOrEmpty(profile.Position) + ? "-" + : string.IsNullOrEmpty(profile.PositionLeaveName) + ? profile.Position + : $"{profile.Position}{profile.PositionLeaveName}").ToThaiNumber(), positionLeaveName = profile!.PositionLeaveName == null ? "-" : profile!.PositionLeaveName.ToThaiNumber(), - organizationName = profile!.Oc!.ToThaiNumber() ?? "", + posExecutiveName = profile.PosExecutiveName, + organizationName = profile!.Oc!.ToThaiNumber(), + posExOrg = !string.IsNullOrEmpty(profile.PositionLeaveName) && + (profile.PositionLeaveName.Contains("อำนวยการ") || profile.PositionLeaveName.Contains("บริหาร")) + ? new[] { (profile!.Oc!.ToThaiNumber()) } + : !string.IsNullOrEmpty(profile.PosExecutiveName) + ? new[] { (profile.PosExecutiveName.ToThaiNumber()), (profile!.Oc!.ToThaiNumber()) } + : new[] { (profile!.Oc!.ToThaiNumber()) }, leaveSalary = data.LeaveSalary == null ? "" : data.LeaveSalary.Value.ToNumericText().ToThaiNumber(), leaveSalaryText = data.LeaveSalaryText.ToThaiNumber(), @@ -748,9 +919,28 @@ namespace BMA.EHR.Leave.Service.Controllers leaveSubTypeName = data.LeaveSubTypeName != null ? data.LeaveSubTypeName.ToThaiNumber() : "", dear = data.CommanderPosition == null ? data.Dear : data.CommanderPosition.ToThaiNumber(), fullname = fullName, - positionName = profile!.Position == null ? "-" : profile!.Position.ToThaiNumber(), + position = string.IsNullOrEmpty(profile.Position) ? "-" : profile.Position, + positionName = (!string.IsNullOrEmpty(profile.PositionLeaveName) && + (profile.PositionLeaveName.Contains("อำนวยการ") || profile.PositionLeaveName.Contains("บริหาร")) + ? string.IsNullOrEmpty(profile.PosExecutiveName) + ? string.IsNullOrEmpty(profile.Position) + ? "-" + : profile.Position + : profile.PosExecutiveName + : string.IsNullOrEmpty(profile.Position) + ? "-" + : string.IsNullOrEmpty(profile.PositionLeaveName) + ? profile.Position + : $"{profile.Position}{profile.PositionLeaveName}").ToThaiNumber(), positionLeaveName = profile!.PositionLeaveName == null ? "-" : profile!.PositionLeaveName.ToThaiNumber(), - organizationName = profile!.Oc!.ToThaiNumber() ?? "", + posExecutiveName = profile.PosExecutiveName, + organizationName = profile!.Oc!.ToThaiNumber(), + posExOrg = !string.IsNullOrEmpty(profile.PositionLeaveName) && + (profile.PositionLeaveName.Contains("อำนวยการ") || profile.PositionLeaveName.Contains("บริหาร")) + ? new[] { (profile!.Oc!.ToThaiNumber()) } + : !string.IsNullOrEmpty(profile.PosExecutiveName) + ? new[] { (profile.PosExecutiveName.ToThaiNumber()), (profile!.Oc!.ToThaiNumber()) } + : new[] { (profile!.Oc!.ToThaiNumber()) }, leaveDateStart = data.LeaveStartDate.Date.ToThaiShortDate().ToThaiNumber(), leaveDateEnd = data.LeaveEndDate.Date.ToThaiShortDate().ToThaiNumber(), @@ -2606,11 +2796,12 @@ namespace BMA.EHR.Leave.Service.Controllers { var _default = new { - fullName = "......................", - positionName = "......................", - positionSign = "......................", - updatedAt = "...... /...... /......", - comment = "......................", + fullName = "............................................", + positionName = "............................................", + posExOrg = Array.Empty(), + positionSign = "............................................", + updatedAt = "............/............/............", + comment = "......................................................................................................................................................................", approveType = "" }; @@ -2618,37 +2809,61 @@ namespace BMA.EHR.Leave.Service.Controllers .Select(x => new { fullName = $"{(x.Prefix ?? "")}{(x.FirstName ?? "")} {(x.LastName ?? "")}".Trim(), - positionName = x.PositionName ?? "......................", - positionSign = x.PositionSign ?? "......................", + positionName = (!string.IsNullOrEmpty(x.PositionLevelName) && + (x.PositionLevelName.Contains("อำนวยการ") || x.PositionLevelName.Contains("บริหาร")) + ? string.IsNullOrEmpty(x.PosExecutiveName) + ? string.IsNullOrEmpty(x.PositionName) + ? "............................................" + : x.PositionName + : x.PosExecutiveName + : string.IsNullOrEmpty(x.PositionName) + ? "............................................" + : string.IsNullOrEmpty(x.PositionLevelName) + ? x.PositionName + : $"{x.PositionName}{x.PositionLevelName}").ToThaiNumber(), + posExOrg = !string.IsNullOrEmpty(x.PositionLevelName) && + (x.PositionLevelName.Contains("อำนวยการ") || x.PositionLevelName.Contains("บริหาร")) + ? new[] { (x!.OrganizationName!.ToThaiNumber()) } + : !string.IsNullOrEmpty(x.PosExecutiveName) + ? new[] { (x.PosExecutiveName.ToThaiNumber()), (x!.OrganizationName!.ToThaiNumber()) } + : new[] { (x!.OrganizationName!.ToThaiNumber()) }, + positionSign = !string.IsNullOrEmpty(x.PositionSign) + ? x.PositionSign.Replace("\r", "").Replace("\n", " ") + : "............................................", updatedAt = x.LastUpdatedAt.HasValue ? x.LastUpdatedAt.Value.Date.ToThaiShortDate().ToThaiNumber() - : "...... /...... /......", - comment = !string.IsNullOrEmpty(x.Comment) - ? x.Comment.Replace("\r", "").Replace("\n", "").Trim() - : "......................", + : "............/............/............", + comment = !string.IsNullOrEmpty(x.Comment) + ? x.Comment.Replace("\r", "").Replace("\n", " ").Trim() + : "......................................................................................................................................................................", approveType = (x.ApproveType ?? "").Trim().ToUpper() }) .ToList(); + // การเจ้าหน้าที่ var sender = approvers .FirstOrDefault(x => x.approveType == "SENDER") ?? _default; - var approver = approvers - .FirstOrDefault(x => x.approveType == "APPROVER") - ?? _default; - + // ผู้บังคับบัญชา (มีได้มากกว่า 1 คน) var commanders = approvers .Where(x => x.approveType == "COMMANDER") .DefaultIfEmpty(_default) .ToList(); + // ผู้มีอำนาจ + var approver = approvers + .FirstOrDefault(x => x.approveType == "APPROVER") + ?? _default; + return new { + sign = "............................................", sender = sender, approver = approver, commanders = commanders }; + } } diff --git a/BMA.EHR.Leave/Controllers/LeaveRequestController.cs b/BMA.EHR.Leave/Controllers/LeaveRequestController.cs index 4db84d53..dc3afc62 100644 --- a/BMA.EHR.Leave/Controllers/LeaveRequestController.cs +++ b/BMA.EHR.Leave/Controllers/LeaveRequestController.cs @@ -166,7 +166,14 @@ namespace BMA.EHR.Leave.Service.Controllers ProfileId = r.ProfileId, KeycloakId = r.KeycloakId, ApproveStatus = "PENDING", - ApproveType = type.Trim().ToUpper() + ApproveType = type.Trim().ToUpper(), + + CreatedFullName = FullName ?? "", + CreatedUserId = UserId!, + CreatedAt = DateTime.Now, + LastUpdateFullName = FullName ?? "", + LastUpdateUserId = UserId!, + LastUpdatedAt = DateTime.Now, }); } @@ -2033,7 +2040,7 @@ namespace BMA.EHR.Leave.Service.Controllers Prefix = profile.Prefix ?? "", FirstName = profile.FirstName ?? "", LastName = profile.LastName ?? "", - PositionName = $"{profile.Position ?? ""}{profile.PositionLeaveName ?? ""}", + PositionName = $"{profile.Position ?? ""}", ProfileId = profile.Id, KeycloakId = Guid.Parse(UserId!), ApproveType = "SENDER", From 21f82d69e1f2d99b5f9436a99d1cc046c61a57cf Mon Sep 17 00:00:00 2001 From: Suphonchai Phoonsawat Date: Mon, 19 Jan 2026 14:27:43 +0700 Subject: [PATCH 044/183] Add load testing script for simulating 30,000 requests over 10 minutes --- BMA.EHR.Leave/Controllers/LeaveController.cs | 4 +- dotnet_keycloak_test.js | 54 ++++++++++++++++++++ 2 files changed, 57 insertions(+), 1 deletion(-) create mode 100644 dotnet_keycloak_test.js diff --git a/BMA.EHR.Leave/Controllers/LeaveController.cs b/BMA.EHR.Leave/Controllers/LeaveController.cs index b4206777..aa89fc6a 100644 --- a/BMA.EHR.Leave/Controllers/LeaveController.cs +++ b/BMA.EHR.Leave/Controllers/LeaveController.cs @@ -2448,6 +2448,7 @@ namespace BMA.EHR.Leave.Service.Controllers : DateTime.Parse($"{DateTime.Now.Date.ToString("yyyy-MM-dd")} {duty.EndTimeAfternoon}"); var endTimeMorning = DateTime.Parse($"{DateTime.Now.Date.ToString("yyyy-MM-dd")} {duty.EndTimeMorning}"); + var endTimeDisplay = endTime; var status = string.Empty; if(lastCheckIn == null) @@ -2473,6 +2474,7 @@ namespace BMA.EHR.Leave.Service.Controllers if(time < endTimeMorning) { status = "ABSENT"; + endTimeDisplay = endTimeMorning; } else { @@ -2502,7 +2504,7 @@ namespace BMA.EHR.Leave.Service.Controllers Status = status, StatusText = status == "ABSENT" ? "ขาดราชการ" : "ปกติ", ServerTime = time, - EndTime = endTime + EndTime = endTimeDisplay }); } diff --git a/dotnet_keycloak_test.js b/dotnet_keycloak_test.js new file mode 100644 index 00000000..d271087d --- /dev/null +++ b/dotnet_keycloak_test.js @@ -0,0 +1,54 @@ +// ทดสอบการยิง 30,000 requests ในเวลา 10 นาที โดยให้กระจายการยิงในเวลาที่ต่างๆ กัน + +import { check, sleep } from "k6"; +import http from "k6/http"; +import { Rate } from "k6/metrics"; + +export let errorRate = new Rate("errors"); + +// จำนวน request ที่ต้องการยิง + +// ระยะเวลาทดสอบทั้งหมด + +// จำนวน Virtual Users เฉลี่ยที่ต้องการ 300 users +//const averageVus = Math.ceil(totalRequests / totalDuration); +const averageVus = 300; + +export let options = { + stages: [ + { duration: "2m", target: averageVus * 0.5 }, // 20% ของการทดสอบ เพิ่ม VUs เป็น 50% ของค่าเฉลี่ย + { duration: "4m", target: averageVus }, // 40% ของการทดสอบ เพิ่ม VUs เป็น 100% ของค่าเฉลี่ย + { duration: "2m", target: averageVus * 1.5 }, // 20% ของการทดสอบ เพิ่ม VUs เป็น 150% ของค่าเฉลี่ย + { duration: "2m", target: 0 }, // ลด VUs ลงมาเป็น 0 + ], + thresholds: { + errors: ["rate<0.01"], // อัตรา error ต้องน้อยกว่า 1% + http_req_duration: ["p(95)<2000"], // 95% ของ requests ควรใช้เวลาไม่เกิน 2 วินาที + }, +}; + +export default function () { + // ตัวเลือก headers + let headers = { + "Content-Type": "application/json", + Authorization: + "Bearer eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJ4WTJWUi1FRnZ2TlBzTXMzOXU4b29WQldRTDZtUHdyTkpPaDNrb0pGVGdVIn0.eyJleHAiOjE3NzYyMTkxNjgsImlhdCI6MTc2ODQ0MzE2OCwianRpIjoiZDQxMmI5MWEtZmZhMi00N2JiLTliZDUtZDE5NTdmMDFjYzQyIiwiaXNzIjoiaHR0cHM6Ly9ocm1zLWlkLmJhbmdrb2suZ28udGgvcmVhbG1zL2hybXMiLCJhdWQiOiJhY2NvdW50Iiwic3ViIjoiYmFmYzU3OTUtYmVmYy00ZDNmLWE0NjEtMzUzM2MzOGE1ZmMxIiwidHlwIjoiQmVhcmVyIiwiYXpwIjoiZ2V0dG9rZW4tY2hlY2tpbiIsInNpZCI6IjBkNzdiY2Y5LTE4YWQtNGQyMS1hYjBjLTI4Y2ZiZjUyZGZiNCIsImFjciI6IjEiLCJhbGxvd2VkLW9yaWdpbnMiOlsiaHR0cHM6Ly9ocm1zLmJhbmdrb2suZ28udGgiLCJodHRwczovL2hybXMtY2hlY2tpbi5iYW5na29rLmdvLnRoIl0sInJlYWxtX2FjY2VzcyI6eyJyb2xlcyI6WyJTVVBFUl9BRE1JTiIsInN0b3JhZ2VfbWFuYWdlbWVudCIsIm9mZmxpbmVfYWNjZXNzIiwiU1RBRkYiLCJkZWZhdWx0LXJvbGVzLWhybXMiLCJ1bWFfYXV0aG9yaXphdGlvbiIsIkFETUlOIiwiVVNFUiJdfSwicmVzb3VyY2VfYWNjZXNzIjp7ImFjY291bnQiOnsicm9sZXMiOlsibWFuYWdlLWFjY291bnQiLCJtYW5hZ2UtYWNjb3VudC1saW5rcyIsInZpZXctcHJvZmlsZSJdfX0sInNjb3BlIjoiZW1haWwgb3BlbmlkIHByb2ZpbGUiLCJlbWFpbF92ZXJpZmllZCI6ZmFsc2UsInJvbGUiOlsiU1VQRVJfQURNSU4iLCJzdG9yYWdlX21hbmFnZW1lbnQiLCJvZmZsaW5lX2FjY2VzcyIsIlNUQUZGIiwiZGVmYXVsdC1yb2xlcy1ocm1zIiwidW1hX2F1dGhvcml6YXRpb24iLCJBRE1JTiIsIlVTRVIiXSwibmFtZSI6IuC4p-C4seC4meC5gOC4ieC4peC4tOC4oSDguInguLHguJXguKPguJfguK3guIciLCJwcmVmZXJyZWRfdXNlcm5hbWUiOiIzMTIwMjAwNDI0OTc1IiwiZ2l2ZW5fbmFtZSI6IuC4p-C4seC4meC5gOC4ieC4peC4tOC4oSIsImZhbWlseV9uYW1lIjoi4LiJ4Lix4LiV4Lij4LiX4Lit4LiHIn0.UhMn0NEkymPxMAcb4noZedHCSqXotCyD2RziBtLYHn5OhA9yk1915Rrt9iV4wVaebr74iZ2eZMpBwp8YVy8-3cPXSv9T3vzbXwFP7IeICPCDDf4bOPFEHP5FYow2s9v48qG81wnu01AG7_EL2-CQKh1sBVrCVUUlATlf-P4lT_lHeHOCKNXTmw4V0IWm96ec6pk-jFY3KH2JdRSWR7wq8g-KVxhLOxk_pF72kMwOpdvcr_99byg28zzj6QfeNYXLt61koHXnZppUqytt86mQQgfamv2FNVywCEzbRITUceu2rmJnwQE8ubeoCh4UOsYauUuSKd7RPqvvXxL_Vg__8Q", + //"Bearer eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJTT2wwWmFidm9rRzZET3pDZVBtT09Kek5haTdMUldkci1zV3lEYjRELTc0In0.eyJleHAiOjE3Njg4ODAzMjgsImlhdCI6MTc2ODc5MzkyOCwianRpIjoiMDYxODBlMWYtNTQzYy00MjU0LWFmN2QtYWI1NDA5NzFmNWY2IiwiaXNzIjoiaHR0cHM6Ly9ocm1zYmtrLWlkLmNhc2UtY29sbGVjdGlvbi5jb20vcmVhbG1zL2hybXMiLCJhdWQiOlsiYWNjb3VudCIsImdldHRva2VuIl0sInN1YiI6IjQzOWZhMzZkLTZiYzUtNGVmNS05NWFhLWVmMjllNjRkMmU5ZiIsInR5cCI6IkJlYXJlciIsImF6cCI6ImdldHRva2VuIiwic2lkIjoiZGI2YzUxNjItNzZhYS00MmVmLWI0ZDMtYThmOTk2N2NjZWM2IiwiYWNyIjoiMSIsImFsbG93ZWQtb3JpZ2lucyI6WyIqIl0sInJlYWxtX2FjY2VzcyI6eyJyb2xlcyI6WyJTVVBFUl9BRE1JTiIsIm9mZmxpbmVfYWNjZXNzIiwiU1RBRkYiLCJkZWZhdWx0LXJvbGVzLWhybXMiLCJ1bWFfYXV0aG9yaXphdGlvbiIsIkFETUlOIiwiVVNFUiJdfSwicmVzb3VyY2VfYWNjZXNzIjp7ImFjY291bnQiOnsicm9sZXMiOlsibWFuYWdlLWFjY291bnQiLCJtYW5hZ2UtYWNjb3VudC1saW5rcyIsInZpZXctcHJvZmlsZSJdfX0sInNjb3BlIjoiZW1haWwgb3BlbmlkIHByb2ZpbGUiLCJlbWFpbF92ZXJpZmllZCI6ZmFsc2UsInJvbGUiOlsiU1VQRVJfQURNSU4iLCJvZmZsaW5lX2FjY2VzcyIsIlNUQUZGIiwiZGVmYXVsdC1yb2xlcy1ocm1zIiwidW1hX2F1dGhvcml6YXRpb24iLCJBRE1JTiIsIlVTRVIiXSwibmFtZSI6IuC4p-C4seC4meC5gOC4ieC4peC4tOC4oSDguInguLHguJXguKPguJfguK3guIciLCJwcmVmZXJyZWRfdXNlcm5hbWUiOiIzMTIwMjAwNDI0OTc1IiwiZ2l2ZW5fbmFtZSI6IuC4p-C4seC4meC5gOC4ieC4peC4tOC4oSIsImZhbWlseV9uYW1lIjoi4LiJ4Lix4LiV4Lij4LiX4Lit4LiHIn0.fHdMzpHMD4JcbzYnUrfM473FSXka2Z4lz_S3HI2c-dPXfO5ATpijqsi12C6-ExE0RJRXUK671erMuyVXL6u2qj-FvdliBL3ubKy4J3jIT3svkcZxZL2ib16dRg375dITefvqd-J4vw6MR4bq8YAGPbqRIy6BQ2pdEiZgNiwUUihHAFwZlVER1lNbaqlbL6vk_L4k-g25DBVnDr756BFvrw7zEDbawkKZ31EZF5_DYk4RWej0wvWrGHQWLw-RyzYVSBB_AooqHkncHn_CwLBGC5juOEfFO4a2ThuKwoxYCstjtBj-zmjpHFs-Hh3CBTWJCGFcKst1Ey28StlKtNkLiw", + }; + + // ส่ง GET request + let response = http.get( + //"https://bma-hrms.bangkok.go.th/api/v1/leave/fake-check-in", + //"https://hrmsbkk.case-collection.com/api/v1/org/dotnet/keycloak/439fa36d-6bc5-4ef5-95aa-ef29e64d2e9f", + "https://hrms.bangkok.go.th/api/v1/org/dotnet/keycloak/bafc5795-befc-4d3f-a461-3533c38a5fc1", + { headers: headers } + ); + + // ตรวจสอบการตอบสนอง + check(response, { + "is status 200": (r) => r.status === 200, + }); + + // หน่วงเวลา 1 วินาที + sleep(1); +} From 93a83b34e6e0bab690af4270b06cd2a2b9f88c87 Mon Sep 17 00:00:00 2001 From: harid Date: Mon, 19 Jan 2026 17:15:17 +0700 Subject: [PATCH 045/183] =?UTF-8?q?=E0=B8=9B=E0=B8=A3=E0=B8=B1=E0=B8=9A?= =?UTF-8?q?=E0=B9=81=E0=B8=9A=E0=B8=9A=E0=B9=83=E0=B8=9A=E0=B8=82=E0=B8=AD?= =?UTF-8?q?=E0=B8=A2=E0=B8=81=E0=B9=80=E0=B8=A5=E0=B8=B4=E0=B8=81=E0=B8=A7?= =?UTF-8?q?=E0=B8=B1=E0=B8=99=E0=B8=A5=E0=B8=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Controllers/LeaveReportController.cs | 25 ++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/BMA.EHR.Leave/Controllers/LeaveReportController.cs b/BMA.EHR.Leave/Controllers/LeaveReportController.cs index 0dec80c6..5dad15fa 100644 --- a/BMA.EHR.Leave/Controllers/LeaveReportController.cs +++ b/BMA.EHR.Leave/Controllers/LeaveReportController.cs @@ -1136,9 +1136,28 @@ namespace BMA.EHR.Leave.Service.Controllers dateSendLeave = data.CreatedAt.Date.ToThaiShortDate().ToThaiNumber(), leaveTypeName = data.Type.Name, fullname = fullName, - positionName = profile!.Position == null ? "-" : profile!.Position.ToThaiNumber(), + position = string.IsNullOrEmpty(profile.Position) ? "-" : profile.Position, + positionName = (!string.IsNullOrEmpty(profile.PositionLeaveName) && + (profile.PositionLeaveName.Contains("อำนวยการ") || profile.PositionLeaveName.Contains("บริหาร")) + ? string.IsNullOrEmpty(profile.PosExecutiveName) + ? string.IsNullOrEmpty(profile.Position) + ? "-" + : profile.Position + : profile.PosExecutiveName + : string.IsNullOrEmpty(profile.Position) + ? "-" + : string.IsNullOrEmpty(profile.PositionLeaveName) + ? profile.Position + : $"{profile.Position}{profile.PositionLeaveName}").ToThaiNumber(), positionLeaveName = profile!.PositionLeaveName == null ? "-" : profile!.PositionLeaveName.ToThaiNumber(), - organizationName = profile!.Oc ?? "", + posExecutiveName = profile.PosExecutiveName, + organizationName = profile!.Oc!.ToThaiNumber(), + posExOrg = !string.IsNullOrEmpty(profile.PositionLeaveName) && + (profile.PositionLeaveName.Contains("อำนวยการ") || profile.PositionLeaveName.Contains("บริหาร")) + ? new[] { (profile!.Oc!.ToThaiNumber()) } + : !string.IsNullOrEmpty(profile.PosExecutiveName) + ? new[] { (profile.PosExecutiveName.ToThaiNumber()), (profile!.Oc!.ToThaiNumber()) } + : new[] { (profile!.Oc!.ToThaiNumber()) }, leaveDateStart = data.LeaveStartDate.Date.ToThaiShortDate().ToThaiNumber(), leaveDateEnd = data.LeaveEndDate.Date.ToThaiShortDate().ToThaiNumber(), dear = data.CommanderPosition == null ? data.Dear : data.CommanderPosition.ToThaiNumber(), @@ -2834,7 +2853,7 @@ namespace BMA.EHR.Leave.Service.Controllers ? x.LastUpdatedAt.Value.Date.ToThaiShortDate().ToThaiNumber() : "............/............/............", comment = !string.IsNullOrEmpty(x.Comment) - ? x.Comment.Replace("\r", "").Replace("\n", " ").Trim() + ? (x.Comment.Replace("\r", "").Replace("\n", " ").Trim()).ToThaiNumber() : "......................................................................................................................................................................", approveType = (x.ApproveType ?? "").Trim().ToUpper() }) From 1aab307f6a2545ecd21671cb6440b52a3d618ecd Mon Sep 17 00:00:00 2001 From: harid Date: Mon, 19 Jan 2026 18:17:11 +0700 Subject: [PATCH 046/183] =?UTF-8?q?fix=20=E0=B8=A3=E0=B8=B2=E0=B8=A2?= =?UTF-8?q?=E0=B8=81=E0=B8=B2=E0=B8=A3=E0=B9=83=E0=B8=AB=E0=B9=89=E0=B8=AD?= =?UTF-8?q?=E0=B8=AD=E0=B8=81=E0=B8=A5=E0=B8=B9=E0=B8=81=E0=B8=88=E0=B9=89?= =?UTF-8?q?=E0=B8=B2=E0=B8=87=20=E0=B8=A3=E0=B8=B0=E0=B8=9A=E0=B8=9A?= =?UTF-8?q?=E0=B9=83=E0=B8=8A=E0=B9=89=E0=B8=AA=E0=B8=B4=E0=B8=97=E0=B8=98?= =?UTF-8?q?=E0=B8=B4=E0=B9=8C=20API=20=E0=B9=80=E0=B8=AA=E0=B9=89=E0=B8=99?= =?UTF-8?q?=E0=B9=80=E0=B8=94=E0=B8=B5=E0=B8=A2=E0=B8=A7=E0=B8=81=E0=B8=B1?= =?UTF-8?q?=E0=B8=9A=E0=B8=82=E0=B8=AD=E0=B8=87=E0=B8=82=E0=B8=A3=E0=B8=81?= =?UTF-8?q?.=20#2173?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Controllers/RetirementOutController.cs | 44 ++++++++++++------- 1 file changed, 28 insertions(+), 16 deletions(-) diff --git a/BMA.EHR.Retirement.Service/Controllers/RetirementOutController.cs b/BMA.EHR.Retirement.Service/Controllers/RetirementOutController.cs index e996abab..be31a5c9 100644 --- a/BMA.EHR.Retirement.Service/Controllers/RetirementOutController.cs +++ b/BMA.EHR.Retirement.Service/Controllers/RetirementOutController.cs @@ -239,16 +239,6 @@ namespace BMA.EHR.Retirement.Service.Controllers [HttpGet("{id:length(36)}")] public async Task> GetDetailAdmin(Guid id) { - var getWorkflow = await _permission.GetPermissionAPIWorkflowAsync(id.ToString(), "SYS_DISMISS"); - if (getWorkflow == false) - { - var getPermission = await _permission.GetPermissionAPIAsync("GET", "SYS_DISMISS"); - var jsonData = JsonConvert.DeserializeObject(getPermission); - if (jsonData["status"]?.ToString() != "200") - { - return Error(jsonData["message"]?.ToString(), StatusCodes.Status403Forbidden); - } - } var data = await _context.RetirementOuts.AsQueryable() .Where(x => x.Id == id) .Select(p => new @@ -294,6 +284,20 @@ namespace BMA.EHR.Retirement.Service.Controllers p.OrganizationOld, }) .FirstOrDefaultAsync(); + + string _system = data != null && data.profileType?.Trim().ToUpper() == "OFFICER" ? "SYS_DISMISS" : "SYS_DISMISS_EMP"; + + var getWorkflow = await _permission.GetPermissionAPIWorkflowAsync(id.ToString(), _system); + if (getWorkflow == false) + { + var getPermission = await _permission.GetPermissionAPIAsync("GET", "SYS_DISMISS"); + var jsonData = JsonConvert.DeserializeObject(getPermission); + if (jsonData["status"]?.ToString() != "200") + { + return Error(jsonData["message"]?.ToString(), StatusCodes.Status403Forbidden); + } + } + if (data == null) return Error(GlobalMessages.DataNotFound, 404); @@ -462,14 +466,18 @@ namespace BMA.EHR.Retirement.Service.Controllers [HttpPut("{id:length(36)}")] public async Task> Put([FromBody] RetirementOutEditRequest req, Guid id) { - var getPermission = await _permission.GetPermissionAPIAsync("UPDATE", "SYS_DISMISS"); + var uppdated = await _context.RetirementOuts + .FirstOrDefaultAsync(x => x.Id == id); + + string _system = uppdated != null && uppdated.profileType?.Trim().ToUpper() == "OFFICER" ? "SYS_DISMISS" : "SYS_DISMISS_EMP"; + + var getPermission = await _permission.GetPermissionAPIAsync("UPDATE", _system); var jsonData = JsonConvert.DeserializeObject(getPermission); if (jsonData["status"]?.ToString() != "200") { return Error(jsonData["message"]?.ToString(), StatusCodes.Status403Forbidden); } - var uppdated = await _context.RetirementOuts - .FirstOrDefaultAsync(x => x.Id == id); + if (uppdated == null) return Error(GlobalMessages.RetirementOutNotFound, 404); @@ -527,14 +535,18 @@ namespace BMA.EHR.Retirement.Service.Controllers [HttpDelete("{id:length(36)}")] public async Task> Delete(Guid id) { - var getPermission = await _permission.GetPermissionAPIAsync("DELETE", "SYS_DISMISS"); + var deleted = await _context.RetirementOuts.AsQueryable() + .FirstOrDefaultAsync(x => x.Id == id); + + string _system = deleted != null && deleted.profileType?.Trim().ToUpper() == "OFFICER" ? "SYS_DISMISS" : "SYS_DISMISS_EMP"; + + var getPermission = await _permission.GetPermissionAPIAsync("DELETE", _system); var jsonData = JsonConvert.DeserializeObject(getPermission); if (jsonData["status"]?.ToString() != "200") { return Error(jsonData["message"]?.ToString(), StatusCodes.Status403Forbidden); } - var deleted = await _context.RetirementOuts.AsQueryable() - .FirstOrDefaultAsync(x => x.Id == id); + if (deleted == null) return NotFound(); _context.RetirementOuts.Remove(deleted); From 0ab75b2a1935454d130213d25373988d051f47d4 Mon Sep 17 00:00:00 2001 From: Suphonchai Phoonsawat Date: Mon, 19 Jan 2026 22:42:39 +0700 Subject: [PATCH 047/183] Add delay to start message consumption until 8:10 AM and implement time calculation --- BMA.EHR.CheckInConsumer/Program.cs | 27 +++++++++++++++++++++++++++ dotnet_keycloak_test.js | 2 +- 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/BMA.EHR.CheckInConsumer/Program.cs b/BMA.EHR.CheckInConsumer/Program.cs index 1a5a68d1..40a9db26 100644 --- a/BMA.EHR.CheckInConsumer/Program.cs +++ b/BMA.EHR.CheckInConsumer/Program.cs @@ -13,6 +13,9 @@ var configuration = new ConfigurationBuilder() WriteToConsole("Consumer Start!"); +// Wait until 8:00 AM before starting to consume messages +await WaitUntil8AM(); + var host = configuration["Rabbit:Host"] ?? ""; var user = configuration["Rabbit:User"] ?? ""; var pass = configuration["Rabbit:Password"] ?? ""; @@ -95,6 +98,30 @@ async Task CallRestApi(string requestData) } } +async Task WaitUntil8AM() +{ + // Get current time in Bangkok timezone + var bangkokTimeZone = TimeZoneInfo.FindSystemTimeZoneById("SE Asia Standard Time"); + var currentTime = TimeZoneInfo.ConvertTimeFromUtc(DateTime.UtcNow, bangkokTimeZone); + + var targetTime = new DateTime(currentTime.Year, currentTime.Month, currentTime.Day, 8, 10, 0); + + // If current time is already past 8:10 AM today, start immediately + if (currentTime >= targetTime) + { + WriteToConsole($"Current time is {currentTime:HH:mm:ss}. Starting consumer immediately."); + return; + } + + // Calculate time to wait + var timeToWait = targetTime - currentTime; + WriteToConsole($"Current time is {currentTime:HH:mm:ss}. Waiting until 08:10:00 to start consuming messages."); + WriteToConsole($"Time to wait: {timeToWait.Hours} hours, {timeToWait.Minutes} minutes, {timeToWait.Seconds} seconds"); + + await Task.Delay(timeToWait); + WriteToConsole("It's now 08:10:00. Starting to consume messages from queue."); +} + public class ResponseObject { diff --git a/dotnet_keycloak_test.js b/dotnet_keycloak_test.js index d271087d..1e623210 100644 --- a/dotnet_keycloak_test.js +++ b/dotnet_keycloak_test.js @@ -41,7 +41,7 @@ export default function () { //"https://bma-hrms.bangkok.go.th/api/v1/leave/fake-check-in", //"https://hrmsbkk.case-collection.com/api/v1/org/dotnet/keycloak/439fa36d-6bc5-4ef5-95aa-ef29e64d2e9f", "https://hrms.bangkok.go.th/api/v1/org/dotnet/keycloak/bafc5795-befc-4d3f-a461-3533c38a5fc1", - { headers: headers } + { headers: headers }, ); // ตรวจสอบการตอบสนอง From 3532df32fdf882971f321f2fb143ac41147a8f75 Mon Sep 17 00:00:00 2001 From: Suphonchai Phoonsawat Date: Tue, 20 Jan 2026 05:38:42 +0700 Subject: [PATCH 048/183] Refactor message consumption to start after 8:10 AM and implement time checks for operating hours --- BMA.EHR.CheckInConsumer/Program.cs | 100 +++++++++++++++-------------- 1 file changed, 51 insertions(+), 49 deletions(-) diff --git a/BMA.EHR.CheckInConsumer/Program.cs b/BMA.EHR.CheckInConsumer/Program.cs index 40a9db26..e09c61c6 100644 --- a/BMA.EHR.CheckInConsumer/Program.cs +++ b/BMA.EHR.CheckInConsumer/Program.cs @@ -13,9 +13,6 @@ var configuration = new ConfigurationBuilder() WriteToConsole("Consumer Start!"); -// Wait until 8:00 AM before starting to consume messages -await WaitUntil8AM(); - var host = configuration["Rabbit:Host"] ?? ""; var user = configuration["Rabbit:User"] ?? ""; var pass = configuration["Rabbit:Password"] ?? ""; @@ -24,52 +21,63 @@ var queue = configuration["Rabbit:Queue"] ?? "basic-queue"; // create connection var factory = new ConnectionFactory() { - //Uri = new Uri("amqp://admin:P@ssw0rd@192.168.4.11:5672") - HostName = host,// หรือ hostname ของ RabbitMQ Server ที่คุณใช้ - UserName = user, // ใส่ชื่อผู้ใช้ของคุณ - Password = pass // ใส่รหัสผ่านของคุณ + HostName = host, + UserName = user, + Password = pass }; using var connection = factory.CreateConnection(); using var channel = connection.CreateModel(); -//channel.QueueDeclare(queue: "bma-checkin-queue", durable: true, exclusive: false, autoDelete: false, arguments: null); channel.QueueDeclare(queue: queue, durable: true, exclusive: false, autoDelete: false, arguments: null); var consumer = new EventingBasicConsumer(channel); +string? consumerTag = null; +bool isConsuming = false; consumer.Received += async (model, ea) => { var body = ea.Body.ToArray(); var message = Encoding.UTF8.GetString(body); + + // Double-check time before processing (safety check) + if (!IsWithinOperatingHours()) + { + WriteToConsole($"Message received outside operating hours. Requeuing message."); + channel.BasicNack(ea.DeliveryTag, false, true); // Requeue the message + return; + } + await CallRestApi(message); - - // convert string into object - //var request = JsonConvert.DeserializeObject(message); - //using (var db = new ApplicationDbContext()) - //{ - // var item = new AttendantItem - // { - // Name = request.Name, - // CheckInDateTime = request.CheckInDateTime, - // }; - // db.AttendantItems.Add(item); - // db.SaveChanges(); - - // WriteToConsole($"ได้รับคำขอจาก Queue: {message}"); - // WriteToConsole($"ตอบกลับจาก REST API: {JsonConvert.SerializeObject(item)}"); - //} - WriteToConsole($"ได้รับคำขอจาก Queue: {message}"); - //WriteToConsole($"ตอบกลับจาก REST API: {JsonConvert.SerializeObject(item)}"); }; -//channel.BasicConsume(queue: "bma-checkin-queue", autoAck: true, consumer: consumer); -channel.BasicConsume(queue: queue, autoAck: true, consumer: consumer); +// Monitor and control consumer based on time schedule +using var timer = new PeriodicTimer(TimeSpan.FromMinutes(1)); -//Console.WriteLine("\nPress 'Enter' to exit the process..."); - -await Task.Delay(-1); +while (await timer.WaitForNextTickAsync()) +{ + var shouldBeConsuming = IsWithinOperatingHours(); + + if (shouldBeConsuming && !isConsuming) + { + // Start consuming + consumerTag = channel.BasicConsume(queue: queue, autoAck: true, consumer: consumer); + isConsuming = true; + WriteToConsole($"✅ Started consuming messages at {GetCurrentBangkokTime():yyyy-MM-dd HH:mm:ss}"); + } + else if (!shouldBeConsuming && isConsuming) + { + // Stop consuming + if (consumerTag != null) + { + channel.BasicCancel(consumerTag); + consumerTag = null; + } + isConsuming = false; + WriteToConsole($"⏸️ Stopped consuming messages at {GetCurrentBangkokTime():yyyy-MM-dd HH:mm:ss}. Will resume after 08:10 tomorrow."); + } +} static void WriteToConsole(string message) { @@ -98,28 +106,22 @@ async Task CallRestApi(string requestData) } } -async Task WaitUntil8AM() +DateTime GetCurrentBangkokTime() { - // Get current time in Bangkok timezone var bangkokTimeZone = TimeZoneInfo.FindSystemTimeZoneById("SE Asia Standard Time"); - var currentTime = TimeZoneInfo.ConvertTimeFromUtc(DateTime.UtcNow, bangkokTimeZone); + return TimeZoneInfo.ConvertTimeFromUtc(DateTime.UtcNow, bangkokTimeZone); +} + +bool IsWithinOperatingHours() +{ + var currentTime = GetCurrentBangkokTime(); + var startTime = new TimeSpan(8, 10, 0); // 8:10 AM + var endTime = new TimeSpan(23, 59, 59); // End of day - var targetTime = new DateTime(currentTime.Year, currentTime.Month, currentTime.Day, 8, 10, 0); + var currentTimeOfDay = currentTime.TimeOfDay; - // If current time is already past 8:10 AM today, start immediately - if (currentTime >= targetTime) - { - WriteToConsole($"Current time is {currentTime:HH:mm:ss}. Starting consumer immediately."); - return; - } - - // Calculate time to wait - var timeToWait = targetTime - currentTime; - WriteToConsole($"Current time is {currentTime:HH:mm:ss}. Waiting until 08:10:00 to start consuming messages."); - WriteToConsole($"Time to wait: {timeToWait.Hours} hours, {timeToWait.Minutes} minutes, {timeToWait.Seconds} seconds"); - - await Task.Delay(timeToWait); - WriteToConsole("It's now 08:10:00. Starting to consume messages from queue."); + // Consumer should only work from 8:10 AM to end of day + return currentTimeOfDay >= startTime && currentTimeOfDay <= endTime; } From 9442d3b29f25519137b17b52f56aa92758e1db6a Mon Sep 17 00:00:00 2001 From: harid Date: Tue, 20 Jan 2026 09:32:10 +0700 Subject: [PATCH 049/183] fix #2173 --- .../Controllers/RetirementOutController.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BMA.EHR.Retirement.Service/Controllers/RetirementOutController.cs b/BMA.EHR.Retirement.Service/Controllers/RetirementOutController.cs index be31a5c9..72a7bc91 100644 --- a/BMA.EHR.Retirement.Service/Controllers/RetirementOutController.cs +++ b/BMA.EHR.Retirement.Service/Controllers/RetirementOutController.cs @@ -290,7 +290,7 @@ namespace BMA.EHR.Retirement.Service.Controllers var getWorkflow = await _permission.GetPermissionAPIWorkflowAsync(id.ToString(), _system); if (getWorkflow == false) { - var getPermission = await _permission.GetPermissionAPIAsync("GET", "SYS_DISMISS"); + var getPermission = await _permission.GetPermissionAPIAsync("GET", _system); var jsonData = JsonConvert.DeserializeObject(getPermission); if (jsonData["status"]?.ToString() != "200") { From 60602d99c476ac877fe3e7fdfbeeadecb3a0bd7e Mon Sep 17 00:00:00 2001 From: harid Date: Tue, 20 Jan 2026 09:54:15 +0700 Subject: [PATCH 050/183] =?UTF-8?q?api=20=E0=B8=82=E0=B8=AD=E0=B9=82?= =?UTF-8?q?=E0=B8=AD=E0=B8=99=20(admin)=20=E0=B9=80=E0=B8=9E=E0=B8=B4?= =?UTF-8?q?=E0=B9=88=E0=B8=A1=E0=B8=9A=E0=B8=B1=E0=B8=99=E0=B8=97=E0=B8=B6?= =?UTF-8?q?=E0=B8=81=E0=B8=9F=E0=B8=B4=E0=B8=A5=E0=B8=A5=E0=B9=8C=20"?= =?UTF-8?q?=E0=B8=82=E0=B8=AD=E0=B9=82=E0=B8=AD=E0=B8=99=E0=B8=95=E0=B8=B1?= =?UTF-8?q?=E0=B9=89=E0=B8=87=E0=B9=81=E0=B8=95=E0=B9=88=E0=B8=A7=E0=B8=B1?= =?UTF-8?q?=E0=B8=99=E0=B8=97=E0=B8=B5=E0=B9=88"=20#2196?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Controllers/PlacementTransferController.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/BMA.EHR.Placement.Service/Controllers/PlacementTransferController.cs b/BMA.EHR.Placement.Service/Controllers/PlacementTransferController.cs index e27b4ad3..73115246 100644 --- a/BMA.EHR.Placement.Service/Controllers/PlacementTransferController.cs +++ b/BMA.EHR.Placement.Service/Controllers/PlacementTransferController.cs @@ -699,6 +699,7 @@ namespace BMA.EHR.Placement.Service.Controllers Organization = req.Organization, Reason = req.Reason, Status = "APPROVE", + Date = req.Date, CreatedFullName = FullName ?? "System Administrator", CreatedUserId = UserId ?? "", CreatedAt = DateTime.Now, From a463df571695723a9ba32bf599fe6553dc0399c4 Mon Sep 17 00:00:00 2001 From: Suphonchai Phoonsawat Date: Tue, 20 Jan 2026 10:49:13 +0700 Subject: [PATCH 051/183] Add migration to create CheckInJobStatuses table for RMQ task control - Introduced a new migration that creates the CheckInJobStatuses table. - The table includes fields for tracking job statuses, timestamps, user information, and error messages. - Supports various statuses such as PENDING, PROCESSING, COMPLETED, and FAILED. --- .../ApplicationServicesRegistration.cs | 1 + .../CheckInJobStatusRepository.cs | 135 ++ BMA.EHR.CheckInConsumer/Program.cs | 86 +- BMA.EHR.CheckInConsumer/appsettings.json | 16 +- .../Leave/TimeAttendants/CheckInJobStatus.cs | 39 + ...120032158_Add RMQ Task Control.Designer.cs | 1705 +++++++++++++++++ .../20260120032158_Add RMQ Task Control.cs | 58 + .../LeaveDb/LeaveDbContextModelSnapshot.cs | 93 + .../Persistence/LeaveDbContext.cs | 2 + BMA.EHR.Leave/Controllers/LeaveController.cs | 247 ++- BMA.EHR.Leave/DTOs/CheckIn/CheckTimeDto.cs | 1 + BMA.EHR.Leave/appsettings.json | 2 +- 12 files changed, 2259 insertions(+), 126 deletions(-) create mode 100644 BMA.EHR.Application/Repositories/Leaves/TimeAttendants/CheckInJobStatusRepository.cs create mode 100644 BMA.EHR.Domain/Models/Leave/TimeAttendants/CheckInJobStatus.cs create mode 100644 BMA.EHR.Infrastructure/Migrations/LeaveDb/20260120032158_Add RMQ Task Control.Designer.cs create mode 100644 BMA.EHR.Infrastructure/Migrations/LeaveDb/20260120032158_Add RMQ Task Control.cs diff --git a/BMA.EHR.Application/ApplicationServicesRegistration.cs b/BMA.EHR.Application/ApplicationServicesRegistration.cs index 350b7a75..bf6dc6df 100644 --- a/BMA.EHR.Application/ApplicationServicesRegistration.cs +++ b/BMA.EHR.Application/ApplicationServicesRegistration.cs @@ -53,6 +53,7 @@ namespace BMA.EHR.Application services.AddTransient(); services.AddTransient(); services.AddTransient(); + services.AddTransient(); services.AddTransient(); services.AddTransient(); diff --git a/BMA.EHR.Application/Repositories/Leaves/TimeAttendants/CheckInJobStatusRepository.cs b/BMA.EHR.Application/Repositories/Leaves/TimeAttendants/CheckInJobStatusRepository.cs new file mode 100644 index 00000000..302bdd12 --- /dev/null +++ b/BMA.EHR.Application/Repositories/Leaves/TimeAttendants/CheckInJobStatusRepository.cs @@ -0,0 +1,135 @@ +using BMA.EHR.Application.Common.Interfaces; +using BMA.EHR.Domain.Models.Leave.TimeAttendants; +using Microsoft.AspNetCore.Http; +using Microsoft.EntityFrameworkCore; + +namespace BMA.EHR.Application.Repositories.Leaves.TimeAttendants +{ + public class CheckInJobStatusRepository : GenericLeaveRepository + { + #region " Fields " + + private readonly ILeaveDbContext _dbContext; + + #endregion + + #region " Constructor and Destructor " + + public CheckInJobStatusRepository(ILeaveDbContext dbContext, + IHttpContextAccessor httpContextAccessor) : base(dbContext, httpContextAccessor) + { + _dbContext = dbContext; + } + + #endregion + + #region " Methods " + + /// + /// ดึงข้อมูล Job Status จาก TaskId + /// + public async Task GetByTaskIdAsync(Guid taskId) + { + var data = await _dbContext.Set() + .Where(x => x.TaskId == taskId) + .FirstOrDefaultAsync(); + + return data; + } + + /// + /// ดึงข้อมูล Job Status จาก UserId และสถานะ + /// + public async Task> GetByUserIdAndStatusAsync(Guid userId, string status) + { + var data = await _dbContext.Set() + .Where(x => x.KeycloakUserId == userId && x.Status == status) + .OrderByDescending(x => x.CreatedDate) + .ToListAsync(); + + return data; + } + + /// + /// ดึงข้อมูล Job Status ที่ยัง pending หรือ processing + /// + public async Task> GetPendingOrProcessingJobsAsync(Guid userId) + { + var data = await _dbContext.Set() + .Where(x => x.KeycloakUserId == userId && + (x.Status == "PENDING" || x.Status == "PROCESSING")) + //.OrderByDescending(x => x.CreatedDate) + .ToListAsync(); + + return data; + } + + /// + /// อัปเดตสถานะเป็น Processing + /// + public async Task UpdateToProcessingAsync(Guid taskId) + { + var job = await GetByTaskIdAsync(taskId); + if (job != null) + { + job.Status = "PROCESSING"; + job.ProcessingDate = DateTime.Now; + await UpdateAsync(job); + } + return job!; + } + + /// + /// อัปเดตสถานะเป็น Completed + /// + public async Task UpdateToCompletedAsync(Guid taskId, string? additionalData = null) + { + var job = await GetByTaskIdAsync(taskId); + if (job != null) + { + job.Status = "COMPLETED"; + job.CompletedDate = DateTime.Now; + if (!string.IsNullOrEmpty(additionalData)) + { + job.AdditionalData = additionalData; + } + await UpdateAsync(job); + } + return job!; + } + + /// + /// อัปเดตสถานะเป็น Failed + /// + public async Task UpdateToFailedAsync(Guid taskId, string errorMessage) + { + var job = await GetByTaskIdAsync(taskId); + if (job != null) + { + job.Status = "FAILED"; + job.CompletedDate = DateTime.Now; + job.ErrorMessage = errorMessage; + await UpdateAsync(job); + } + return job!; + } + + /// + /// ล้างข้อมูล Job Status ที่เก่าเกิน X วัน + /// + public async Task CleanupOldJobsAsync(int daysOld = 30) + { + var cutoffDate = DateTime.Now.AddDays(-daysOld); + var oldJobs = await _dbContext.Set() + .Where(x => x.CreatedDate < cutoffDate) + .ToListAsync(); + + _dbContext.Set().RemoveRange(oldJobs); + await _dbContext.SaveChangesAsync(); + + return oldJobs.Count; + } + + #endregion + } +} diff --git a/BMA.EHR.CheckInConsumer/Program.cs b/BMA.EHR.CheckInConsumer/Program.cs index e09c61c6..0ae439cf 100644 --- a/BMA.EHR.CheckInConsumer/Program.cs +++ b/BMA.EHR.CheckInConsumer/Program.cs @@ -21,63 +21,52 @@ var queue = configuration["Rabbit:Queue"] ?? "basic-queue"; // create connection var factory = new ConnectionFactory() { - HostName = host, - UserName = user, - Password = pass + //Uri = new Uri("amqp://admin:P@ssw0rd@192.168.4.11:5672") + HostName = host,// หรือ hostname ของ RabbitMQ Server ที่คุณใช้ + UserName = user, // ใส่ชื่อผู้ใช้ของคุณ + Password = pass // ใส่รหัสผ่านของคุณ }; using var connection = factory.CreateConnection(); using var channel = connection.CreateModel(); +//channel.QueueDeclare(queue: "bma-checkin-queue", durable: true, exclusive: false, autoDelete: false, arguments: null); channel.QueueDeclare(queue: queue, durable: true, exclusive: false, autoDelete: false, arguments: null); var consumer = new EventingBasicConsumer(channel); -string? consumerTag = null; -bool isConsuming = false; consumer.Received += async (model, ea) => { var body = ea.Body.ToArray(); var message = Encoding.UTF8.GetString(body); - - // Double-check time before processing (safety check) - if (!IsWithinOperatingHours()) - { - WriteToConsole($"Message received outside operating hours. Requeuing message."); - channel.BasicNack(ea.DeliveryTag, false, true); // Requeue the message - return; - } - await CallRestApi(message); + + // convert string into object + //var request = JsonConvert.DeserializeObject(message); + //using (var db = new ApplicationDbContext()) + //{ + // var item = new AttendantItem + // { + // Name = request.Name, + // CheckInDateTime = request.CheckInDateTime, + // }; + // db.AttendantItems.Add(item); + // db.SaveChanges(); + + // WriteToConsole($"ได้รับคำขอจาก Queue: {message}"); + // WriteToConsole($"ตอบกลับจาก REST API: {JsonConvert.SerializeObject(item)}"); + //} + WriteToConsole($"ได้รับคำขอจาก Queue: {message}"); + //WriteToConsole($"ตอบกลับจาก REST API: {JsonConvert.SerializeObject(item)}"); }; -// Monitor and control consumer based on time schedule -using var timer = new PeriodicTimer(TimeSpan.FromMinutes(1)); +//channel.BasicConsume(queue: "bma-checkin-queue", autoAck: true, consumer: consumer); +channel.BasicConsume(queue: queue, autoAck: true, consumer: consumer); -while (await timer.WaitForNextTickAsync()) -{ - var shouldBeConsuming = IsWithinOperatingHours(); - - if (shouldBeConsuming && !isConsuming) - { - // Start consuming - consumerTag = channel.BasicConsume(queue: queue, autoAck: true, consumer: consumer); - isConsuming = true; - WriteToConsole($"✅ Started consuming messages at {GetCurrentBangkokTime():yyyy-MM-dd HH:mm:ss}"); - } - else if (!shouldBeConsuming && isConsuming) - { - // Stop consuming - if (consumerTag != null) - { - channel.BasicCancel(consumerTag); - consumerTag = null; - } - isConsuming = false; - WriteToConsole($"⏸️ Stopped consuming messages at {GetCurrentBangkokTime():yyyy-MM-dd HH:mm:ss}. Will resume after 08:10 tomorrow."); - } -} +//Console.WriteLine("\nPress 'Enter' to exit the process..."); + +await Task.Delay(-1); static void WriteToConsole(string message) { @@ -106,25 +95,6 @@ async Task CallRestApi(string requestData) } } -DateTime GetCurrentBangkokTime() -{ - var bangkokTimeZone = TimeZoneInfo.FindSystemTimeZoneById("SE Asia Standard Time"); - return TimeZoneInfo.ConvertTimeFromUtc(DateTime.UtcNow, bangkokTimeZone); -} - -bool IsWithinOperatingHours() -{ - var currentTime = GetCurrentBangkokTime(); - var startTime = new TimeSpan(8, 10, 0); // 8:10 AM - var endTime = new TimeSpan(23, 59, 59); // End of day - - var currentTimeOfDay = currentTime.TimeOfDay; - - // Consumer should only work from 8:10 AM to end of day - return currentTimeOfDay >= startTime && currentTimeOfDay <= endTime; -} - - public class ResponseObject { [JsonPropertyName("status")] diff --git a/BMA.EHR.CheckInConsumer/appsettings.json b/BMA.EHR.CheckInConsumer/appsettings.json index b180f90c..76f86c86 100644 --- a/BMA.EHR.CheckInConsumer/appsettings.json +++ b/BMA.EHR.CheckInConsumer/appsettings.json @@ -1,9 +1,9 @@ { - "Rabbit": { - "Host": "192.168.1.40", - "User": "admin", - "Password": "Test123456", - "Queue": "bma-checkin-queue" - }, - "API": "https://localhost:7283/api/v1" -} \ No newline at end of file + "Rabbit": { + "Host": "192.168.1.63", + "User": "admin", + "Password": "12345678", + "Queue": "hrms-checkin-queue-dev" + }, + "API": "https://localhost:7283/api/v1" +} diff --git a/BMA.EHR.Domain/Models/Leave/TimeAttendants/CheckInJobStatus.cs b/BMA.EHR.Domain/Models/Leave/TimeAttendants/CheckInJobStatus.cs new file mode 100644 index 00000000..922f966b --- /dev/null +++ b/BMA.EHR.Domain/Models/Leave/TimeAttendants/CheckInJobStatus.cs @@ -0,0 +1,39 @@ +using BMA.EHR.Domain.Models.Base; +using Microsoft.EntityFrameworkCore; +using System.ComponentModel.DataAnnotations; + +namespace BMA.EHR.Domain.Models.Leave.TimeAttendants +{ + public class CheckInJobStatus : EntityBase + { + [Required, Comment("Task ID สำหรับติดตามสถานะงาน")] + public Guid TaskId { get; set; } = Guid.Empty; + + [Required, Comment("รหัส User ของ Keycloak")] + public Guid KeycloakUserId { get; set; } = Guid.Empty; + + [Comment("วันเวลาที่สร้างงาน")] + public DateTime CreatedDate { get; set; } = DateTime.Now; + + [Comment("วันเวลาที่เริ่มประมวลผล")] + public DateTime? ProcessingDate { get; set; } + + [Comment("วันเวลาที่เสร็จสิ้นการประมวลผล")] + public DateTime? CompletedDate { get; set; } + + [Required, Comment("สถานะงาน: PENDING, PROCESSING, COMPLETED, FAILED")] + public string Status { get; set; } = "PENDING"; + + [Comment("ประเภทการลงเวลา: CHECK_IN, CHECK_OUT")] + public string? CheckType { get; set; } + + [Comment("CheckInId สำหรับ Check-Out")] + public Guid? CheckInId { get; set; } + + [Comment("ข้อความแสดงข้อผิดพลาด")] + public string? ErrorMessage { get; set; } + + [Comment("ข้อมูลเพิ่มเติม (JSON)")] + public string? AdditionalData { get; set; } + } +} diff --git a/BMA.EHR.Infrastructure/Migrations/LeaveDb/20260120032158_Add RMQ Task Control.Designer.cs b/BMA.EHR.Infrastructure/Migrations/LeaveDb/20260120032158_Add RMQ Task Control.Designer.cs new file mode 100644 index 00000000..a7f4e1fb --- /dev/null +++ b/BMA.EHR.Infrastructure/Migrations/LeaveDb/20260120032158_Add RMQ Task Control.Designer.cs @@ -0,0 +1,1705 @@ +// +using System; +using BMA.EHR.Infrastructure.Persistence; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace BMA.EHR.Infrastructure.Migrations.LeaveDb +{ + [DbContext(typeof(LeaveDbContext))] + [Migration("20260120032158_Add RMQ Task Control")] + partial class AddRMQTaskControl + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "7.0.9") + .HasAnnotation("Relational:MaxIdentifierLength", 64); + + modelBuilder.Entity("BMA.EHR.Domain.Models.Documents.Document", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CreatedDate") + .HasColumnType("datetime(6)"); + + b.Property("Detail") + .IsRequired() + .HasColumnType("text"); + + b.Property("FileName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("varchar(255)"); + + b.Property("FileSize") + .HasColumnType("int"); + + b.Property("FileType") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("varchar(128)"); + + b.Property("ObjectRefId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.ToTable("Document"); + }); + + modelBuilder.Entity("BMA.EHR.Domain.Models.Leave.Commons.LeaveType", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)") + .HasColumnOrder(0) + .HasComment("PrimaryKey") + .HasAnnotation("Relational:JsonPropertyName", "id"); + + b.Property("Code") + .IsRequired() + .HasColumnType("longtext") + .HasComment("รหัสประเภทการลา"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)") + .HasColumnOrder(100) + .HasComment("สร้างข้อมูลเมื่อ"); + + b.Property("CreatedFullName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)") + .HasColumnOrder(104) + .HasComment("ชื่อ User ที่สร้างข้อมูล"); + + b.Property("CreatedUserId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("varchar(40)") + .HasColumnOrder(101) + .HasComment("User Id ที่สร้างข้อมูล"); + + b.Property("LastUpdateFullName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)") + .HasColumnOrder(105) + .HasComment("ชื่อ User ที่แก้ไขข้อมูลล่าสุด"); + + b.Property("LastUpdateUserId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("varchar(40)") + .HasColumnOrder(103) + .HasComment("User Id ที่แก้ไขข้อมูลล่าสุด"); + + b.Property("LastUpdatedAt") + .HasColumnType("datetime(6)") + .HasColumnOrder(102) + .HasComment("แก้ไขข้อมูลล่าสุดเมื่อ"); + + b.Property("Limit") + .HasColumnType("int") + .HasComment("จำนวนวันลาสูงสุดประจำปี"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext") + .HasComment("ชื่อประเภทการลา"); + + b.HasKey("Id"); + + b.ToTable("LeaveTypes"); + }); + + modelBuilder.Entity("BMA.EHR.Domain.Models.Leave.Requests.LeaveBeginning", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)") + .HasColumnOrder(0) + .HasComment("PrimaryKey") + .HasAnnotation("Relational:JsonPropertyName", "id"); + + b.Property("Child1DnaId") + .HasColumnType("char(36)"); + + b.Property("Child2DnaId") + .HasColumnType("char(36)"); + + b.Property("Child3DnaId") + .HasColumnType("char(36)"); + + b.Property("Child4DnaId") + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)") + .HasColumnOrder(100) + .HasComment("สร้างข้อมูลเมื่อ"); + + b.Property("CreatedFullName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)") + .HasColumnOrder(104) + .HasComment("ชื่อ User ที่สร้างข้อมูล"); + + b.Property("CreatedUserId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("varchar(40)") + .HasColumnOrder(101) + .HasComment("User Id ที่สร้างข้อมูล"); + + b.Property("FirstName") + .HasColumnType("longtext"); + + b.Property("LastName") + .HasColumnType("longtext"); + + b.Property("LastUpdateFullName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)") + .HasColumnOrder(105) + .HasComment("ชื่อ User ที่แก้ไขข้อมูลล่าสุด"); + + b.Property("LastUpdateUserId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("varchar(40)") + .HasColumnOrder(103) + .HasComment("User Id ที่แก้ไขข้อมูลล่าสุด"); + + b.Property("LastUpdatedAt") + .HasColumnType("datetime(6)") + .HasColumnOrder(102) + .HasComment("แก้ไขข้อมูลล่าสุดเมื่อ"); + + b.Property("LeaveDays") + .HasColumnType("double") + .HasComment("จำนวนวันลายกมา"); + + b.Property("LeaveDaysUsed") + .HasColumnType("double") + .HasComment("จำนวนวันลาที่ใช้ไป"); + + b.Property("LeaveTypeId") + .HasColumnType("char(36)") + .HasComment("รหัสประเภทการลา"); + + b.Property("LeaveYear") + .HasColumnType("int") + .HasComment("ปีงบประมาณ"); + + b.Property("Prefix") + .HasColumnType("longtext"); + + b.Property("ProfileId") + .HasColumnType("char(36)") + .HasComment("รหัส Profile ในระบบทะเบียนประวัติ"); + + b.Property("RootDnaId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("LeaveTypeId"); + + b.ToTable("LeaveBeginnings"); + }); + + modelBuilder.Entity("BMA.EHR.Domain.Models.Leave.Requests.LeaveDocument", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)") + .HasColumnOrder(0) + .HasComment("PrimaryKey") + .HasAnnotation("Relational:JsonPropertyName", "id"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)") + .HasColumnOrder(100) + .HasComment("สร้างข้อมูลเมื่อ"); + + b.Property("CreatedFullName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)") + .HasColumnOrder(104) + .HasComment("ชื่อ User ที่สร้างข้อมูล"); + + b.Property("CreatedUserId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("varchar(40)") + .HasColumnOrder(101) + .HasComment("User Id ที่สร้างข้อมูล"); + + b.Property("DocumentId") + .HasColumnType("char(36)"); + + b.Property("LastUpdateFullName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)") + .HasColumnOrder(105) + .HasComment("ชื่อ User ที่แก้ไขข้อมูลล่าสุด"); + + b.Property("LastUpdateUserId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("varchar(40)") + .HasColumnOrder(103) + .HasComment("User Id ที่แก้ไขข้อมูลล่าสุด"); + + b.Property("LastUpdatedAt") + .HasColumnType("datetime(6)") + .HasColumnOrder(102) + .HasComment("แก้ไขข้อมูลล่าสุดเมื่อ"); + + b.Property("LeaveRequestId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("DocumentId"); + + b.HasIndex("LeaveRequestId"); + + b.ToTable("LeaveDocuments"); + }); + + modelBuilder.Entity("BMA.EHR.Domain.Models.Leave.Requests.LeaveRequest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)") + .HasColumnOrder(0) + .HasComment("PrimaryKey") + .HasAnnotation("Relational:JsonPropertyName", "id"); + + b.Property("AbsentDayAt") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("AbsentDayGetIn") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("AbsentDayLocation") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("AbsentDayRegistorDate") + .HasColumnType("datetime(6)"); + + b.Property("AbsentDaySummon") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Amount") + .HasColumnType("double"); + + b.Property("ApproveStep") + .HasColumnType("longtext") + .HasComment("step การอนุมัติ st1 = จทน.อนุมัตื,st2 = ผู้บังคับบัญชา อนุมัติ "); + + b.Property("BirthDate") + .HasColumnType("datetime(6)"); + + b.Property("CancelLeaveWrote") + .HasColumnType("longtext") + .HasComment("เขียนที่ (ขอยกเลิก)"); + + b.Property("Child1") + .HasColumnType("longtext"); + + b.Property("Child1DnaId") + .HasColumnType("char(36)"); + + b.Property("Child1Id") + .HasColumnType("char(36)"); + + b.Property("Child2") + .HasColumnType("longtext"); + + b.Property("Child2DnaId") + .HasColumnType("char(36)"); + + b.Property("Child2Id") + .HasColumnType("char(36)"); + + b.Property("Child3") + .HasColumnType("longtext"); + + b.Property("Child3DnaId") + .HasColumnType("char(36)"); + + b.Property("Child3Id") + .HasColumnType("char(36)"); + + b.Property("Child4") + .HasColumnType("longtext"); + + b.Property("Child4DnaId") + .HasColumnType("char(36)"); + + b.Property("Child4Id") + .HasColumnType("char(36)"); + + b.Property("CitizenId") + .HasColumnType("longtext"); + + b.Property("CommanderPosition") + .HasColumnType("longtext"); + + b.Property("CoupleDayCountryHistory") + .HasColumnType("longtext"); + + b.Property("CoupleDayEndDateHistory") + .HasColumnType("datetime(6)"); + + b.Property("CoupleDayLevel") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("CoupleDayLevelCountry") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("CoupleDayName") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("CoupleDayPosition") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("CoupleDayStartDateHistory") + .HasColumnType("datetime(6)"); + + b.Property("CoupleDaySumTotalHistory") + .HasColumnType("longtext"); + + b.Property("CoupleDayTotalHistory") + .HasColumnType("longtext"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)") + .HasColumnOrder(100) + .HasComment("สร้างข้อมูลเมื่อ"); + + b.Property("CreatedFullName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)") + .HasColumnOrder(104) + .HasComment("ชื่อ User ที่สร้างข้อมูล"); + + b.Property("CreatedUserId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("varchar(40)") + .HasColumnOrder(101) + .HasComment("User Id ที่สร้างข้อมูล"); + + b.Property("DateAppoint") + .HasColumnType("datetime(6)"); + + b.Property("Dear") + .HasColumnType("longtext") + .HasComment("เรียนใคร"); + + b.Property("FirstName") + .HasColumnType("longtext"); + + b.Property("Gender") + .HasColumnType("longtext"); + + b.Property("HajjDayStatus") + .HasColumnType("tinyint(1)"); + + b.Property("KeycloakUserId") + .HasColumnType("char(36)"); + + b.Property("LastName") + .HasColumnType("longtext"); + + b.Property("LastUpdateFullName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)") + .HasColumnOrder(105) + .HasComment("ชื่อ User ที่แก้ไขข้อมูลล่าสุด"); + + b.Property("LastUpdateUserId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("varchar(40)") + .HasColumnOrder(103) + .HasComment("User Id ที่แก้ไขข้อมูลล่าสุด"); + + b.Property("LastUpdatedAt") + .HasColumnType("datetime(6)") + .HasColumnOrder(102) + .HasComment("แก้ไขข้อมูลล่าสุดเมื่อ"); + + b.Property("LeaveAddress") + .IsRequired() + .HasColumnType("longtext") + .HasComment("สถานที่ติดต่อขณะลา"); + + b.Property("LeaveBirthDate") + .HasColumnType("datetime(6)"); + + b.Property("LeaveCancelComment") + .HasColumnType("longtext") + .HasComment("เหตุผลในการขอยกเลิก"); + + b.Property("LeaveCancelDocumentId") + .HasColumnType("char(36)"); + + b.Property("LeaveCancelStatus") + .HasColumnType("longtext") + .HasComment("สถานะของคำขอยกเลิก"); + + b.Property("LeaveComment") + .HasColumnType("longtext") + .HasComment("ความเห็นของผู้บังคับบัญชา"); + + b.Property("LeaveDetail") + .IsRequired() + .HasColumnType("longtext") + .HasComment("รายละเอียดการลา"); + + b.Property("LeaveDirectorComment") + .HasColumnType("longtext") + .HasComment("ความเห็นของผู้อำนวยการสำนัก"); + + b.Property("LeaveDraftDocumentId") + .HasColumnType("char(36)"); + + b.Property("LeaveEndDate") + .HasColumnType("datetime(6)") + .HasComment("วัน เดือน ปีสิ้นสุดลา"); + + b.Property("LeaveGovernmentDate") + .HasColumnType("datetime(6)"); + + b.Property("LeaveLast") + .HasColumnType("datetime(6)"); + + b.Property("LeaveNumber") + .IsRequired() + .HasColumnType("longtext") + .HasComment("หมายเลขที่ติดต่อขณะลา"); + + b.Property("LeaveRange") + .HasColumnType("longtext") + .HasComment("ช่วงของการลาของวันเริ่ม เช่น ลาทั้งวัน ครึ่งวันเช้า ครึ่งวันบ่าย"); + + b.Property("LeaveRangeEnd") + .HasColumnType("longtext") + .HasComment("ช่วงของการลาของวันสิ้นสุด เช่น ลาทั้งวัน ครึ่งวันเช้า ครึ่งวันบ่าย"); + + b.Property("LeaveSalary") + .HasColumnType("int"); + + b.Property("LeaveSalaryText") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("LeaveStartDate") + .HasColumnType("datetime(6)") + .HasComment("วัน เดือน ปีเริ่มต้นลา"); + + b.Property("LeaveStatus") + .IsRequired() + .HasColumnType("longtext") + .HasComment("สถานะของคำร้อง"); + + b.Property("LeaveSubTypeName") + .HasColumnType("longtext"); + + b.Property("LeaveTotal") + .HasColumnType("double"); + + b.Property("LeaveTypeCode") + .HasColumnType("longtext") + .HasComment("code ของประเภทการลา"); + + b.Property("LeaveWrote") + .IsRequired() + .HasColumnType("longtext") + .HasComment("เขียนที่"); + + b.Property("OrdainDayBuddhistLentAddress") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("OrdainDayBuddhistLentName") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("OrdainDayLocationAddress") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("OrdainDayLocationName") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("OrdainDayLocationNumber") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("OrdainDayOrdination") + .HasColumnType("datetime(6)"); + + b.Property("OrdainDayStatus") + .HasColumnType("tinyint(1)"); + + b.Property("OrganizationName") + .HasColumnType("longtext") + .HasComment("สังกัดผู้ยื่นขอ"); + + b.Property("PositionLevelName") + .HasColumnType("longtext") + .HasComment("ระดับผู้ยื่นขอ"); + + b.Property("PositionName") + .HasColumnType("longtext") + .HasComment("ตำแหน่งผู้ยื่นขอ"); + + b.Property("Prefix") + .HasColumnType("longtext"); + + b.Property("ProfileId") + .HasColumnType("char(36)"); + + b.Property("ProfileType") + .HasColumnType("longtext"); + + b.Property("RestDayCurrentTotal") + .HasColumnType("double"); + + b.Property("RestDayOldTotal") + .HasColumnType("double"); + + b.Property("Root") + .HasColumnType("longtext"); + + b.Property("RootDnaId") + .HasColumnType("char(36)"); + + b.Property("RootId") + .HasColumnType("char(36)"); + + b.Property("StudyDayCountry") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("StudyDayDegreeLevel") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("StudyDayScholarship") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("StudyDaySubject") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("StudyDayTrainingName") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("StudyDayTrainingSubject") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("StudyDayUniversityName") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("TypeId") + .HasColumnType("char(36)"); + + b.Property("WifeDayDateBorn") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("WifeDayName") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("LeaveCancelDocumentId"); + + b.HasIndex("LeaveDraftDocumentId"); + + b.HasIndex("TypeId"); + + b.ToTable("LeaveRequests"); + }); + + modelBuilder.Entity("BMA.EHR.Domain.Models.Leave.Requests.LeaveRequestApprover", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)") + .HasColumnOrder(0) + .HasComment("PrimaryKey") + .HasAnnotation("Relational:JsonPropertyName", "id"); + + b.Property("ApproveStatus") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("ApproveType") + .HasColumnType("longtext"); + + b.Property("Comment") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)") + .HasColumnOrder(100) + .HasComment("สร้างข้อมูลเมื่อ"); + + b.Property("CreatedFullName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)") + .HasColumnOrder(104) + .HasComment("ชื่อ User ที่สร้างข้อมูล"); + + b.Property("CreatedUserId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("varchar(40)") + .HasColumnOrder(101) + .HasComment("User Id ที่สร้างข้อมูล"); + + b.Property("FirstName") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("KeycloakId") + .HasColumnType("char(36)"); + + b.Property("LastName") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("LastUpdateFullName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)") + .HasColumnOrder(105) + .HasComment("ชื่อ User ที่แก้ไขข้อมูลล่าสุด"); + + b.Property("LastUpdateUserId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("varchar(40)") + .HasColumnOrder(103) + .HasComment("User Id ที่แก้ไขข้อมูลล่าสุด"); + + b.Property("LastUpdatedAt") + .HasColumnType("datetime(6)") + .HasColumnOrder(102) + .HasComment("แก้ไขข้อมูลล่าสุดเมื่อ"); + + b.Property("LeaveRequestId") + .HasColumnType("char(36)"); + + b.Property("OrganizationName") + .IsRequired() + .HasColumnType("longtext") + .HasComment("สังกัด"); + + b.Property("PosExecutiveName") + .IsRequired() + .HasColumnType("longtext") + .HasComment("ตำแหน่งทางการบริหาร"); + + b.Property("PositionLevelName") + .IsRequired() + .HasColumnType("longtext") + .HasComment("ประเภทระดับตำแหน่ง"); + + b.Property("PositionName") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("PositionSign") + .HasColumnType("longtext") + .HasComment("ตำแหน่งใต้ลายเช็นต์"); + + b.Property("Prefix") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("ProfileId") + .HasColumnType("char(36)"); + + b.Property("Seq") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("LeaveRequestId"); + + b.ToTable("LeaveRequestApprovers"); + }); + + modelBuilder.Entity("BMA.EHR.Domain.Models.Leave.TimeAttendants.AdditionalCheckRequest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)") + .HasColumnOrder(0) + .HasComment("PrimaryKey") + .HasAnnotation("Relational:JsonPropertyName", "id"); + + b.Property("CheckDate") + .HasColumnType("datetime(6)") + .HasComment("*วันที่ลงเวลา"); + + b.Property("CheckInEdit") + .HasColumnType("tinyint(1)") + .HasComment("*ขอลงเวลาช่วงเช้า"); + + b.Property("CheckOutEdit") + .HasColumnType("tinyint(1)") + .HasComment("*ขอลงเวลาช่วงบ่าย"); + + b.Property("Child1DnaId") + .HasColumnType("char(36)"); + + b.Property("Child2DnaId") + .HasColumnType("char(36)"); + + b.Property("Child3DnaId") + .HasColumnType("char(36)"); + + b.Property("Child4DnaId") + .HasColumnType("char(36)"); + + b.Property("Comment") + .HasColumnType("longtext") + .HasComment("หมายเหตุในการการอนุมัติ/ไม่อนุมัติ"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)") + .HasColumnOrder(100) + .HasComment("สร้างข้อมูลเมื่อ"); + + b.Property("CreatedFullName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)") + .HasColumnOrder(104) + .HasComment("ชื่อ User ที่สร้างข้อมูล"); + + b.Property("CreatedUserId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("varchar(40)") + .HasColumnOrder(101) + .HasComment("User Id ที่สร้างข้อมูล"); + + b.Property("Description") + .IsRequired() + .HasColumnType("longtext") + .HasComment("*หมายเหตุขอลงเวลาพิเศษ"); + + b.Property("FirstName") + .HasColumnType("longtext"); + + b.Property("KeycloakUserId") + .HasColumnType("char(36)") + .HasComment("รหัส User ของ Keycloak ที่ร้องขอ"); + + b.Property("LastName") + .HasColumnType("longtext"); + + b.Property("LastUpdateFullName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)") + .HasColumnOrder(105) + .HasComment("ชื่อ User ที่แก้ไขข้อมูลล่าสุด"); + + b.Property("LastUpdateUserId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("varchar(40)") + .HasColumnOrder(103) + .HasComment("User Id ที่แก้ไขข้อมูลล่าสุด"); + + b.Property("LastUpdatedAt") + .HasColumnType("datetime(6)") + .HasColumnOrder(102) + .HasComment("แก้ไขข้อมูลล่าสุดเมื่อ"); + + b.Property("Latitude") + .HasColumnType("double"); + + b.Property("Longitude") + .HasColumnType("double"); + + b.Property("POI") + .HasColumnType("longtext"); + + b.Property("Prefix") + .HasColumnType("longtext"); + + b.Property("RootDnaId") + .HasColumnType("char(36)"); + + b.Property("Status") + .IsRequired() + .HasColumnType("longtext") + .HasComment("สถานะการอนุมัติ"); + + b.HasKey("Id"); + + b.ToTable("AdditionalCheckRequests"); + }); + + modelBuilder.Entity("BMA.EHR.Domain.Models.Leave.TimeAttendants.CheckInJobStatus", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)") + .HasColumnOrder(0) + .HasComment("PrimaryKey") + .HasAnnotation("Relational:JsonPropertyName", "id"); + + b.Property("AdditionalData") + .HasColumnType("longtext") + .HasComment("ข้อมูลเพิ่มเติม (JSON)"); + + b.Property("CheckInId") + .HasColumnType("char(36)") + .HasComment("CheckInId สำหรับ Check-Out"); + + b.Property("CheckType") + .HasColumnType("longtext") + .HasComment("ประเภทการลงเวลา: CHECK_IN, CHECK_OUT"); + + b.Property("CompletedDate") + .HasColumnType("datetime(6)") + .HasComment("วันเวลาที่เสร็จสิ้นการประมวลผล"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)") + .HasColumnOrder(100) + .HasComment("สร้างข้อมูลเมื่อ"); + + b.Property("CreatedDate") + .HasColumnType("datetime(6)") + .HasComment("วันเวลาที่สร้างงาน"); + + b.Property("CreatedFullName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)") + .HasColumnOrder(104) + .HasComment("ชื่อ User ที่สร้างข้อมูล"); + + b.Property("CreatedUserId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("varchar(40)") + .HasColumnOrder(101) + .HasComment("User Id ที่สร้างข้อมูล"); + + b.Property("ErrorMessage") + .HasColumnType("longtext") + .HasComment("ข้อความแสดงข้อผิดพลาด"); + + b.Property("KeycloakUserId") + .HasColumnType("char(36)") + .HasComment("รหัส User ของ Keycloak"); + + b.Property("LastUpdateFullName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)") + .HasColumnOrder(105) + .HasComment("ชื่อ User ที่แก้ไขข้อมูลล่าสุด"); + + b.Property("LastUpdateUserId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("varchar(40)") + .HasColumnOrder(103) + .HasComment("User Id ที่แก้ไขข้อมูลล่าสุด"); + + b.Property("LastUpdatedAt") + .HasColumnType("datetime(6)") + .HasColumnOrder(102) + .HasComment("แก้ไขข้อมูลล่าสุดเมื่อ"); + + b.Property("ProcessingDate") + .HasColumnType("datetime(6)") + .HasComment("วันเวลาที่เริ่มประมวลผล"); + + b.Property("Status") + .IsRequired() + .HasColumnType("longtext") + .HasComment("สถานะงาน: PENDING, PROCESSING, COMPLETED, FAILED"); + + b.Property("TaskId") + .HasColumnType("char(36)") + .HasComment("Task ID สำหรับติดตามสถานะงาน"); + + b.HasKey("Id"); + + b.ToTable("CheckInJobStatuses"); + }); + + modelBuilder.Entity("BMA.EHR.Domain.Models.Leave.TimeAttendants.DutyTime", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)") + .HasColumnOrder(0) + .HasComment("PrimaryKey") + .HasAnnotation("Relational:JsonPropertyName", "id"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)") + .HasColumnOrder(100) + .HasComment("สร้างข้อมูลเมื่อ"); + + b.Property("CreatedFullName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)") + .HasColumnOrder(104) + .HasComment("ชื่อ User ที่สร้างข้อมูล"); + + b.Property("CreatedUserId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("varchar(40)") + .HasColumnOrder(101) + .HasComment("User Id ที่สร้างข้อมูล"); + + b.Property("Description") + .IsRequired() + .HasColumnType("longtext") + .HasComment("คำอธิบาย"); + + b.Property("EndTimeAfternoon") + .IsRequired() + .HasColumnType("longtext") + .HasComment("เวลาออกงานช่วงบ่าย"); + + b.Property("EndTimeMorning") + .IsRequired() + .HasColumnType("longtext") + .HasComment("เวลาออกงานช่วงเช้า"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)") + .HasComment("สถานะการเปิดใช้งาน (เปิด/ปิด)"); + + b.Property("IsDefault") + .HasColumnType("tinyint(1)") + .HasComment("สถานะว่ารอบใดเป็นค่า Default ของข้าราชการ (สำหรับทุกคนที่ยังไม่ได้ทำการเลือกรอบ)"); + + b.Property("LastUpdateFullName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)") + .HasColumnOrder(105) + .HasComment("ชื่อ User ที่แก้ไขข้อมูลล่าสุด"); + + b.Property("LastUpdateUserId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("varchar(40)") + .HasColumnOrder(103) + .HasComment("User Id ที่แก้ไขข้อมูลล่าสุด"); + + b.Property("LastUpdatedAt") + .HasColumnType("datetime(6)") + .HasColumnOrder(102) + .HasComment("แก้ไขข้อมูลล่าสุดเมื่อ"); + + b.Property("StartTimeAfternoon") + .IsRequired() + .HasColumnType("longtext") + .HasComment("เวลาเข้างานช่วงบ่าย"); + + b.Property("StartTimeMorning") + .IsRequired() + .HasColumnType("longtext") + .HasComment("เวลาเข้างานช่วงเช้า"); + + b.HasKey("Id"); + + b.ToTable("DutyTimes"); + }); + + modelBuilder.Entity("BMA.EHR.Domain.Models.Leave.TimeAttendants.ProcessUserTimeStamp", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)") + .HasColumnOrder(0) + .HasComment("PrimaryKey") + .HasAnnotation("Relational:JsonPropertyName", "id"); + + b.Property("CheckIn") + .HasColumnType("datetime(6)") + .HasComment("วัน เวลา เข้างาน"); + + b.Property("CheckInImageUrl") + .IsRequired() + .HasColumnType("longtext") + .HasComment("รูปถ่ายสถานที่ Check-In"); + + b.Property("CheckInLat") + .HasColumnType("double") + .HasComment("พิกัดละติจูด Check-In"); + + b.Property("CheckInLocationName") + .HasColumnType("longtext") + .HasComment("กรณีเลือกนอกสถานที่ตั้ง ต้องระบุข้อมูลชื่อสถานะที่ Check-In"); + + b.Property("CheckInLon") + .HasColumnType("double") + .HasComment("พิกัดลองจิจูด Check-In"); + + b.Property("CheckInPOI") + .IsRequired() + .HasColumnType("longtext") + .HasComment("ชื่อสถานที่ ได้มาจากระบบ ArcGis ของกองสารสนเทศภูมิศาสตร์ Check-In"); + + b.Property("CheckInRemark") + .HasColumnType("longtext") + .HasComment("ข้อความหมายเหตุที่ต้องการระบุเพิ่ม(มีเผื่อไว้อาจไม่ได้ใช้) Check-In"); + + b.Property("CheckInStatus") + .IsRequired() + .HasColumnType("longtext") + .HasComment("สถานะ Check-In"); + + b.Property("CheckOut") + .HasColumnType("datetime(6)") + .HasComment("วัน เวลา ออกงาน"); + + b.Property("CheckOutImageUrl") + .IsRequired() + .HasColumnType("longtext") + .HasComment("รูปถ่ายสถานที่ Check-Out"); + + b.Property("CheckOutLat") + .HasColumnType("double") + .HasComment("พิกัดละติจูด Check-Out"); + + b.Property("CheckOutLocationName") + .HasColumnType("longtext") + .HasComment("กรณีเลือกนอกสถานที่ตั้ง ต้องระบุข้อมูลชื่อสถานะที่ Check-Out"); + + b.Property("CheckOutLon") + .HasColumnType("double") + .HasComment("พิกัดลองจิจูด Check-Out"); + + b.Property("CheckOutPOI") + .IsRequired() + .HasColumnType("longtext") + .HasComment("ชื่อสถานที่ ได้มาจากระบบ ArcGis ของกองสารสนเทศภูมิศาสตร์ Check-Out"); + + b.Property("CheckOutRemark") + .HasColumnType("longtext") + .HasComment("ข้อความหมายเหตุที่ต้องการระบุเพิ่ม(มีเผื่อไว้อาจไม่ได้ใช้) Check-Out"); + + b.Property("CheckOutStatus") + .HasColumnType("longtext") + .HasComment("สถานะ Check-Out"); + + b.Property("Child1") + .HasColumnType("longtext"); + + b.Property("Child1DnaId") + .HasColumnType("char(36)"); + + b.Property("Child1Id") + .HasColumnType("char(36)"); + + b.Property("Child2") + .HasColumnType("longtext"); + + b.Property("Child2DnaId") + .HasColumnType("char(36)"); + + b.Property("Child2Id") + .HasColumnType("char(36)"); + + b.Property("Child3") + .HasColumnType("longtext"); + + b.Property("Child3DnaId") + .HasColumnType("char(36)"); + + b.Property("Child3Id") + .HasColumnType("char(36)"); + + b.Property("Child4") + .HasColumnType("longtext"); + + b.Property("Child4DnaId") + .HasColumnType("char(36)"); + + b.Property("Child4Id") + .HasColumnType("char(36)"); + + b.Property("CitizenId") + .HasColumnType("longtext"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)") + .HasColumnOrder(100) + .HasComment("สร้างข้อมูลเมื่อ"); + + b.Property("CreatedFullName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)") + .HasColumnOrder(104) + .HasComment("ชื่อ User ที่สร้างข้อมูล"); + + b.Property("CreatedUserId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("varchar(40)") + .HasColumnOrder(101) + .HasComment("User Id ที่สร้างข้อมูล"); + + b.Property("EditReason") + .HasColumnType("longtext") + .HasComment("เหตุผลการอนุมัติ/ไม่อนุมัติขอลงเวลาพิเศษ"); + + b.Property("EditStatus") + .HasColumnType("longtext") + .HasComment("สถานะการของลงเวลาพิเศษ"); + + b.Property("FirstName") + .HasColumnType("longtext"); + + b.Property("Gender") + .HasColumnType("longtext"); + + b.Property("IsLocationCheckIn") + .HasColumnType("tinyint(1)") + .HasComment("true คือ ณ สถานที่ตั้ง, false คือ นอกสถานที่ตั้ง Check-In"); + + b.Property("IsLocationCheckOut") + .HasColumnType("tinyint(1)") + .HasComment("true คือ ณ สถานที่ตั้ง, false คือ นอกสถานที่ตั้ง Check-Out"); + + b.Property("IsProcess") + .HasColumnType("tinyint(1)") + .HasComment("นำไปประมวลผลแล้วหรือยัง"); + + b.Property("KeycloakUserId") + .HasColumnType("char(36)") + .HasComment("รหัส User ของ Keycloak"); + + b.Property("LastName") + .HasColumnType("longtext"); + + b.Property("LastUpdateFullName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)") + .HasColumnOrder(105) + .HasComment("ชื่อ User ที่แก้ไขข้อมูลล่าสุด"); + + b.Property("LastUpdateUserId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("varchar(40)") + .HasColumnOrder(103) + .HasComment("User Id ที่แก้ไขข้อมูลล่าสุด"); + + b.Property("LastUpdatedAt") + .HasColumnType("datetime(6)") + .HasColumnOrder(102) + .HasComment("แก้ไขข้อมูลล่าสุดเมื่อ"); + + b.Property("Prefix") + .HasColumnType("longtext"); + + b.Property("ProfileId") + .HasColumnType("char(36)"); + + b.Property("ProfileType") + .HasColumnType("longtext"); + + b.Property("Root") + .HasColumnType("longtext"); + + b.Property("RootDnaId") + .HasColumnType("char(36)"); + + b.Property("RootId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.ToTable("ProcessUserTimeStamps"); + }); + + modelBuilder.Entity("BMA.EHR.Domain.Models.Leave.TimeAttendants.UserCalendar", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)") + .HasColumnOrder(0) + .HasComment("PrimaryKey") + .HasAnnotation("Relational:JsonPropertyName", "id"); + + b.Property("Calendar") + .IsRequired() + .HasColumnType("longtext") + .HasComment("ปฏิทินการทำงานของ ขรก ปกติ หรือ 6 วันต่อสัปดาห์"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)") + .HasColumnOrder(100) + .HasComment("สร้างข้อมูลเมื่อ"); + + b.Property("CreatedFullName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)") + .HasColumnOrder(104) + .HasComment("ชื่อ User ที่สร้างข้อมูล"); + + b.Property("CreatedUserId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("varchar(40)") + .HasColumnOrder(101) + .HasComment("User Id ที่สร้างข้อมูล"); + + b.Property("LastUpdateFullName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)") + .HasColumnOrder(105) + .HasComment("ชื่อ User ที่แก้ไขข้อมูลล่าสุด"); + + b.Property("LastUpdateUserId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("varchar(40)") + .HasColumnOrder(103) + .HasComment("User Id ที่แก้ไขข้อมูลล่าสุด"); + + b.Property("LastUpdatedAt") + .HasColumnType("datetime(6)") + .HasColumnOrder(102) + .HasComment("แก้ไขข้อมูลล่าสุดเมื่อ"); + + b.Property("ProfileId") + .HasColumnType("char(36)") + .HasComment("รหัส Profile ในระบบทะเบียนประวัติ"); + + b.HasKey("Id"); + + b.ToTable("UserCalendars"); + }); + + modelBuilder.Entity("BMA.EHR.Domain.Models.Leave.TimeAttendants.UserDutyTime", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)") + .HasColumnOrder(0) + .HasComment("PrimaryKey") + .HasAnnotation("Relational:JsonPropertyName", "id"); + + b.Property("Child1DnaId") + .HasColumnType("char(36)"); + + b.Property("Child2DnaId") + .HasColumnType("char(36)"); + + b.Property("Child3DnaId") + .HasColumnType("char(36)"); + + b.Property("Child4DnaId") + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)") + .HasColumnOrder(100) + .HasComment("สร้างข้อมูลเมื่อ"); + + b.Property("CreatedFullName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)") + .HasColumnOrder(104) + .HasComment("ชื่อ User ที่สร้างข้อมูล"); + + b.Property("CreatedUserId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("varchar(40)") + .HasColumnOrder(101) + .HasComment("User Id ที่สร้างข้อมูล"); + + b.Property("DutyTimeId") + .HasColumnType("char(36)") + .HasComment("รหัสรอบการลงเวลา"); + + b.Property("EffectiveDate") + .HasColumnType("datetime(6)") + .HasComment("วันที่มีผล"); + + b.Property("IsProcess") + .HasColumnType("tinyint(1)") + .HasComment("ทำการประมวลผลแล้วหรือยัง"); + + b.Property("LastUpdateFullName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)") + .HasColumnOrder(105) + .HasComment("ชื่อ User ที่แก้ไขข้อมูลล่าสุด"); + + b.Property("LastUpdateUserId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("varchar(40)") + .HasColumnOrder(103) + .HasComment("User Id ที่แก้ไขข้อมูลล่าสุด"); + + b.Property("LastUpdatedAt") + .HasColumnType("datetime(6)") + .HasColumnOrder(102) + .HasComment("แก้ไขข้อมูลล่าสุดเมื่อ"); + + b.Property("ProfileId") + .HasColumnType("char(36)") + .HasComment("รหัส Profile ในระบบทะเบียนประวัติ"); + + b.Property("Remark") + .HasColumnType("longtext") + .HasComment("หมายเหตุ"); + + b.Property("RootDnaId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("DutyTimeId"); + + b.ToTable("UserDutyTimes"); + }); + + modelBuilder.Entity("BMA.EHR.Domain.Models.Leave.TimeAttendants.UserTimeStamp", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)") + .HasColumnOrder(0) + .HasComment("PrimaryKey") + .HasAnnotation("Relational:JsonPropertyName", "id"); + + b.Property("CheckIn") + .HasColumnType("datetime(6)") + .HasComment("วัน เวลา เข้างาน"); + + b.Property("CheckInImageUrl") + .IsRequired() + .HasColumnType("longtext") + .HasComment("รูปถ่ายสถานที่ Check-In"); + + b.Property("CheckInLat") + .HasColumnType("double") + .HasComment("พิกัดละติจูด Check-In"); + + b.Property("CheckInLocationName") + .HasColumnType("longtext") + .HasComment("กรณีเลือกนอกสถานที่ตั้ง ต้องระบุข้อมูลชื่อสถานะที่ Check-In"); + + b.Property("CheckInLon") + .HasColumnType("double") + .HasComment("พิกัดลองจิจูด Check-In"); + + b.Property("CheckInPOI") + .IsRequired() + .HasColumnType("longtext") + .HasComment("ชื่อสถานที่ ได้มาจากระบบ ArcGis ของกองสารสนเทศภูมิศาสตร์ Check-In"); + + b.Property("CheckInRemark") + .HasColumnType("longtext") + .HasComment("ข้อความหมายเหตุที่ต้องการระบุเพิ่ม(มีเผื่อไว้อาจไม่ได้ใช้) Check-In"); + + b.Property("CheckOut") + .HasColumnType("datetime(6)") + .HasComment("วัน เวลา ออกงาน"); + + b.Property("CheckOutImageUrl") + .IsRequired() + .HasColumnType("longtext") + .HasComment("รูปถ่ายสถานที่ Check-Out"); + + b.Property("CheckOutLat") + .HasColumnType("double") + .HasComment("พิกัดละติจูด Check-Out"); + + b.Property("CheckOutLocationName") + .HasColumnType("longtext") + .HasComment("กรณีเลือกนอกสถานที่ตั้ง ต้องระบุข้อมูลชื่อสถานะที่ Check-Out"); + + b.Property("CheckOutLon") + .HasColumnType("double") + .HasComment("พิกัดลองจิจูด Check-Out"); + + b.Property("CheckOutPOI") + .IsRequired() + .HasColumnType("longtext") + .HasComment("ชื่อสถานที่ ได้มาจากระบบ ArcGis ของกองสารสนเทศภูมิศาสตร์ Check-Out"); + + b.Property("CheckOutRemark") + .HasColumnType("longtext") + .HasComment("ข้อความหมายเหตุที่ต้องการระบุเพิ่ม(มีเผื่อไว้อาจไม่ได้ใช้) Check-Out"); + + b.Property("Child1") + .HasColumnType("longtext"); + + b.Property("Child1DnaId") + .HasColumnType("char(36)"); + + b.Property("Child1Id") + .HasColumnType("char(36)"); + + b.Property("Child2") + .HasColumnType("longtext"); + + b.Property("Child2DnaId") + .HasColumnType("char(36)"); + + b.Property("Child2Id") + .HasColumnType("char(36)"); + + b.Property("Child3") + .HasColumnType("longtext"); + + b.Property("Child3DnaId") + .HasColumnType("char(36)"); + + b.Property("Child3Id") + .HasColumnType("char(36)"); + + b.Property("Child4") + .HasColumnType("longtext"); + + b.Property("Child4DnaId") + .HasColumnType("char(36)"); + + b.Property("Child4Id") + .HasColumnType("char(36)"); + + b.Property("CitizenId") + .HasColumnType("longtext"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)") + .HasColumnOrder(100) + .HasComment("สร้างข้อมูลเมื่อ"); + + b.Property("CreatedFullName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)") + .HasColumnOrder(104) + .HasComment("ชื่อ User ที่สร้างข้อมูล"); + + b.Property("CreatedUserId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("varchar(40)") + .HasColumnOrder(101) + .HasComment("User Id ที่สร้างข้อมูล"); + + b.Property("FirstName") + .HasColumnType("longtext"); + + b.Property("Gender") + .HasColumnType("longtext"); + + b.Property("IsLocationCheckIn") + .HasColumnType("tinyint(1)") + .HasComment("true คือ ณ สถานที่ตั้ง, false คือ นอกสถานที่ตั้ง Check-In"); + + b.Property("IsLocationCheckOut") + .HasColumnType("tinyint(1)") + .HasComment("true คือ ณ สถานที่ตั้ง, false คือ นอกสถานที่ตั้ง Check-Out"); + + b.Property("IsProcess") + .HasColumnType("tinyint(1)") + .HasComment("นำไปประมวลผลแล้วหรือยัง"); + + b.Property("KeycloakUserId") + .HasColumnType("char(36)") + .HasComment("รหัส User ของ Keycloak"); + + b.Property("LastName") + .HasColumnType("longtext"); + + b.Property("LastUpdateFullName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)") + .HasColumnOrder(105) + .HasComment("ชื่อ User ที่แก้ไขข้อมูลล่าสุด"); + + b.Property("LastUpdateUserId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("varchar(40)") + .HasColumnOrder(103) + .HasComment("User Id ที่แก้ไขข้อมูลล่าสุด"); + + b.Property("LastUpdatedAt") + .HasColumnType("datetime(6)") + .HasColumnOrder(102) + .HasComment("แก้ไขข้อมูลล่าสุดเมื่อ"); + + b.Property("Prefix") + .HasColumnType("longtext"); + + b.Property("ProfileId") + .HasColumnType("char(36)"); + + b.Property("ProfileType") + .HasColumnType("longtext"); + + b.Property("Root") + .HasColumnType("longtext"); + + b.Property("RootDnaId") + .HasColumnType("char(36)"); + + b.Property("RootId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.ToTable("UserTimeStamps"); + }); + + modelBuilder.Entity("BMA.EHR.Domain.Models.Leave.Requests.LeaveBeginning", b => + { + b.HasOne("BMA.EHR.Domain.Models.Leave.Commons.LeaveType", "LeaveType") + .WithMany() + .HasForeignKey("LeaveTypeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("LeaveType"); + }); + + modelBuilder.Entity("BMA.EHR.Domain.Models.Leave.Requests.LeaveDocument", b => + { + b.HasOne("BMA.EHR.Domain.Models.Documents.Document", "Document") + .WithMany() + .HasForeignKey("DocumentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("BMA.EHR.Domain.Models.Leave.Requests.LeaveRequest", "LeaveRequest") + .WithMany("LeaveDocument") + .HasForeignKey("LeaveRequestId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Document"); + + b.Navigation("LeaveRequest"); + }); + + modelBuilder.Entity("BMA.EHR.Domain.Models.Leave.Requests.LeaveRequest", b => + { + b.HasOne("BMA.EHR.Domain.Models.Documents.Document", "LeaveCancelDocument") + .WithMany() + .HasForeignKey("LeaveCancelDocumentId"); + + b.HasOne("BMA.EHR.Domain.Models.Documents.Document", "LeaveDraftDocument") + .WithMany() + .HasForeignKey("LeaveDraftDocumentId"); + + b.HasOne("BMA.EHR.Domain.Models.Leave.Commons.LeaveType", "Type") + .WithMany() + .HasForeignKey("TypeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("LeaveCancelDocument"); + + b.Navigation("LeaveDraftDocument"); + + b.Navigation("Type"); + }); + + modelBuilder.Entity("BMA.EHR.Domain.Models.Leave.Requests.LeaveRequestApprover", b => + { + b.HasOne("BMA.EHR.Domain.Models.Leave.Requests.LeaveRequest", "LeaveRequest") + .WithMany("Approvers") + .HasForeignKey("LeaveRequestId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("LeaveRequest"); + }); + + modelBuilder.Entity("BMA.EHR.Domain.Models.Leave.TimeAttendants.UserDutyTime", b => + { + b.HasOne("BMA.EHR.Domain.Models.Leave.TimeAttendants.DutyTime", "DutyTime") + .WithMany() + .HasForeignKey("DutyTimeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("DutyTime"); + }); + + modelBuilder.Entity("BMA.EHR.Domain.Models.Leave.Requests.LeaveRequest", b => + { + b.Navigation("Approvers"); + + b.Navigation("LeaveDocument"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/BMA.EHR.Infrastructure/Migrations/LeaveDb/20260120032158_Add RMQ Task Control.cs b/BMA.EHR.Infrastructure/Migrations/LeaveDb/20260120032158_Add RMQ Task Control.cs new file mode 100644 index 00000000..f8d2090b --- /dev/null +++ b/BMA.EHR.Infrastructure/Migrations/LeaveDb/20260120032158_Add RMQ Task Control.cs @@ -0,0 +1,58 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace BMA.EHR.Infrastructure.Migrations.LeaveDb +{ + /// + public partial class AddRMQTaskControl : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "CheckInJobStatuses", + columns: table => new + { + Id = table.Column(type: "char(36)", nullable: false, comment: "PrimaryKey", collation: "ascii_general_ci"), + CreatedAt = table.Column(type: "datetime(6)", nullable: false, comment: "สร้างข้อมูลเมื่อ"), + CreatedUserId = table.Column(type: "varchar(40)", maxLength: 40, nullable: false, comment: "User Id ที่สร้างข้อมูล") + .Annotation("MySql:CharSet", "utf8mb4"), + LastUpdatedAt = table.Column(type: "datetime(6)", nullable: true, comment: "แก้ไขข้อมูลล่าสุดเมื่อ"), + LastUpdateUserId = table.Column(type: "varchar(40)", maxLength: 40, nullable: false, comment: "User Id ที่แก้ไขข้อมูลล่าสุด") + .Annotation("MySql:CharSet", "utf8mb4"), + CreatedFullName = table.Column(type: "varchar(200)", maxLength: 200, nullable: false, comment: "ชื่อ User ที่สร้างข้อมูล") + .Annotation("MySql:CharSet", "utf8mb4"), + LastUpdateFullName = table.Column(type: "varchar(200)", maxLength: 200, nullable: false, comment: "ชื่อ User ที่แก้ไขข้อมูลล่าสุด") + .Annotation("MySql:CharSet", "utf8mb4"), + TaskId = table.Column(type: "char(36)", nullable: false, comment: "Task ID สำหรับติดตามสถานะงาน", collation: "ascii_general_ci"), + KeycloakUserId = table.Column(type: "char(36)", nullable: false, comment: "รหัส User ของ Keycloak", collation: "ascii_general_ci"), + CreatedDate = table.Column(type: "datetime(6)", nullable: false, comment: "วันเวลาที่สร้างงาน"), + ProcessingDate = table.Column(type: "datetime(6)", nullable: true, comment: "วันเวลาที่เริ่มประมวลผล"), + CompletedDate = table.Column(type: "datetime(6)", nullable: true, comment: "วันเวลาที่เสร็จสิ้นการประมวลผล"), + Status = table.Column(type: "longtext", nullable: false, comment: "สถานะงาน: PENDING, PROCESSING, COMPLETED, FAILED") + .Annotation("MySql:CharSet", "utf8mb4"), + CheckType = table.Column(type: "longtext", nullable: true, comment: "ประเภทการลงเวลา: CHECK_IN, CHECK_OUT") + .Annotation("MySql:CharSet", "utf8mb4"), + CheckInId = table.Column(type: "char(36)", nullable: true, comment: "CheckInId สำหรับ Check-Out", collation: "ascii_general_ci"), + ErrorMessage = table.Column(type: "longtext", nullable: true, comment: "ข้อความแสดงข้อผิดพลาด") + .Annotation("MySql:CharSet", "utf8mb4"), + AdditionalData = table.Column(type: "longtext", nullable: true, comment: "ข้อมูลเพิ่มเติม (JSON)") + .Annotation("MySql:CharSet", "utf8mb4") + }, + constraints: table => + { + table.PrimaryKey("PK_CheckInJobStatuses", x => x.Id); + }) + .Annotation("MySql:CharSet", "utf8mb4"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "CheckInJobStatuses"); + } + } +} diff --git a/BMA.EHR.Infrastructure/Migrations/LeaveDb/LeaveDbContextModelSnapshot.cs b/BMA.EHR.Infrastructure/Migrations/LeaveDb/LeaveDbContextModelSnapshot.cs index 14e45995..d12bf747 100644 --- a/BMA.EHR.Infrastructure/Migrations/LeaveDb/LeaveDbContextModelSnapshot.cs +++ b/BMA.EHR.Infrastructure/Migrations/LeaveDb/LeaveDbContextModelSnapshot.cs @@ -882,6 +882,99 @@ namespace BMA.EHR.Infrastructure.Migrations.LeaveDb b.ToTable("AdditionalCheckRequests"); }); + modelBuilder.Entity("BMA.EHR.Domain.Models.Leave.TimeAttendants.CheckInJobStatus", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)") + .HasColumnOrder(0) + .HasComment("PrimaryKey") + .HasAnnotation("Relational:JsonPropertyName", "id"); + + b.Property("AdditionalData") + .HasColumnType("longtext") + .HasComment("ข้อมูลเพิ่มเติม (JSON)"); + + b.Property("CheckInId") + .HasColumnType("char(36)") + .HasComment("CheckInId สำหรับ Check-Out"); + + b.Property("CheckType") + .HasColumnType("longtext") + .HasComment("ประเภทการลงเวลา: CHECK_IN, CHECK_OUT"); + + b.Property("CompletedDate") + .HasColumnType("datetime(6)") + .HasComment("วันเวลาที่เสร็จสิ้นการประมวลผล"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)") + .HasColumnOrder(100) + .HasComment("สร้างข้อมูลเมื่อ"); + + b.Property("CreatedDate") + .HasColumnType("datetime(6)") + .HasComment("วันเวลาที่สร้างงาน"); + + b.Property("CreatedFullName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)") + .HasColumnOrder(104) + .HasComment("ชื่อ User ที่สร้างข้อมูล"); + + b.Property("CreatedUserId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("varchar(40)") + .HasColumnOrder(101) + .HasComment("User Id ที่สร้างข้อมูล"); + + b.Property("ErrorMessage") + .HasColumnType("longtext") + .HasComment("ข้อความแสดงข้อผิดพลาด"); + + b.Property("KeycloakUserId") + .HasColumnType("char(36)") + .HasComment("รหัส User ของ Keycloak"); + + b.Property("LastUpdateFullName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)") + .HasColumnOrder(105) + .HasComment("ชื่อ User ที่แก้ไขข้อมูลล่าสุด"); + + b.Property("LastUpdateUserId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("varchar(40)") + .HasColumnOrder(103) + .HasComment("User Id ที่แก้ไขข้อมูลล่าสุด"); + + b.Property("LastUpdatedAt") + .HasColumnType("datetime(6)") + .HasColumnOrder(102) + .HasComment("แก้ไขข้อมูลล่าสุดเมื่อ"); + + b.Property("ProcessingDate") + .HasColumnType("datetime(6)") + .HasComment("วันเวลาที่เริ่มประมวลผล"); + + b.Property("Status") + .IsRequired() + .HasColumnType("longtext") + .HasComment("สถานะงาน: PENDING, PROCESSING, COMPLETED, FAILED"); + + b.Property("TaskId") + .HasColumnType("char(36)") + .HasComment("Task ID สำหรับติดตามสถานะงาน"); + + b.HasKey("Id"); + + b.ToTable("CheckInJobStatuses"); + }); + modelBuilder.Entity("BMA.EHR.Domain.Models.Leave.TimeAttendants.DutyTime", b => { b.Property("Id") diff --git a/BMA.EHR.Infrastructure/Persistence/LeaveDbContext.cs b/BMA.EHR.Infrastructure/Persistence/LeaveDbContext.cs index c6d37b7a..19848c6b 100644 --- a/BMA.EHR.Infrastructure/Persistence/LeaveDbContext.cs +++ b/BMA.EHR.Infrastructure/Persistence/LeaveDbContext.cs @@ -22,6 +22,8 @@ namespace BMA.EHR.Infrastructure.Persistence public DbSet UserCalendars { get; set; } + public DbSet CheckInJobStatuses { get; set; } + #endregion #region " Leave System " diff --git a/BMA.EHR.Leave/Controllers/LeaveController.cs b/BMA.EHR.Leave/Controllers/LeaveController.cs index aa89fc6a..b2544898 100644 --- a/BMA.EHR.Leave/Controllers/LeaveController.cs +++ b/BMA.EHR.Leave/Controllers/LeaveController.cs @@ -58,6 +58,7 @@ namespace BMA.EHR.Leave.Service.Controllers private readonly LeaveRequestRepository _leaveRequestRepository; private readonly UserCalendarRepository _userCalendarRepository; private readonly PermissionRepository _permission; + private readonly CheckInJobStatusRepository _checkInJobStatusRepository; private readonly CommandRepository _commandRepository; @@ -92,6 +93,7 @@ namespace BMA.EHR.Leave.Service.Controllers ObjectPool objectPool, PermissionRepository permission, NotificationRepository notificationRepository, + CheckInJobStatusRepository checkInJobStatusRepository, HttpClient httpClient) { _dutyTimeRepository = dutyTimeRepository; @@ -109,6 +111,7 @@ namespace BMA.EHR.Leave.Service.Controllers _commandRepository = commandRepository; _leaveRequestRepository = leaveRequestRepository; _notificationRepository = notificationRepository; + _checkInJobStatusRepository = checkInJobStatusRepository; _objectPool = objectPool; _permission = permission; @@ -540,11 +543,15 @@ namespace BMA.EHR.Leave.Service.Controllers } } + // add task id for check in queue + string taskId = Guid.NewGuid().ToString(); + var checkData = new CheckTimeDtoRB { UserId = userId, CurrentDate = currentDate, CheckInId = data.CheckInId, + TaskId = Guid.Parse(taskId), Lat = data.Lat, Lon = data.Lon, POI = data.POI, @@ -564,11 +571,27 @@ namespace BMA.EHR.Leave.Service.Controllers var serializedObject = JsonConvert.SerializeObject(checkData); var body = Encoding.UTF8.GetBytes(serializedObject); - // add task id for check in queue - string taskId = Guid.NewGuid().ToString(); var properties = channel.CreateBasicProperties(); properties.Persistent = true; - properties.MessageId = userId.ToString("D");// ระบบลงเวลาต้องมีการเช็คสถานะใน rabbitMQ ด้วยว่ามีการรอรันอยู่ไหม ลงเวลาเข้า/ออกงาน #894 + properties.MessageId = taskId; + + // บันทึกสถานะงานก่อนส่งไป RabbitMQ + var jobStatus = new CheckInJobStatus + { + TaskId = Guid.Parse(taskId), + KeycloakUserId = userId, + CreatedDate = currentDate, + 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 + }) + }; + await _checkInJobStatusRepository.AddAsync(jobStatus); channel.BasicPublish(exchange: "", routingKey: queue, @@ -583,6 +606,78 @@ namespace BMA.EHR.Leave.Service.Controllers } } + /// + /// ตรวจสอบสถานะงาน check-in ด้วย Task ID + /// + /// Task ID ที่ได้จากการเรียก CheckInAsync + /// + /// + /// เมื่อทำรายการสำเร็จ + /// ไม่ได้ Login เข้าระบบ + /// ไม่พบข้อมูลงาน + /// เมื่อเกิดข้อผิดพลาดในการทำงาน + [HttpGet("job-status/{taskId:guid}")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status401Unauthorized)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesResponseType(StatusCodes.Status500InternalServerError)] + public async Task> GetJobStatusAsync(Guid taskId) + { + var jobStatus = await _checkInJobStatusRepository.GetByTaskIdAsync(taskId); + + if (jobStatus == null) + { + return Error("ไม่พบข้อมูลงาน", StatusCodes.Status404NotFound); + } + + var result = new + { + taskId = jobStatus.TaskId, + keycloakUserId = jobStatus.KeycloakUserId, + status = jobStatus.Status, + checkType = jobStatus.CheckType, + checkInId = jobStatus.CheckInId, + createdDate = jobStatus.CreatedDate, + processingDate = jobStatus.ProcessingDate, + completedDate = jobStatus.CompletedDate, + errorMessage = jobStatus.ErrorMessage, + additionalData = jobStatus.AdditionalData != null ? + JsonConvert.DeserializeObject(jobStatus.AdditionalData) : null + }; + + return Success(result); + } + + /// + /// ดึงรายการงานที่ยัง pending หรือ processing ของผู้ใช้ + /// + /// + /// + /// เมื่อทำรายการสำเร็จ + /// ไม่ได้ Login เข้าระบบ + /// เมื่อเกิดข้อผิดพลาดในการทำงาน + [HttpGet("pending-jobs")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status401Unauthorized)] + [ProducesResponseType(StatusCodes.Status500InternalServerError)] + public async Task> GetPendingJobsAsync() + { + var userId = UserId == null ? Guid.Empty : Guid.Parse(UserId); + var jobs = await _checkInJobStatusRepository.GetPendingOrProcessingJobsAsync(userId); + + var result = jobs.Select(job => new + { + taskId = job.TaskId, + status = job.Status, + checkType = job.CheckType, + checkInId = job.CheckInId, + createdDate = job.CreatedDate, + processingDate = job.ProcessingDate + }).ToList(); + + return Success(new { count = result.Count, jobs = result }); + } + [HttpGet("check-status")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status401Unauthorized)] @@ -590,61 +685,62 @@ namespace BMA.EHR.Leave.Service.Controllers public async Task> CheckInCheckStatus() { var userId = UserId == null ? Guid.Empty : Guid.Parse(UserId); - var currentDate = DateTime.Now; - var channel = _objectPool.Get(); + // var currentDate = DateTime.Now; + // var channel = _objectPool.Get(); try { - var _url = _configuration["Rabbit:URL"] ?? ""; - var _queue = _configuration["Rabbit:Queue"] ?? "basic-queue"; + // var _url = _configuration["Rabbit:URL"] ?? ""; + // var _queue = _configuration["Rabbit:Queue"] ?? "basic-queue"; - // Step 1: ตรวจสอบจำนวน message ทั้งหมดในคิว - string queueUrl = $"{_url}{_queue}"; - var queueResponse = await _httpClient.GetAsync(queueUrl); - if (!queueResponse.IsSuccessStatusCode) - { - return Error("Error accessing RabbitMQ API", (int)queueResponse.StatusCode); - } + // // Step 1: ตรวจสอบจำนวน message ทั้งหมดในคิว + // string queueUrl = $"{_url}{_queue}"; + // var queueResponse = await _httpClient.GetAsync(queueUrl); + // if (!queueResponse.IsSuccessStatusCode) + // { + // return Error("Error accessing RabbitMQ API", (int)queueResponse.StatusCode); + // } - var queueContent = await queueResponse.Content.ReadAsStringAsync(); - var queueData = JObject.Parse(queueContent); - int totalMessages = queueData["messages"]?.Value() ?? 0; + // var queueContent = await queueResponse.Content.ReadAsStringAsync(); + // var queueData = JObject.Parse(queueContent); + // int totalMessages = queueData["messages"]?.Value() ?? 0; - // Step 2: วนลูปดึง message ทีละ 100 งาน - int batchSize = 100; - var allMessages = new List(); - int processedMessages = 0; + // // Step 2: วนลูปดึง message ทีละ 100 งาน + // int batchSize = 100; + // var allMessages = new List(); + // int processedMessages = 0; - while (processedMessages < totalMessages) - { - var requestBody = new StringContent( - $"{{\"count\":{batchSize},\"requeue\":true,\"encoding\":\"auto\",\"ackmode\":\"ack_requeue_true\"}}", - Encoding.UTF8, - "application/json" - ); + // while (processedMessages < totalMessages) + // { + // var requestBody = new StringContent( + // $"{{\"count\":{batchSize},\"requeue\":true,\"encoding\":\"auto\",\"ackmode\":\"ack_requeue_true\"}}", + // Encoding.UTF8, + // "application/json" + // ); - string getMessagesUrl = $"{_url}{_queue}/get"; - var response = await _httpClient.PostAsync(getMessagesUrl, requestBody); - if (!response.IsSuccessStatusCode) - { - return StatusCode((int)response.StatusCode, "Error retrieving messages from RabbitMQ."); - } + // string getMessagesUrl = $"{_url}{_queue}/get"; + // var response = await _httpClient.PostAsync(getMessagesUrl, requestBody); + // if (!response.IsSuccessStatusCode) + // { + // return StatusCode((int)response.StatusCode, "Error retrieving messages from RabbitMQ."); + // } - var content = await response.Content.ReadAsStringAsync(); - var messages = JArray.Parse(content); + // var content = await response.Content.ReadAsStringAsync(); + // var messages = JArray.Parse(content); - if (messages.Count == 0) - { - break; - } + // if (messages.Count == 0) + // { + // break; + // } - processedMessages += messages.Count; - allMessages.AddRange(messages.Select(m => m["properties"].ToString())); - } + // processedMessages += messages.Count; + // allMessages.AddRange(messages.Select(m => m["properties"].ToString())); + // } + var jobs = await _checkInJobStatusRepository.GetPendingOrProcessingJobsAsync(userId); // Step 3: ค้นหา taskIds ที่อยู่ใน messages ทั้งหมด - var foundTasks = allMessages.FirstOrDefault(x => x.Contains(userId.ToString("D"))); + //var foundTasks = allMessages.FirstOrDefault(x => x.Contains(userId.ToString("D"))); - return Success(new { keycloakId = userId, InQueue = foundTasks != null }); + return Success(new { keycloakId = userId, InQueue = (jobs != null && jobs.Count > 0) }); } catch (Exception ex) @@ -653,7 +749,7 @@ namespace BMA.EHR.Leave.Service.Controllers } finally { - _objectPool.Return(channel); + //_objectPool.Return(channel); } } @@ -790,21 +886,31 @@ namespace BMA.EHR.Leave.Service.Controllers public async Task> ProcessCheckInAsync([FromBody] CheckTimeDtoRB data) { var userId = data.UserId ?? Guid.Empty; - var profile = await _userProfileRepository.GetProfileByKeycloakIdAsync(userId, data.Token); + var taskId = data.TaskId ?? Guid.Empty; - if (profile == null) - return Error(GlobalMessages.DataNotFound, StatusCodes.Status404NotFound); - - if (data.CheckInFileName == "no-file") throw new Exception(GlobalMessages.NoFileToUpload); - var currentDate = data.CurrentDate ?? DateTime.Now; - - var check_status = data.CheckInId == null ? "check-in-picture" : "check-out-picture"; - - var fileName = $"{_bucketName}/{userId}/{currentDate.ToString("dd-MM-yyyy")}/{check_status}/{data.CheckInFileName}"; - using (var ms = new MemoryStream(data.CheckInFileBytes ?? new byte[0])) + try { - await _minIOService.UploadFileAsync(fileName, ms); - } + // อัปเดตสถานะเป็น PROCESSING + if (taskId != Guid.Empty) + { + await _checkInJobStatusRepository.UpdateToProcessingAsync(taskId); + } + + var profile = await _userProfileRepository.GetProfileByKeycloakIdAsync(userId, data.Token); + + if (profile == null) + return Error(GlobalMessages.DataNotFound, StatusCodes.Status404NotFound); + + if (data.CheckInFileName == "no-file") throw new Exception(GlobalMessages.NoFileToUpload); + var currentDate = data.CurrentDate ?? DateTime.Now; + + var check_status = data.CheckInId == null ? "check-in-picture" : "check-out-picture"; + + var fileName = $"{_bucketName}/{userId}/{currentDate.ToString("dd-MM-yyyy")}/{check_status}/{data.CheckInFileName}"; + using (var ms = new MemoryStream(data.CheckInFileBytes ?? new byte[0])) + { + await _minIOService.UploadFileAsync(fileName, ms); + } var defaultRound = await _dutyTimeRepository.GetDefaultAsync(); if (defaultRound == null) @@ -1058,9 +1164,32 @@ namespace BMA.EHR.Leave.Service.Controllers } } + + // อัปเดตสถานะเป็น COMPLETED + if (taskId != Guid.Empty) + { + var additionalData = JsonConvert.SerializeObject(new + { + CheckInType = data.CheckInId == null ? "check-in" : "check-out", + FileName = fileName, + ProcessedDate = currentDate + }); + await _checkInJobStatusRepository.UpdateToCompletedAsync(taskId, additionalData); + } + var checkInType = data.CheckInId == null ? "check-in" : "check-out"; return Success(new { user = $"{profile.FirstName} {profile.LastName}", date = currentDate, type = checkInType }); ; } + catch (Exception ex) + { + // อัปเดตสถานะเป็น FAILED + if (taskId != Guid.Empty) + { + await _checkInJobStatusRepository.UpdateToFailedAsync(taskId, ex.Message); + } + throw; + } + } /// /// LV1_005 - ลงเวลาเข้า-ออกงาน (USER) diff --git a/BMA.EHR.Leave/DTOs/CheckIn/CheckTimeDto.cs b/BMA.EHR.Leave/DTOs/CheckIn/CheckTimeDto.cs index 3a738170..0d49c24f 100644 --- a/BMA.EHR.Leave/DTOs/CheckIn/CheckTimeDto.cs +++ b/BMA.EHR.Leave/DTOs/CheckIn/CheckTimeDto.cs @@ -54,6 +54,7 @@ namespace BMA.EHR.Leave.Service.DTOs.CheckIn public Guid? CheckInId { get; set; } + public Guid? TaskId { get; set; } public double Lat { get; set; } = 0; diff --git a/BMA.EHR.Leave/appsettings.json b/BMA.EHR.Leave/appsettings.json index e1bb5d10..d1f58a39 100644 --- a/BMA.EHR.Leave/appsettings.json +++ b/BMA.EHR.Leave/appsettings.json @@ -53,7 +53,7 @@ "Host": "192.168.1.63", "User": "admin", "Password": "12345678", - "Queue": "hrms-checkin-queue", + "Queue": "hrms-checkin-queue-dev", "URL": "http://192.168.1.63:9122/api/queues/%2F/" }, "Mail": { From 90eea1ac7febedfc33e57258705d81f64991c496 Mon Sep 17 00:00:00 2001 From: Suphonchai Phoonsawat Date: Tue, 20 Jan 2026 11:09:13 +0700 Subject: [PATCH 052/183] Enhance CombinedErrorHandlerAndLoggingMiddleware with caching and improved token handling --- ...ombinedErrorHandlerAndLoggingMiddleware.cs | 230 ++++++++++++------ 1 file changed, 162 insertions(+), 68 deletions(-) diff --git a/BMA.EHR.Domain/Middlewares/CombinedErrorHandlerAndLoggingMiddleware.cs b/BMA.EHR.Domain/Middlewares/CombinedErrorHandlerAndLoggingMiddleware.cs index a9de25fc..5f5aa2af 100644 --- a/BMA.EHR.Domain/Middlewares/CombinedErrorHandlerAndLoggingMiddleware.cs +++ b/BMA.EHR.Domain/Middlewares/CombinedErrorHandlerAndLoggingMiddleware.cs @@ -18,6 +18,10 @@ namespace BMA.EHR.Domain.Middlewares { private readonly RequestDelegate _next; private readonly IConfiguration _configuration; + private static ElasticClient? _elasticClient; + private static readonly object _lock = new object(); + private static readonly Dictionary _profileCache = new(); + private static readonly TimeSpan _cacheExpiry = TimeSpan.FromMinutes(10); private string Uri = ""; private string IndexFormat = ""; @@ -31,19 +35,28 @@ namespace BMA.EHR.Domain.Middlewares Uri = _configuration["ElasticConfiguration:Uri"] ?? "http://192.168.1.40:9200"; IndexFormat = _configuration["ElasticConfiguration:IndexFormat"] ?? "bma-ehr-log-index"; SystemName = _configuration["ElasticConfiguration:SystemName"] ?? "Unknown"; + + // สร้าง ElasticClient แค่ครั้งเดียว + if (_elasticClient == null) + { + lock (_lock) + { + if (_elasticClient == null) + { + var settings = new ConnectionSettings(new Uri(Uri)) + .DefaultIndex(IndexFormat) + .DisableDirectStreaming() // เพิ่มประสิทธิภาพ + .RequestTimeout(TimeSpan.FromSeconds(5)); // กำหนด timeout + _elasticClient = new ElasticClient(settings); + } + } + } } public async Task Invoke(HttpContext context) { - Console.WriteLine("=== CombinedErrorHandlerAndLoggingMiddleware Start ==="); - - var settings = new ConnectionSettings(new Uri(Uri)) - .DefaultIndex(IndexFormat); - var client = new ElasticClient(settings); - var startTime = DateTime.UtcNow; var stopwatch = Stopwatch.StartNew(); - string? responseBodyJson = null; string? requestBodyJson = null; Exception? caughtException = null; @@ -64,27 +77,15 @@ namespace BMA.EHR.Domain.Middlewares string keycloakId = Guid.Empty.ToString("D"); var token = context.Request.Headers["Authorization"]; GetProfileByKeycloakIdLocal? pf = null; + var tokenUserInfo = await ExtractTokenUserInfoAsync(token); - // ลองดึง keycloakId จาก JWT token ก่อน (ถ้ามี) - try - { - keycloakId = await ExtractKeycloakIdFromToken(token); - } - catch (Exception ex) - { - Console.WriteLine($"Error extracting keycloakId from token: {ex.Message}"); - } + // ดึง keycloakId จาก JWT token + keycloakId = tokenUserInfo.KeycloakId; - try + // ดึง profile จาก cache หรือ API + if (Guid.TryParse(keycloakId, out var parsedId) && parsedId != Guid.Empty) { - if (Guid.TryParse(keycloakId, out var parsedId) && parsedId != Guid.Empty) - { - pf = await GetProfileByKeycloakIdAsync(parsedId, token); - } - } - catch (Exception ex) - { - Console.WriteLine($"Error getting profile: {ex.Message}"); + pf = await GetProfileWithCacheAsync(parsedId, token); } try @@ -103,17 +104,17 @@ namespace BMA.EHR.Domain.Middlewares Console.WriteLine($"Updated keycloakId from authenticated user: {keycloakId}"); // อัพเดต profile ด้วย keycloakId ที่ถูกต้อง - try - { - if (Guid.TryParse(keycloakId, out var parsedId)) - { - pf = await GetProfileByKeycloakIdAsync(parsedId, token); - } - } - catch (Exception ex) - { - Console.WriteLine($"Error updating profile after authentication: {ex.Message}"); - } + // try + // { + // if (Guid.TryParse(keycloakId, out var parsedId)) + // { + // //pf = await GetProfileByKeycloakIdAsync(parsedId, token); + // } + // } + // catch (Exception ex) + // { + // Console.WriteLine($"Error updating profile after authentication: {ex.Message}"); + // } } } @@ -142,7 +143,19 @@ namespace BMA.EHR.Domain.Middlewares finally { stopwatch.Stop(); - await LogRequest(context, client, startTime, stopwatch, pf, keycloakId, requestBodyJson, memoryStream, caughtException); + + // ทำ logging แบบ fire-and-forget เพื่อไม่ block response + _ = Task.Run(async () => + { + try + { + await LogRequestAsync(context, _elasticClient!, startTime, stopwatch, pf, keycloakId, requestBodyJson, memoryStream, caughtException); + } + catch (Exception ex) + { + Console.WriteLine($"Background logging error: {ex.Message}"); + } + }); // เขียนข้อมูลกลับไปยัง original Response body if (memoryStream.Length > 0) @@ -379,7 +392,7 @@ namespace BMA.EHR.Domain.Middlewares } } - private async Task LogRequest(HttpContext context, ElasticClient client, DateTime startTime, Stopwatch stopwatch, + private async Task LogRequestAsync(HttpContext context, ElasticClient client, DateTime startTime, Stopwatch stopwatch, GetProfileByKeycloakIdLocal? pf, string keycloakId, string? requestBodyJson, MemoryStream memoryStream, Exception? caughtException) { try @@ -399,7 +412,7 @@ namespace BMA.EHR.Domain.Middlewares string? message = null; string? responseBodyJson = null; - // อ่านข้อมูลจาก Response + // อ่านข้อมูลจาก Response (ลด serialization ที่ซ้ำ) if (memoryStream.Length > 0) { memoryStream.Seek(0, SeekOrigin.Begin); @@ -407,7 +420,7 @@ namespace BMA.EHR.Domain.Middlewares if (!string.IsNullOrEmpty(responseBody)) { - var contentType = context.Response.ContentType; + var contentType = context.Response.ContentType ?? ""; var isFileResponse = !contentType.StartsWith("application/json") && !contentType.StartsWith("text/html") && ( contentType.StartsWith("application/") || contentType.StartsWith("image/") || @@ -422,33 +435,23 @@ namespace BMA.EHR.Domain.Middlewares } else { + // ใช้ response body ที่มีอยู่แล้วโดยไม่ serialize ซ้ำ + responseBodyJson = responseBody; + try { - var jsonOptions = new JsonSerializerOptions - { - PropertyNamingPolicy = JsonNamingPolicy.CamelCase, - Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, - WriteIndented = true, - Converters = { new DateTimeFixConverter() } - }; - responseBodyJson = JsonSerializer.Serialize(JsonSerializer.Deserialize(responseBody), jsonOptions); - var json = JsonSerializer.Deserialize(responseBody); if (json.ValueKind == JsonValueKind.Array) { message = "success"; } - else + else if (json.TryGetProperty("message", out var messageElement)) { - if (json.TryGetProperty("message", out var messageElement)) - { - message = messageElement.GetString(); - } + message = messageElement.GetString(); } } catch { - responseBodyJson = responseBody; message = caughtException?.Message ?? "Unknown error"; } } @@ -536,11 +539,19 @@ namespace BMA.EHR.Domain.Middlewares private async Task ExtractKeycloakIdFromToken(string? authorizationHeader) { + var tokenInfo = await ExtractTokenUserInfoAsync(authorizationHeader); + return tokenInfo.KeycloakId; + } + + private async Task ExtractTokenUserInfoAsync(string? authorizationHeader) + { + var defaultResult = new TokenUserInfo { KeycloakId = Guid.Empty.ToString("D") }; + try { if (string.IsNullOrEmpty(authorizationHeader) || !authorizationHeader.StartsWith("Bearer ")) { - return Guid.Empty.ToString("D"); + return defaultResult; } var token = authorizationHeader.Replace("Bearer ", ""); @@ -549,7 +560,7 @@ namespace BMA.EHR.Domain.Middlewares var parts = token.Split('.'); if (parts.Length != 3) { - return Guid.Empty.ToString("D"); + return defaultResult; } // Decode Base64Url payload (JWT uses Base64Url encoding, not standard Base64) @@ -570,31 +581,55 @@ namespace BMA.EHR.Domain.Middlewares Console.WriteLine($"JWT Payload: {payloadJson}"); - // Parse JSON และดึง sub (subject) claim + // Parse JSON และดึง claims ต่างๆ var jsonDoc = JsonDocument.Parse(payloadJson); + var result = new TokenUserInfo(); - // ลองหา keycloak ID ใน claims ต่างๆ - string? keycloakId = null; - + // ดึง keycloak ID if (jsonDoc.RootElement.TryGetProperty("sub", out var subElement)) { - keycloakId = subElement.GetString(); + result.KeycloakId = subElement.GetString() ?? Guid.Empty.ToString("D"); } else if (jsonDoc.RootElement.TryGetProperty("nameid", out var nameidElement)) { - keycloakId = nameidElement.GetString(); + result.KeycloakId = nameidElement.GetString() ?? Guid.Empty.ToString("D"); } else if (jsonDoc.RootElement.TryGetProperty("user_id", out var userIdElement)) { - keycloakId = userIdElement.GetString(); + result.KeycloakId = userIdElement.GetString() ?? Guid.Empty.ToString("D"); + } + else + { + result.KeycloakId = Guid.Empty.ToString("D"); } - return keycloakId ?? Guid.Empty.ToString("D"); + // ดึง preferred_username + if (jsonDoc.RootElement.TryGetProperty("preferred_username", out var preferredUsernameElement)) + { + result.PreferredUsername = preferredUsernameElement.GetString(); + Console.WriteLine($"Extracted preferred_username: {result.PreferredUsername}"); + } + + // ดึง given_name + if (jsonDoc.RootElement.TryGetProperty("given_name", out var givenNameElement)) + { + result.GivenName = givenNameElement.GetString(); + Console.WriteLine($"Extracted given_name: {result.GivenName}"); + } + + // ดึง family_name + if (jsonDoc.RootElement.TryGetProperty("family_name", out var familyNameElement)) + { + result.FamilyName = familyNameElement.GetString(); + Console.WriteLine($"Extracted family_name: {result.FamilyName}"); + } + + return result; } catch (Exception ex) { - Console.WriteLine($"Error extracting keycloak ID from token: {ex.Message}"); - return Guid.Empty.ToString("D"); + Console.WriteLine($"Error extracting token user info: {ex.Message}"); + return defaultResult; } } @@ -640,7 +675,58 @@ namespace BMA.EHR.Domain.Middlewares } catch { - throw; + return null; + } + } + + private async Task GetProfileWithCacheAsync(Guid keycloakId, string? accessToken) + { + var cacheKey = keycloakId.ToString(); + + // ตรวจสอบ cache + lock (_profileCache) + { + if (_profileCache.TryGetValue(cacheKey, out var cached)) + { + if (cached.ExpiryTime > DateTime.UtcNow) + { + return cached.Profile; + } + // ลบ cache ที่หมดอายุ + _profileCache.Remove(cacheKey); + } + } + + // ดึงข้อมูลจาก API + try + { + var profile = await GetProfileByKeycloakIdAsync(keycloakId, accessToken); + if (profile != null) + { + // เก็บใน cache + lock (_profileCache) + { + _profileCache[cacheKey] = (profile, DateTime.UtcNow.Add(_cacheExpiry)); + + // ลบ cache เก่าที่เกิน 1000 รายการ + if (_profileCache.Count > 1000) + { + var expiredKeys = _profileCache + .Where(x => x.Value.ExpiryTime < DateTime.UtcNow) + .Select(x => x.Key) + .ToList(); + foreach (var key in expiredKeys) + { + _profileCache.Remove(key); + } + } + } + } + return profile; + } + catch + { + return null; } } @@ -655,6 +741,14 @@ namespace BMA.EHR.Domain.Middlewares } // Model classes + public class TokenUserInfo + { + public string KeycloakId { get; set; } = string.Empty; + public string? PreferredUsername { get; set; } + public string? GivenName { get; set; } + public string? FamilyName { get; set; } + } + public class GetProfileByKeycloakIdLocal { public Guid Id { get; set; } From e3a228773e5f55cafd1e315b767ca0e0941b8a04 Mon Sep 17 00:00:00 2001 From: Suphonchai Phoonsawat Date: Tue, 20 Jan 2026 11:14:30 +0700 Subject: [PATCH 053/183] Add scheduled job to clean up CheckIn Job Status older than 30 days --- BMA.EHR.Leave/Program.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/BMA.EHR.Leave/Program.cs b/BMA.EHR.Leave/Program.cs index 7ff93507..928c8fc2 100644 --- a/BMA.EHR.Leave/Program.cs +++ b/BMA.EHR.Leave/Program.cs @@ -181,6 +181,8 @@ var manager = new RecurringJobManager(); if (manager != null) { manager.AddOrUpdate("ปรับปรุงรอบการลงเวลาทำงาน", Job.FromExpression(x => x.UpdateUserDutyTime()), "0 1 * * *", bangkokTimeZone); + // ทำความสะอาดข้อมูล CheckIn Job Status ที่เก่ากว่า 30 วัน - รันทุกวันเวลา 02:00 น. + manager.AddOrUpdate("ทำความสะอาดข้อมูล CheckIn Job Status", Job.FromExpression(x => x.CleanupOldJobsAsync(30)), "0 2 * * *", bangkokTimeZone); } // apply migrations From 15f5d7cae76d1e6a2ba92deff06c890509264a4f Mon Sep 17 00:00:00 2001 From: harid Date: Tue, 20 Jan 2026 16:41:22 +0700 Subject: [PATCH 054/183] =?UTF-8?q?Task=20#2207=20=E0=B8=81=E0=B8=A3?= =?UTF-8?q?=E0=B8=93=E0=B8=B5=E0=B8=84=E0=B8=99=E0=B8=82=E0=B8=AD=E0=B9=82?= =?UTF-8?q?=E0=B8=AD=E0=B8=99=E0=B8=AD=E0=B8=A2=E0=B8=B9=E0=B9=88=E0=B9=83?= =?UTF-8?q?=E0=B8=99=E0=B8=AA=E0=B8=B3=E0=B8=99=E0=B8=B1=E0=B8=81=E0=B8=9B?= =?UTF-8?q?=E0=B8=A5=E0=B8=B1=E0=B8=94=E0=B8=81=E0=B8=A3=E0=B8=B8=E0=B8=87?= =?UTF-8?q?=E0=B9=80=E0=B8=97=E0=B8=9E=E0=B8=A1=E0=B8=AB=E0=B8=B2=E0=B8=99?= =?UTF-8?q?=E0=B8=84=E0=B8=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Controllers/PlacementTransferController.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/BMA.EHR.Placement.Service/Controllers/PlacementTransferController.cs b/BMA.EHR.Placement.Service/Controllers/PlacementTransferController.cs index 73115246..f2931e3d 100644 --- a/BMA.EHR.Placement.Service/Controllers/PlacementTransferController.cs +++ b/BMA.EHR.Placement.Service/Controllers/PlacementTransferController.cs @@ -574,6 +574,7 @@ namespace BMA.EHR.Placement.Service.Controllers LastUpdatedAt = DateTime.Now, }; var apiUrl = $"{_configuration["API"]}/org/profile/keycloak/position"; + bool isDeputy = false; using (var client = new HttpClient()) { client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token.Replace("Bearer ", "")); @@ -587,6 +588,8 @@ namespace BMA.EHR.Placement.Service.Controllers if (org == null || org.result == null) return Error("ไม่พบหน่วยงานของผู้ใช้งานคนนี้", 404); + isDeputy = org.result.isDeputy.HasValue ? org.result.isDeputy.Value : false; + placementTransfer.profileId = org.result.profileId; placementTransfer.prefix = org.result.prefix; placementTransfer.firstName = org.result.firstName; @@ -663,6 +666,7 @@ namespace BMA.EHR.Placement.Service.Controllers } } } + var baseAPIOrg = _configuration["API"]; var apiUrlOrg = $"{baseAPIOrg}/org/workflow/add-workflow"; using (var client = new HttpClient()) @@ -675,7 +679,8 @@ namespace BMA.EHR.Placement.Service.Controllers sysName = "SYS_TRANSFER_REQ", posLevelName = placementTransfer.posLevelNameOld, posTypeName = placementTransfer.posTypeNameOld, - fullName = $"{placementTransfer.prefix}{placementTransfer.firstName} {placementTransfer.lastName}" + fullName = $"{placementTransfer.prefix}{placementTransfer.firstName} {placementTransfer.lastName}", + isDeputy = isDeputy }); } await _context.SaveChangesAsync(); From 5219934e052dd868b9b35027ad0c1a018db529a0 Mon Sep 17 00:00:00 2001 From: harid Date: Tue, 20 Jan 2026 17:30:11 +0700 Subject: [PATCH 055/183] fix #2207 --- BMA.EHR.Placement.Service/Requests/OrgRequest.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/BMA.EHR.Placement.Service/Requests/OrgRequest.cs b/BMA.EHR.Placement.Service/Requests/OrgRequest.cs index 817c583f..c943bc53 100644 --- a/BMA.EHR.Placement.Service/Requests/OrgRequest.cs +++ b/BMA.EHR.Placement.Service/Requests/OrgRequest.cs @@ -52,6 +52,7 @@ namespace BMA.EHR.Placement.Service.Requests public string? education { get; set; } public double? Amount { get; set; } public string? avatarUrl { get; set; } + public bool? isDeputy { get; set; } } } \ No newline at end of file From 27b3773c79bc85f3c2b50c78c32034758a292982 Mon Sep 17 00:00:00 2001 From: Suphonchai Phoonsawat Date: Tue, 20 Jan 2026 20:54:47 +0700 Subject: [PATCH 056/183] load test script --- dotnet_leave_test.js | 54 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 dotnet_leave_test.js diff --git a/dotnet_leave_test.js b/dotnet_leave_test.js new file mode 100644 index 00000000..5add397d --- /dev/null +++ b/dotnet_leave_test.js @@ -0,0 +1,54 @@ +// ทดสอบการยิง 30,000 requests ในเวลา 10 นาที โดยให้กระจายการยิงในเวลาที่ต่างๆ กัน + +import { check, sleep } from "k6"; +import http from "k6/http"; +import { Rate } from "k6/metrics"; + +export let errorRate = new Rate("errors"); + +// จำนวน request ที่ต้องการยิง + +// ระยะเวลาทดสอบทั้งหมด + +// จำนวน Virtual Users เฉลี่ยที่ต้องการ 300 users +//const averageVus = Math.ceil(totalRequests / totalDuration); +const averageVus = 300; + +export let options = { + stages: [ + { duration: "2m", target: averageVus * 0.5 }, // 20% ของการทดสอบ เพิ่ม VUs เป็น 50% ของค่าเฉลี่ย + { duration: "4m", target: averageVus }, // 40% ของการทดสอบ เพิ่ม VUs เป็น 100% ของค่าเฉลี่ย + { duration: "2m", target: averageVus * 1.5 }, // 20% ของการทดสอบ เพิ่ม VUs เป็น 150% ของค่าเฉลี่ย + { duration: "2m", target: 0 }, // ลด VUs ลงมาเป็น 0 + ], + thresholds: { + errors: ["rate<0.01"], // อัตรา error ต้องน้อยกว่า 1% + http_req_duration: ["p(95)<2000"], // 95% ของ requests ควรใช้เวลาไม่เกิน 2 วินาที + }, +}; + +export default function () { + // ตัวเลือก headers + let headers = { + "Content-Type": "application/json", + Authorization: + "Bearer eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJ4WTJWUi1FRnZ2TlBzTXMzOXU4b29WQldRTDZtUHdyTkpPaDNrb0pGVGdVIn0.eyJleHAiOjE3NzYyMTkxNjgsImlhdCI6MTc2ODQ0MzE2OCwianRpIjoiZDQxMmI5MWEtZmZhMi00N2JiLTliZDUtZDE5NTdmMDFjYzQyIiwiaXNzIjoiaHR0cHM6Ly9ocm1zLWlkLmJhbmdrb2suZ28udGgvcmVhbG1zL2hybXMiLCJhdWQiOiJhY2NvdW50Iiwic3ViIjoiYmFmYzU3OTUtYmVmYy00ZDNmLWE0NjEtMzUzM2MzOGE1ZmMxIiwidHlwIjoiQmVhcmVyIiwiYXpwIjoiZ2V0dG9rZW4tY2hlY2tpbiIsInNpZCI6IjBkNzdiY2Y5LTE4YWQtNGQyMS1hYjBjLTI4Y2ZiZjUyZGZiNCIsImFjciI6IjEiLCJhbGxvd2VkLW9yaWdpbnMiOlsiaHR0cHM6Ly9ocm1zLmJhbmdrb2suZ28udGgiLCJodHRwczovL2hybXMtY2hlY2tpbi5iYW5na29rLmdvLnRoIl0sInJlYWxtX2FjY2VzcyI6eyJyb2xlcyI6WyJTVVBFUl9BRE1JTiIsInN0b3JhZ2VfbWFuYWdlbWVudCIsIm9mZmxpbmVfYWNjZXNzIiwiU1RBRkYiLCJkZWZhdWx0LXJvbGVzLWhybXMiLCJ1bWFfYXV0aG9yaXphdGlvbiIsIkFETUlOIiwiVVNFUiJdfSwicmVzb3VyY2VfYWNjZXNzIjp7ImFjY291bnQiOnsicm9sZXMiOlsibWFuYWdlLWFjY291bnQiLCJtYW5hZ2UtYWNjb3VudC1saW5rcyIsInZpZXctcHJvZmlsZSJdfX0sInNjb3BlIjoiZW1haWwgb3BlbmlkIHByb2ZpbGUiLCJlbWFpbF92ZXJpZmllZCI6ZmFsc2UsInJvbGUiOlsiU1VQRVJfQURNSU4iLCJzdG9yYWdlX21hbmFnZW1lbnQiLCJvZmZsaW5lX2FjY2VzcyIsIlNUQUZGIiwiZGVmYXVsdC1yb2xlcy1ocm1zIiwidW1hX2F1dGhvcml6YXRpb24iLCJBRE1JTiIsIlVTRVIiXSwibmFtZSI6IuC4p-C4seC4meC5gOC4ieC4peC4tOC4oSDguInguLHguJXguKPguJfguK3guIciLCJwcmVmZXJyZWRfdXNlcm5hbWUiOiIzMTIwMjAwNDI0OTc1IiwiZ2l2ZW5fbmFtZSI6IuC4p-C4seC4meC5gOC4ieC4peC4tOC4oSIsImZhbWlseV9uYW1lIjoi4LiJ4Lix4LiV4Lij4LiX4Lit4LiHIn0.UhMn0NEkymPxMAcb4noZedHCSqXotCyD2RziBtLYHn5OhA9yk1915Rrt9iV4wVaebr74iZ2eZMpBwp8YVy8-3cPXSv9T3vzbXwFP7IeICPCDDf4bOPFEHP5FYow2s9v48qG81wnu01AG7_EL2-CQKh1sBVrCVUUlATlf-P4lT_lHeHOCKNXTmw4V0IWm96ec6pk-jFY3KH2JdRSWR7wq8g-KVxhLOxk_pF72kMwOpdvcr_99byg28zzj6QfeNYXLt61koHXnZppUqytt86mQQgfamv2FNVywCEzbRITUceu2rmJnwQE8ubeoCh4UOsYauUuSKd7RPqvvXxL_Vg__8Q", + //"Bearer eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJTT2wwWmFidm9rRzZET3pDZVBtT09Kek5haTdMUldkci1zV3lEYjRELTc0In0.eyJleHAiOjE3Njg4ODAzMjgsImlhdCI6MTc2ODc5MzkyOCwianRpIjoiMDYxODBlMWYtNTQzYy00MjU0LWFmN2QtYWI1NDA5NzFmNWY2IiwiaXNzIjoiaHR0cHM6Ly9ocm1zYmtrLWlkLmNhc2UtY29sbGVjdGlvbi5jb20vcmVhbG1zL2hybXMiLCJhdWQiOlsiYWNjb3VudCIsImdldHRva2VuIl0sInN1YiI6IjQzOWZhMzZkLTZiYzUtNGVmNS05NWFhLWVmMjllNjRkMmU5ZiIsInR5cCI6IkJlYXJlciIsImF6cCI6ImdldHRva2VuIiwic2lkIjoiZGI2YzUxNjItNzZhYS00MmVmLWI0ZDMtYThmOTk2N2NjZWM2IiwiYWNyIjoiMSIsImFsbG93ZWQtb3JpZ2lucyI6WyIqIl0sInJlYWxtX2FjY2VzcyI6eyJyb2xlcyI6WyJTVVBFUl9BRE1JTiIsIm9mZmxpbmVfYWNjZXNzIiwiU1RBRkYiLCJkZWZhdWx0LXJvbGVzLWhybXMiLCJ1bWFfYXV0aG9yaXphdGlvbiIsIkFETUlOIiwiVVNFUiJdfSwicmVzb3VyY2VfYWNjZXNzIjp7ImFjY291bnQiOnsicm9sZXMiOlsibWFuYWdlLWFjY291bnQiLCJtYW5hZ2UtYWNjb3VudC1saW5rcyIsInZpZXctcHJvZmlsZSJdfX0sInNjb3BlIjoiZW1haWwgb3BlbmlkIHByb2ZpbGUiLCJlbWFpbF92ZXJpZmllZCI6ZmFsc2UsInJvbGUiOlsiU1VQRVJfQURNSU4iLCJvZmZsaW5lX2FjY2VzcyIsIlNUQUZGIiwiZGVmYXVsdC1yb2xlcy1ocm1zIiwidW1hX2F1dGhvcml6YXRpb24iLCJBRE1JTiIsIlVTRVIiXSwibmFtZSI6IuC4p-C4seC4meC5gOC4ieC4peC4tOC4oSDguInguLHguJXguKPguJfguK3guIciLCJwcmVmZXJyZWRfdXNlcm5hbWUiOiIzMTIwMjAwNDI0OTc1IiwiZ2l2ZW5fbmFtZSI6IuC4p-C4seC4meC5gOC4ieC4peC4tOC4oSIsImZhbWlseV9uYW1lIjoi4LiJ4Lix4LiV4Lij4LiX4Lit4LiHIn0.fHdMzpHMD4JcbzYnUrfM473FSXka2Z4lz_S3HI2c-dPXfO5ATpijqsi12C6-ExE0RJRXUK671erMuyVXL6u2qj-FvdliBL3ubKy4J3jIT3svkcZxZL2ib16dRg375dITefvqd-J4vw6MR4bq8YAGPbqRIy6BQ2pdEiZgNiwUUihHAFwZlVER1lNbaqlbL6vk_L4k-g25DBVnDr756BFvrw7zEDbawkKZ31EZF5_DYk4RWej0wvWrGHQWLw-RyzYVSBB_AooqHkncHn_CwLBGC5juOEfFO4a2ThuKwoxYCstjtBj-zmjpHFs-Hh3CBTWJCGFcKst1Ey28StlKtNkLiw", + }; + + // ส่ง GET request + let response = http.get( + //"https://bma-hrms.bangkok.go.th/api/v1/leave/fake-check-in", + //"https://hrmsbkk.case-collection.com/api/v1/org/dotnet/keycloak/439fa36d-6bc5-4ef5-95aa-ef29e64d2e9f", + "https://hrms.bangkok.go.th/api/v1/leave/check-time", + { headers: headers }, + ); + + // ตรวจสอบการตอบสนอง + check(response, { + "is status 200": (r) => r.status === 200, + }); + + // หน่วงเวลา 1 วินาที + sleep(1); +} From d3501e831c4e8ebe4fd20c42af50cea2b78830f5 Mon Sep 17 00:00:00 2001 From: harid Date: Wed, 21 Jan 2026 17:00:45 +0700 Subject: [PATCH 057/183] =?UTF-8?q?fix=20=E0=B8=81=E0=B8=88.=20=E0=B9=84?= =?UTF-8?q?=E0=B8=A1=E0=B9=88=E0=B9=80=E0=B8=AB=E0=B9=87=E0=B8=99=E0=B8=A3?= =?UTF-8?q?=E0=B8=B2=E0=B8=A2=E0=B8=8A=E0=B8=B7=E0=B9=88=E0=B8=AD=E0=B8=9C?= =?UTF-8?q?=E0=B8=B9=E0=B9=89=E0=B8=AA=E0=B8=AD=E0=B8=9A=E0=B8=9C=E0=B9=88?= =?UTF-8?q?=E0=B8=B2=E0=B8=99=E0=B8=AB=E0=B8=A5=E0=B8=B1=E0=B8=87=E0=B8=88?= =?UTF-8?q?=E0=B8=B2=E0=B8=81=E0=B9=80=E0=B8=9C=E0=B8=A2=E0=B9=81=E0=B8=9E?= =?UTF-8?q?=E0=B8=A3=E0=B9=88=E0=B9=82=E0=B8=84=E0=B8=A3=E0=B8=87=E0=B8=AA?= =?UTF-8?q?=E0=B8=A3=E0=B9=89=E0=B8=B2=E0=B8=87=20Task=20#2219?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Controllers/PlacementController.cs | 18 +++++++++++------- .../Requests/OrgRequestAct.cs | 1 + 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/BMA.EHR.Placement.Service/Controllers/PlacementController.cs b/BMA.EHR.Placement.Service/Controllers/PlacementController.cs index 62b9bbce..a485a42d 100644 --- a/BMA.EHR.Placement.Service/Controllers/PlacementController.cs +++ b/BMA.EHR.Placement.Service/Controllers/PlacementController.cs @@ -155,6 +155,7 @@ namespace BMA.EHR.Placement.Service.Controllers var child2Id = ""; var child3Id = ""; var child4Id = ""; + var rootDnaId = ""; var apiUrl = $"{_configuration["API"]}/org/profile/keycloak/position-act"; using (var client = new HttpClient()) { @@ -173,11 +174,12 @@ namespace BMA.EHR.Placement.Service.Controllers // child2Id = org.result.child2Id == null ? "" : org.result.child2Id; // child3Id = org.result.child3Id == null ? "" : org.result.child3Id; // child4Id = org.result.child4Id == null ? "" : org.result.child4Id; + rootDnaId = org.result.rootDnaId == null ? "" : org.result.rootDnaId; var data1 = await _context.PlacementProfiles .Where(x => x.Placement.Id == examId) .Where(x => x.Draft == true) .Where(x => x.PlacementStatus != "UN-CONTAIN") - .Where(x => rootId == "" ? true : (child1Id == "" ? x.rootId == rootId : (child2Id == "" ? x.child1Id == child1Id : (child3Id == "" ? x.child2Id == child2Id : (child4Id == "" ? x.child3Id == child3Id : x.child4Id == child4Id))))) + .Where(x => rootDnaId == "" ? true : (child1Id == "" ? x.rootDnaId == rootDnaId : (child2Id == "" ? x.child1Id == child1Id : (child3Id == "" ? x.child2Id == child2Id : (child4Id == "" ? x.child3Id == child3Id : x.child4Id == child4Id))))) .Select(x => new { Id = x.Id, @@ -694,6 +696,7 @@ namespace BMA.EHR.Placement.Service.Controllers var child2Id = ""; var child3Id = ""; var child4Id = ""; + var rootDnaId = ""; var apiUrl = $"{_configuration["API"]}/org/profile/keycloak/position-act"; using (var client = new HttpClient()) { @@ -713,16 +716,17 @@ namespace BMA.EHR.Placement.Service.Controllers // child2Id = org.result.child2Id == null ? "" : org.result.child2Id; // child3Id = org.result.child3Id == null ? "" : org.result.child3Id; // child4Id = org.result.child4Id == null ? "" : org.result.child4Id; + rootDnaId = org.result.rootDnaId == null ? "" : org.result.rootDnaId; var placement = await _context.Placements .Where(x => x.Id == examId) .Select(x => new { - Total = x.PlacementProfiles.Where(x => x.Draft == true).Where(x => rootId == "" ? true : (child1Id == "" ? x.rootId == rootId : (child2Id == "" ? x.child1Id == child1Id : (child3Id == "" ? x.child2Id == child2Id : (child4Id == "" ? x.child3Id == child3Id : x.child4Id == child4Id))))).Count(), - UnContain = x.PlacementProfiles.Where(x => x.Draft == true).Where(x => rootId == "" ? true : (child1Id == "" ? x.rootId == rootId : (child2Id == "" ? x.child1Id == child1Id : (child3Id == "" ? x.child2Id == child2Id : (child4Id == "" ? x.child3Id == child3Id : x.child4Id == child4Id))))).Where(p => p.PlacementStatus.Trim().ToUpper() == "UN-CONTAIN").Count(), - PrepareContain = x.PlacementProfiles.Where(x => x.Draft == true).Where(x => rootId == "" ? true : (child1Id == "" ? x.rootId == rootId : (child2Id == "" ? x.child1Id == child1Id : (child3Id == "" ? x.child2Id == child2Id : (child4Id == "" ? x.child3Id == child3Id : x.child4Id == child4Id))))).Where(p => p.PlacementStatus.Trim().ToUpper() == "PREPARE-CONTAIN").Count(), - Report = x.PlacementProfiles.Where(x => x.Draft == true).Where(x => rootId == "" ? true : (child1Id == "" ? x.rootId == rootId : (child2Id == "" ? x.child1Id == child1Id : (child3Id == "" ? x.child2Id == child2Id : (child4Id == "" ? x.child3Id == child3Id : x.child4Id == child4Id))))).Where(p => p.PlacementStatus.Trim().ToUpper() == "REPORT").Count(), - Done = x.PlacementProfiles.Where(x => x.Draft == true).Where(x => rootId == "" ? true : (child1Id == "" ? x.rootId == rootId : (child2Id == "" ? x.child1Id == child1Id : (child3Id == "" ? x.child2Id == child2Id : (child4Id == "" ? x.child3Id == child3Id : x.child4Id == child4Id))))).Where(p => p.PlacementStatus.Trim().ToUpper() == "DONE").Count(), - Disclaim = x.PlacementProfiles.Where(x => x.Draft == true).Where(x => rootId == "" ? true : (child1Id == "" ? x.rootId == rootId : (child2Id == "" ? x.child1Id == child1Id : (child3Id == "" ? x.child2Id == child2Id : (child4Id == "" ? x.child3Id == child3Id : x.child4Id == child4Id))))).Where(p => p.PlacementStatus.Trim().ToUpper() == "DISCLAIM").Count(), + Total = x.PlacementProfiles.Where(x => x.Draft == true).Where(x => rootDnaId == "" ? true : (child1Id == "" ? x.rootDnaId == rootDnaId : (child2Id == "" ? x.child1Id == child1Id : (child3Id == "" ? x.child2Id == child2Id : (child4Id == "" ? x.child3Id == child3Id : x.child4Id == child4Id))))).Count(), + UnContain = x.PlacementProfiles.Where(x => x.Draft == true).Where(x => rootDnaId == "" ? true : (child1Id == "" ? x.rootDnaId == rootDnaId : (child2Id == "" ? x.child1Id == child1Id : (child3Id == "" ? x.child2Id == child2Id : (child4Id == "" ? x.child3Id == child3Id : x.child4Id == child4Id))))).Where(p => p.PlacementStatus.Trim().ToUpper() == "UN-CONTAIN").Count(), + PrepareContain = x.PlacementProfiles.Where(x => x.Draft == true).Where(x => rootDnaId == "" ? true : (child1Id == "" ? x.rootDnaId == rootDnaId : (child2Id == "" ? x.child1Id == child1Id : (child3Id == "" ? x.child2Id == child2Id : (child4Id == "" ? x.child3Id == child3Id : x.child4Id == child4Id))))).Where(p => p.PlacementStatus.Trim().ToUpper() == "PREPARE-CONTAIN").Count(), + Report = x.PlacementProfiles.Where(x => x.Draft == true).Where(x => rootDnaId == "" ? true : (child1Id == "" ? x.rootDnaId == rootDnaId : (child2Id == "" ? x.child1Id == child1Id : (child3Id == "" ? x.child2Id == child2Id : (child4Id == "" ? x.child3Id == child3Id : x.child4Id == child4Id))))).Where(p => p.PlacementStatus.Trim().ToUpper() == "REPORT").Count(), + Done = x.PlacementProfiles.Where(x => x.Draft == true).Where(x => rootDnaId == "" ? true : (child1Id == "" ? x.rootDnaId == rootDnaId : (child2Id == "" ? x.child1Id == child1Id : (child3Id == "" ? x.child2Id == child2Id : (child4Id == "" ? x.child3Id == child3Id : x.child4Id == child4Id))))).Where(p => p.PlacementStatus.Trim().ToUpper() == "DONE").Count(), + Disclaim = x.PlacementProfiles.Where(x => x.Draft == true).Where(x => rootDnaId == "" ? true : (child1Id == "" ? x.rootDnaId == rootDnaId : (child2Id == "" ? x.child1Id == child1Id : (child3Id == "" ? x.child2Id == child2Id : (child4Id == "" ? x.child3Id == child3Id : x.child4Id == child4Id))))).Where(p => p.PlacementStatus.Trim().ToUpper() == "DISCLAIM").Count(), }).FirstOrDefaultAsync(); if (placement == null) return Error(GlobalMessages.DataNotFound, 404); diff --git a/BMA.EHR.Placement.Service/Requests/OrgRequestAct.cs b/BMA.EHR.Placement.Service/Requests/OrgRequestAct.cs index dace91d3..6189eaf4 100644 --- a/BMA.EHR.Placement.Service/Requests/OrgRequestAct.cs +++ b/BMA.EHR.Placement.Service/Requests/OrgRequestAct.cs @@ -15,5 +15,6 @@ namespace BMA.EHR.Placement.Service.Requests public string? child2Id { get; set; } public string? child3Id { get; set; } public string? child4Id { get; set; } + public string? rootDnaId { get; set; } } } \ No newline at end of file From d945deae4fdaa952966a39ebe055e1e88309907e Mon Sep 17 00:00:00 2001 From: Suphonchai Phoonsawat Date: Thu, 22 Jan 2026 11:58:26 +0700 Subject: [PATCH 058/183] Add error handling for permission API calls and enhance logging in middleware --- .../Repositories/PermissionRepository.cs | 4 + .../Repositories/UserProfileRepository.cs | 24 ++++++ ...ombinedErrorHandlerAndLoggingMiddleware.cs | 84 ++++++++++--------- 3 files changed, 72 insertions(+), 40 deletions(-) diff --git a/BMA.EHR.Application/Repositories/PermissionRepository.cs b/BMA.EHR.Application/Repositories/PermissionRepository.cs index d5ac981a..84191f91 100644 --- a/BMA.EHR.Application/Repositories/PermissionRepository.cs +++ b/BMA.EHR.Application/Repositories/PermissionRepository.cs @@ -62,6 +62,10 @@ namespace BMA.EHR.Application.Repositories new AuthenticationHeaderValue("Bearer", AccessToken.Replace("Bearer ", "")); client.DefaultRequestHeaders.Add("api-key", _configuration["API_KEY"]); var req = await client.GetAsync(apiPath); + if (!req.IsSuccessStatusCode) + { + throw new Exception("Error calling permission API"); + } var res = await req.Content.ReadAsStringAsync(); return res; } diff --git a/BMA.EHR.Application/Repositories/UserProfileRepository.cs b/BMA.EHR.Application/Repositories/UserProfileRepository.cs index 1c9d5596..9ef3f76e 100644 --- a/BMA.EHR.Application/Repositories/UserProfileRepository.cs +++ b/BMA.EHR.Application/Repositories/UserProfileRepository.cs @@ -186,6 +186,30 @@ namespace BMA.EHR.Application.Repositories } } + public async Task GetProfileByKeycloakIdNewAsync(Guid keycloakId, string? accessToken) + { + try + { + var apiPath = $"{_configuration["API"]}/org/dotnet/by-keycloak/{keycloakId}"; + var apiKey = _configuration["API_KEY"]; + + var apiResult = await GetExternalAPIAsync(apiPath, accessToken ?? "", apiKey); + if (apiResult != null) + { + var raw = JsonConvert.DeserializeObject(apiResult); + if (raw != null) + return raw.Result; + } + + return null; + } + catch + { + throw; + } + } + + public async Task GetProfileLeaveByKeycloakIdAsync(Guid keycloakId, string? accessToken) { try diff --git a/BMA.EHR.Domain/Middlewares/CombinedErrorHandlerAndLoggingMiddleware.cs b/BMA.EHR.Domain/Middlewares/CombinedErrorHandlerAndLoggingMiddleware.cs index 5f5aa2af..3391e6a3 100644 --- a/BMA.EHR.Domain/Middlewares/CombinedErrorHandlerAndLoggingMiddleware.cs +++ b/BMA.EHR.Domain/Middlewares/CombinedErrorHandlerAndLoggingMiddleware.cs @@ -144,12 +144,22 @@ namespace BMA.EHR.Domain.Middlewares { stopwatch.Stop(); + // อ่านข้อมูล response ก่อนที่ stream จะถูก dispose + string? responseBodyForLogging = null; + if (memoryStream.Length > 0) + { + memoryStream.Seek(0, SeekOrigin.Begin); + using var reader = new StreamReader(memoryStream, leaveOpen: true); + responseBodyForLogging = await reader.ReadToEndAsync(); + memoryStream.Seek(0, SeekOrigin.Begin); + } + // ทำ logging แบบ fire-and-forget เพื่อไม่ block response _ = Task.Run(async () => { try { - await LogRequestAsync(context, _elasticClient!, startTime, stopwatch, pf, keycloakId, requestBodyJson, memoryStream, caughtException); + await LogRequestAsync(context, _elasticClient!, startTime, stopwatch, pf, keycloakId, requestBodyJson, responseBodyForLogging, caughtException); } catch (Exception ex) { @@ -393,7 +403,7 @@ namespace BMA.EHR.Domain.Middlewares } private async Task LogRequestAsync(HttpContext context, ElasticClient client, DateTime startTime, Stopwatch stopwatch, - GetProfileByKeycloakIdLocal? pf, string keycloakId, string? requestBodyJson, MemoryStream memoryStream, Exception? caughtException) + GetProfileByKeycloakIdLocal? pf, string keycloakId, string? requestBodyJson, string? responseBodyForLogging, Exception? caughtException) { try { @@ -412,48 +422,42 @@ namespace BMA.EHR.Domain.Middlewares string? message = null; string? responseBodyJson = null; - // อ่านข้อมูลจาก Response (ลด serialization ที่ซ้ำ) - if (memoryStream.Length > 0) + // ใช้ response body ที่ส่งมาจากการอ่านก่อนหน้า + if (!string.IsNullOrEmpty(responseBodyForLogging)) { - memoryStream.Seek(0, SeekOrigin.Begin); - var responseBody = new StreamReader(memoryStream).ReadToEnd(); + var contentType = context.Response.ContentType ?? ""; + var isFileResponse = !contentType.StartsWith("application/json") && !contentType.StartsWith("text/html") && ( + contentType.StartsWith("application/") || + contentType.StartsWith("image/") || + contentType.StartsWith("audio/") || + context.Response.Headers.ContainsKey("Content-Disposition") + ); - if (!string.IsNullOrEmpty(responseBody)) + if (isFileResponse) { - var contentType = context.Response.ContentType ?? ""; - var isFileResponse = !contentType.StartsWith("application/json") && !contentType.StartsWith("text/html") && ( - contentType.StartsWith("application/") || - contentType.StartsWith("image/") || - contentType.StartsWith("audio/") || - context.Response.Headers.ContainsKey("Content-Disposition") - ); - - if (isFileResponse) + responseBodyJson = ""; + message = "success"; + } + else + { + // ใช้ response body ที่มีอยู่แล้วโดยไม่ serialize ซ้ำ + responseBodyJson = responseBodyForLogging; + + try { - responseBodyJson = ""; - message = "success"; + var json = JsonSerializer.Deserialize(responseBodyForLogging); + if (json.ValueKind == JsonValueKind.Array) + { + message = "success"; + } + else if (json.TryGetProperty("message", out var messageElement)) + { + message = messageElement.GetString(); + } } - else + catch { - // ใช้ response body ที่มีอยู่แล้วโดยไม่ serialize ซ้ำ - responseBodyJson = responseBody; - - try - { - var json = JsonSerializer.Deserialize(responseBody); - if (json.ValueKind == JsonValueKind.Array) - { - message = "success"; - } - else if (json.TryGetProperty("message", out var messageElement)) - { - message = messageElement.GetString(); - } - } - catch - { - message = caughtException?.Message ?? "Unknown error"; - } + message = caughtException?.Message ?? "Unknown error"; } } } @@ -467,7 +471,7 @@ namespace BMA.EHR.Domain.Middlewares { logType = logType, ip = context.Connection.RemoteIpAddress?.ToString(), - rootId = pf?.RootId, + rootId = pf?.RootDnaId, systemName = SystemName, startTimeStamp = startTime.ToString("o"), endTimeStamp = endTime.ToString("o"), @@ -660,7 +664,7 @@ namespace BMA.EHR.Domain.Middlewares { try { - var apiPath = $"{_configuration["API"]}/org/dotnet/keycloak/{keycloakId}"; + var apiPath = $"{_configuration["API"]}/org/dotnet/by-keycloak/{keycloakId}"; var apiKey = _configuration["API_KEY"]; var apiResult = await GetExternalAPIAsync(apiPath, accessToken ?? "", apiKey); From 2b737de23b29766c65e398fed4b598dcdfce4c40 Mon Sep 17 00:00:00 2001 From: Suphonchai Phoonsawat Date: Thu, 22 Jan 2026 12:24:27 +0700 Subject: [PATCH 059/183] Update ElasticConfiguration URIs and IndexFormats in appsettings for Leave and Insignia --- ...ombinedErrorHandlerAndLoggingMiddleware.cs | 72 ++++++++++++------- BMA.EHR.Insignia/appsettings.json | 4 +- BMA.EHR.Leave/appsettings.json | 4 +- 3 files changed, 50 insertions(+), 30 deletions(-) diff --git a/BMA.EHR.Domain/Middlewares/CombinedErrorHandlerAndLoggingMiddleware.cs b/BMA.EHR.Domain/Middlewares/CombinedErrorHandlerAndLoggingMiddleware.cs index 3391e6a3..57ff69dd 100644 --- a/BMA.EHR.Domain/Middlewares/CombinedErrorHandlerAndLoggingMiddleware.cs +++ b/BMA.EHR.Domain/Middlewares/CombinedErrorHandlerAndLoggingMiddleware.cs @@ -154,25 +154,37 @@ namespace BMA.EHR.Domain.Middlewares memoryStream.Seek(0, SeekOrigin.Begin); } - // ทำ logging แบบ fire-and-forget เพื่อไม่ block response - _ = Task.Run(async () => + // เก็บข้อมูลที่จำเป็นจาก HttpContext ก่อนที่มันจะถูก dispose + var logData = new { - try - { - await LogRequestAsync(context, _elasticClient!, startTime, stopwatch, pf, keycloakId, requestBodyJson, responseBodyForLogging, caughtException); - } - catch (Exception ex) - { - Console.WriteLine($"Background logging error: {ex.Message}"); - } - }); - - // เขียนข้อมูลกลับไปยัง original Response body + RemoteIpAddress = context.Connection.RemoteIpAddress?.ToString(), + HostValue = context.Request.Host.Value, + Method = context.Request.Method, + Path = context.Request.Path.ToString(), + QueryString = context.Request.QueryString.ToString(), + StatusCode = context.Response.StatusCode, + ContentType = context.Response.ContentType ?? "" + }; + + // เขียนข้อมูลกลับไปยัง original Response body ก่อน if (memoryStream.Length > 0) { memoryStream.Seek(0, SeekOrigin.Begin); await memoryStream.CopyToAsync(originalBodyStream); } + + // ทำ logging แบบ await + Console.WriteLine("[DEBUG] Starting logging..."); + try + { + await LogRequestAsync(_elasticClient!, startTime, stopwatch, pf, keycloakId, requestBodyJson, responseBodyForLogging, caughtException, logData); + Console.WriteLine("[DEBUG] Logging completed successfully"); + } + catch (Exception ex) + { + Console.WriteLine($"[ERROR] Logging error: {ex.Message}"); + Console.WriteLine($"[ERROR] Stack trace: {ex.StackTrace}"); + } } } @@ -402,15 +414,16 @@ namespace BMA.EHR.Domain.Middlewares } } - private async Task LogRequestAsync(HttpContext context, ElasticClient client, DateTime startTime, Stopwatch stopwatch, - GetProfileByKeycloakIdLocal? pf, string keycloakId, string? requestBodyJson, string? responseBodyForLogging, Exception? caughtException) + private async Task LogRequestAsync(ElasticClient client, DateTime startTime, Stopwatch stopwatch, + GetProfileByKeycloakIdLocal? pf, string keycloakId, string? requestBodyJson, string? responseBodyForLogging, Exception? caughtException, dynamic contextData) { + Console.WriteLine("[DEBUG] LogRequestAsync called"); try { var processTime = stopwatch.ElapsedMilliseconds; var endTime = DateTime.UtcNow; - var statusCode = caughtException != null ? (int)HttpStatusCode.InternalServerError : context.Response.StatusCode; + var statusCode = caughtException != null ? (int)HttpStatusCode.InternalServerError : (int)contextData.StatusCode; var logType = caughtException != null ? "error" : statusCode switch { @@ -425,12 +438,11 @@ namespace BMA.EHR.Domain.Middlewares // ใช้ response body ที่ส่งมาจากการอ่านก่อนหน้า if (!string.IsNullOrEmpty(responseBodyForLogging)) { - var contentType = context.Response.ContentType ?? ""; + var contentType = (string)contextData.ContentType; var isFileResponse = !contentType.StartsWith("application/json") && !contentType.StartsWith("text/html") && ( contentType.StartsWith("application/") || contentType.StartsWith("image/") || - contentType.StartsWith("audio/") || - context.Response.Headers.ContainsKey("Content-Disposition") + contentType.StartsWith("audio/") ); if (isFileResponse) @@ -470,15 +482,15 @@ namespace BMA.EHR.Domain.Middlewares var logData = new { logType = logType, - ip = context.Connection.RemoteIpAddress?.ToString(), - rootId = pf?.RootDnaId, + ip = (string)contextData.RemoteIpAddress, + rootId = pf?.RootId, systemName = SystemName, startTimeStamp = startTime.ToString("o"), endTimeStamp = endTime.ToString("o"), processTime = processTime, - host = context.Request.Host.Value, - method = context.Request.Method, - endpoint = context.Request.Path + context.Request.QueryString, + host = (string)contextData.HostValue, + method = (string)contextData.Method, + endpoint = (string)contextData.Path + (string)contextData.QueryString, responseCode = statusCode == 304 ? "200" : statusCode.ToString(), responseDescription = message, input = requestBodyJson, @@ -489,11 +501,19 @@ namespace BMA.EHR.Domain.Middlewares exception = caughtException?.ToString() }; - await client.IndexDocumentAsync(logData); + Console.WriteLine($"[DEBUG] Sending log to Elasticsearch: {logType} - {(string)contextData.Method} {(string)contextData.Path}"); + var response = await client.IndexDocumentAsync(logData); + Console.WriteLine($"[DEBUG] Elasticsearch response: IsValid={response.IsValid}, Index={response.Index}"); + + if (!response.IsValid) + { + Console.WriteLine($"[ERROR] Elasticsearch error: {response.OriginalException?.Message ?? response.ServerError?.ToString()}"); + } } catch (Exception ex) { - Console.WriteLine($"Error logging request: {ex.Message}"); + Console.WriteLine($"[ERROR] Error logging request: {ex.Message}"); + Console.WriteLine($"[ERROR] Stack trace: {ex.StackTrace}"); } } diff --git a/BMA.EHR.Insignia/appsettings.json b/BMA.EHR.Insignia/appsettings.json index 3d74b2ae..4f3d3faf 100644 --- a/BMA.EHR.Insignia/appsettings.json +++ b/BMA.EHR.Insignia/appsettings.json @@ -9,8 +9,8 @@ } }, "ElasticConfiguration": { - "Uri": "http://192.168.1.40:9200", - "IndexFormat": "bma-ehr-log-index", + "Uri": "http://192.168.1.63:9200", + "IndexFormat": "hrms-log-index", "SystemName": "insignia" }, "AllowedHosts": "*", diff --git a/BMA.EHR.Leave/appsettings.json b/BMA.EHR.Leave/appsettings.json index d1f58a39..f8562d6e 100644 --- a/BMA.EHR.Leave/appsettings.json +++ b/BMA.EHR.Leave/appsettings.json @@ -9,8 +9,8 @@ } }, "ElasticConfiguration": { - "Uri": "http://192.168.1.40:9200", - "IndexFormat": "bma-ehr-log-index", + "Uri": "http://192.168.1.63:9200", + "IndexFormat": "hrms-log-index", "SystemName": "leave" }, "AllowedHosts": "*", From 2f366374fabac7b33d19da5b7fa87fd46ca67370 Mon Sep 17 00:00:00 2001 From: Suphonchai Phoonsawat Date: Thu, 22 Jan 2026 12:43:52 +0700 Subject: [PATCH 060/183] Refactor user profile retrieval to use new method GetProfileByKeycloakIdNewAsync --- BMA.EHR.Leave/Controllers/LeaveController.cs | 26 ++++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/BMA.EHR.Leave/Controllers/LeaveController.cs b/BMA.EHR.Leave/Controllers/LeaveController.cs index b2544898..32085d94 100644 --- a/BMA.EHR.Leave/Controllers/LeaveController.cs +++ b/BMA.EHR.Leave/Controllers/LeaveController.cs @@ -433,7 +433,7 @@ namespace BMA.EHR.Leave.Service.Controllers var userId = UserId == null ? Guid.Empty : Guid.Parse(UserId); var data = await _userTimeStampRepository.GetLastRecord(userId); - var profile = await _userProfileRepository.GetProfileByKeycloakIdAsync(userId, AccessToken); + var profile = await _userProfileRepository.GetProfileByKeycloakIdNewAsync(userId, AccessToken); if (profile == null) { throw new Exception(GlobalMessages.DataNotFound); @@ -896,7 +896,7 @@ namespace BMA.EHR.Leave.Service.Controllers await _checkInJobStatusRepository.UpdateToProcessingAsync(taskId); } - var profile = await _userProfileRepository.GetProfileByKeycloakIdAsync(userId, data.Token); + var profile = await _userProfileRepository.GetProfileByKeycloakIdNewAsync(userId, data.Token); if (profile == null) return Error(GlobalMessages.DataNotFound, StatusCodes.Status404NotFound); @@ -1206,7 +1206,7 @@ namespace BMA.EHR.Leave.Service.Controllers public async Task> CheckInOldAsync([FromForm] CheckTimeDto data) { var userId = UserId == null ? Guid.Empty : Guid.Parse(UserId); - var profile = await _userProfileRepository.GetProfileByKeycloakIdAsync(userId, AccessToken); + var profile = await _userProfileRepository.GetProfileByKeycloakIdNewAsync(userId, AccessToken); if (profile == null) return Error(GlobalMessages.DataNotFound, StatusCodes.Status404NotFound); @@ -1371,7 +1371,7 @@ namespace BMA.EHR.Leave.Service.Controllers { var userId = UserId == null ? Guid.Empty : Guid.Parse(UserId); - var profile = await _userProfileRepository.GetProfileByKeycloakIdAsync(userId, AccessToken); + var profile = await _userProfileRepository.GetProfileByKeycloakIdNewAsync(userId, AccessToken); if (profile == null) { return Error(GlobalMessages.DataNotFound, StatusCodes.Status404NotFound); @@ -1660,7 +1660,7 @@ namespace BMA.EHR.Leave.Service.Controllers } else { - var profile = await _userProfileRepository.GetProfileByKeycloakIdAsync(d.KeycloakUserId, AccessToken); + var profile = await _userProfileRepository.GetProfileByKeycloakIdNewAsync(d.KeycloakUserId, AccessToken); if (profile == null) { return Error(GlobalMessages.DataNotFound, StatusCodes.Status404NotFound); @@ -2543,7 +2543,7 @@ namespace BMA.EHR.Leave.Service.Controllers var time = DateTime.Now; var userId = UserId != null ? Guid.Parse(UserId) : Guid.Empty; - var profile = await _userProfileRepository.GetProfileByKeycloakIdAsync(userId, AccessToken); + var profile = await _userProfileRepository.GetProfileByKeycloakIdNewAsync(userId, AccessToken); if (profile == null) { throw new Exception(GlobalMessages.DataNotFound); @@ -2663,7 +2663,7 @@ namespace BMA.EHR.Leave.Service.Controllers } var userId = UserId != null ? Guid.Parse(UserId) : Guid.Empty; - var profile = await _userProfileRepository.GetProfileByKeycloakIdAsync(userId, AccessToken); + var profile = await _userProfileRepository.GetProfileByKeycloakIdNewAsync(userId, AccessToken); if (profile == null) { throw new Exception(GlobalMessages.DataNotFound); @@ -2826,7 +2826,7 @@ namespace BMA.EHR.Leave.Service.Controllers foreach (var data in rawDataPaged) { - var profile = await _userProfileRepository.GetProfileByKeycloakIdAsync(data.KeycloakUserId, AccessToken); + var profile = await _userProfileRepository.GetProfileByKeycloakIdNewAsync(data.KeycloakUserId, AccessToken); if (profile == null) { return Error(GlobalMessages.DataNotFound, StatusCodes.Status404NotFound); @@ -3006,7 +3006,7 @@ namespace BMA.EHR.Leave.Service.Controllers // change user timestamp var processTimeStamp = await _processUserTimeStampRepository.GetTimestampByDateAsync(requestData.KeycloakUserId, requestData.CheckDate.Date); - var profile = await _userProfileRepository.GetProfileByKeycloakIdAsync(requestData.KeycloakUserId, AccessToken); + var profile = await _userProfileRepository.GetProfileByKeycloakIdNewAsync(requestData.KeycloakUserId, AccessToken); if (processTimeStamp == null) { @@ -3160,7 +3160,7 @@ namespace BMA.EHR.Leave.Service.Controllers requestData.Comment = req.Reason; await _additionalCheckRequestRepository.UpdateAsync(requestData); - var profile = await _userProfileRepository.GetProfileByKeycloakIdAsync(requestData.KeycloakUserId, AccessToken); + var profile = await _userProfileRepository.GetProfileByKeycloakIdNewAsync(requestData.KeycloakUserId, AccessToken); var recvId = new List { profile.Id }; await _notificationRepository.PushNotificationsAsync(recvId.ToArray(), "ลงเวลากรณีพิเศษ", "การขอลงเวลากรณีพิเศษของคุณไม่ได้รับการอนุมัติ", "", "", true, false); @@ -3204,7 +3204,7 @@ namespace BMA.EHR.Leave.Service.Controllers } else { - var profile = await _userProfileRepository.GetProfileByKeycloakIdAsync(d.KeycloakUserId, AccessToken); + var profile = await _userProfileRepository.GetProfileByKeycloakIdNewAsync(d.KeycloakUserId, AccessToken); if (profile == null) { return Error(GlobalMessages.DataNotFound, StatusCodes.Status404NotFound); @@ -3298,7 +3298,7 @@ namespace BMA.EHR.Leave.Service.Controllers foreach (var data in rawData) { - var profile = await _userProfileRepository.GetProfileByKeycloakIdAsync(data.KeycloakUserId, AccessToken); + var profile = await _userProfileRepository.GetProfileByKeycloakIdNewAsync(data.KeycloakUserId, AccessToken); if (profile == null) { return Error(GlobalMessages.DataNotFound, StatusCodes.Status404NotFound); @@ -3611,7 +3611,7 @@ namespace BMA.EHR.Leave.Service.Controllers [ProducesResponseType(StatusCodes.Status500InternalServerError)] public async Task> GetLeaveSummaryByProfileAsync(Guid id, [FromBody] GetLeaveSummaryDto req) { - var profile = await _userProfileRepository.GetProfileByKeycloakIdAsync(id, AccessToken); + var profile = await _userProfileRepository.GetProfileByKeycloakIdNewAsync(id, AccessToken); var thisYear = DateTime.Now.Year; var startDate = req.StartDate; From 4e4eec3d844cea341c3fbb56ff9ee824777e5ccb Mon Sep 17 00:00:00 2001 From: Suphonchai Phoonsawat Date: Fri, 23 Jan 2026 09:32:17 +0700 Subject: [PATCH 061/183] Add job status check for pending or processing check-in/check-out requests --- BMA.EHR.Leave/Controllers/LeaveController.cs | 41 +++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/BMA.EHR.Leave/Controllers/LeaveController.cs b/BMA.EHR.Leave/Controllers/LeaveController.cs index 32085d94..8faf42c2 100644 --- a/BMA.EHR.Leave/Controllers/LeaveController.cs +++ b/BMA.EHR.Leave/Controllers/LeaveController.cs @@ -530,6 +530,26 @@ 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; + + // ตรวจสอบว่ามีงานที่กำลัง pending หรือ processing อยู่หรือไม่ + var existingJobs = await _checkInJobStatusRepository.GetPendingOrProcessingJobsAsync(userId); + if (existingJobs != null && existingJobs.Count > 0) + { + // กรองเฉพาะงานที่เป็นประเภทเดียวกัน (CHECK_IN หรือ CHECK_OUT) + var checkType = data.CheckInId == null ? "CHECK_IN" : "CHECK_OUT"; + var sameTypeJob = existingJobs.FirstOrDefault(j => j.CheckType == checkType); + + if (sameTypeJob != null) + { + // ตรวจสอบว่างานที่มีอยู่ถูกสร้างเมื่อไหร่ ถ้าเกิน 2 นาทีให้สร้างใหม่ได้ + var timeDiff = (currentDate - sameTypeJob.CreatedDate).TotalMinutes; + if (timeDiff < 2) + { + return Error($"มีงาน {checkType} กำลังดำเนินการอยู่ กรุณารอสักครู่", StatusCodes.Status409Conflict); + } + } + } + var checkFileBytes = new byte[0]; // fix issue : ระบบลงเวลาปฏิบัติงาน>>รูปภาพไม่แสดงในฝั่งของ Admin #804 @@ -564,6 +584,8 @@ namespace BMA.EHR.Leave.Service.Controllers }; var channel = _objectPool.Get(); + CheckInJobStatus? jobStatus = null; + try { var queue = _configuration["Rabbit:Queue"] ?? "basic-queue"; @@ -576,7 +598,7 @@ namespace BMA.EHR.Leave.Service.Controllers properties.MessageId = taskId; // บันทึกสถานะงานก่อนส่งไป RabbitMQ - var jobStatus = new CheckInJobStatus + jobStatus = new CheckInJobStatus { TaskId = Guid.Parse(taskId), KeycloakUserId = userId, @@ -593,6 +615,7 @@ namespace BMA.EHR.Leave.Service.Controllers }; await _checkInJobStatusRepository.AddAsync(jobStatus); + // ส่งไป RabbitMQ channel.BasicPublish(exchange: "", routingKey: queue, basicProperties: properties, @@ -600,6 +623,22 @@ namespace BMA.EHR.Leave.Service.Controllers return Success(new { date = currentDate, taskId = taskId, keycloakId = userId }); } + catch (Exception ex) + { + // ถ้าส่งไป queue ไม่สำเร็จ ให้ลบ job status ที่สร้างไว้ออก + if (jobStatus != null) + { + try + { + await _checkInJobStatusRepository.DeleteAsync(jobStatus); + } + catch + { + // Ignore delete error + } + } + throw new Exception($"ไม่สามารถส่งงานไปยัง Queue ได้: {ex.Message}"); + } finally { _objectPool.Return(channel); From e1c7688913936a8e062712e69704d375911c26aa Mon Sep 17 00:00:00 2001 From: Suphonchai Phoonsawat Date: Fri, 23 Jan 2026 20:27:22 +0700 Subject: [PATCH 062/183] Update LeaveController to set end times based on duty schedule for check-in/check-out #2228 --- BMA.EHR.Leave/Controllers/LeaveController.cs | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/BMA.EHR.Leave/Controllers/LeaveController.cs b/BMA.EHR.Leave/Controllers/LeaveController.cs index 8faf42c2..7f90fbe1 100644 --- a/BMA.EHR.Leave/Controllers/LeaveController.cs +++ b/BMA.EHR.Leave/Controllers/LeaveController.cs @@ -1015,13 +1015,18 @@ namespace BMA.EHR.Leave.Service.Controllers }; var startTime = ""; + var endTime = ""; if (!data.IsLocation && data.LocationName == "ไปประชุม / อบรม / สัมมนา") { //startTime = "09:30"; startTime = "10:30"; + endTime = "12:00"; } else + { startTime = duty.StartTimeMorning; + endTime = duty.EndTimeMorning; + } string checkInStatus = "NORMAL"; var leaveReq = await _leaveRequestRepository.GetLeavePeriodAsync(userId, currentDate.Date); @@ -1126,15 +1131,22 @@ namespace BMA.EHR.Leave.Service.Controllers } var endTime = ""; + var startTime = ""; + var endTimeMorning = ""; if (!data.IsLocation && data.LocationName == "ไปประชุม / อบรม / สัมมนา") { - //startTime = "09:30"; + startTime = "13:00"; endTime = "14:30"; + endTimeMorning = "12:00"; } else + { endTime = duty.EndTimeAfternoon; + startTime = duty.StartTimeAfternoon; + endTimeMorning = duty.EndTimeMorning; + } - var endTimeMorning = duty.EndTimeMorning; + string checkOutStatus = "NORMAL"; var leaveReq = await _leaveRequestRepository.GetLeavePeriodAsync(userId, currentDate.Date); @@ -1161,7 +1173,7 @@ namespace BMA.EHR.Leave.Service.Controllers "NORMAL" : "ABSENT" : DateTime.Parse(currentDate.ToString("yyyy-MM-dd HH:mm")) < - DateTime.Parse($"{currentDate.ToString("yyyy-MM-dd")} {duty.EndTimeMorning}") ? + DateTime.Parse($"{currentDate.ToString("yyyy-MM-dd")} {endTimeMorning}") ? "ABSENT" : "NORMAL"; } @@ -1178,7 +1190,7 @@ namespace BMA.EHR.Leave.Service.Controllers "NORMAL" : "ABSENT" : DateTime.Parse(currentDate.ToString("yyyy-MM-dd HH:mm")) < - DateTime.Parse($"{currentDate.ToString("yyyy-MM-dd")} {duty.EndTimeMorning}") ? + DateTime.Parse($"{currentDate.ToString("yyyy-MM-dd")} {endTimeMorning}") ? "ABSENT" : "NORMAL"; } From 9bd6017ded190cf9e87a4c31d21bc95304c41fc0 Mon Sep 17 00:00:00 2001 From: Suphonchai Phoonsawat Date: Fri, 23 Jan 2026 20:35:54 +0700 Subject: [PATCH 063/183] Update LeaveController to adjust check-in/check-out times based on user duty schedule #2223 --- BMA.EHR.Leave/Controllers/LeaveController.cs | 24 ++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/BMA.EHR.Leave/Controllers/LeaveController.cs b/BMA.EHR.Leave/Controllers/LeaveController.cs index 7f90fbe1..ebfd5c96 100644 --- a/BMA.EHR.Leave/Controllers/LeaveController.cs +++ b/BMA.EHR.Leave/Controllers/LeaveController.cs @@ -3634,6 +3634,30 @@ namespace BMA.EHR.Leave.Service.Controllers var data = await _processUserTimeStampRepository.GetByIdAsync(id); if (data == null) return Error(GlobalMessages.DataNotFound); + + if (data.CheckInStatus == "NORMAL" || data.CheckOutStatus == "NORMAL") + { + var profile = await _userProfileRepository.GetProfileByKeycloakIdNewAsync(id, AccessToken); + var defaultRound = await _dutyTimeRepository.GetDefaultAsync(); + if (defaultRound == null) + { + return Error("ไม่พบรอบการลงเวลา Default", StatusCodes.Status404NotFound); + } + var effectiveDate = await _userDutyTimeRepository.GetLastEffectRound(profile!.Id); + var roundId = effectiveDate != null ? effectiveDate.DutyTimeId : Guid.Empty; + var userRound = await _dutyTimeRepository.GetByIdAsync(roundId); + + var duty = userRound ?? defaultRound; + if (req.CheckInStatus == "NORMAL") + { + data.CheckIn = DateTime.Parse($"{data.CheckIn.Date.ToString("yyyy-MM-dd")} {duty.StartTimeMorning}"); + } + if (req.CheckOutStatus == "NORMAL" ) + { + var checkOutTime = data.CheckOut != null ? data.CheckOut.Value : data.CheckIn; + data.CheckOut = DateTime.Parse($"{checkOutTime.Date.ToString("yyyy-MM-dd")} {duty.EndTimeAfternoon}"); + } + } data.CheckInStatus = req.CheckInStatus; data.CheckOutStatus = req.CheckOutStatus; From ecf5ada7edaf242c76f2ebf4a6e609a272d76ca4 Mon Sep 17 00:00:00 2001 From: Suphonchai Phoonsawat Date: Fri, 23 Jan 2026 20:55:21 +0700 Subject: [PATCH 064/183] Update LeaveController to conditionally set check-out time based on existing value and duty schedule --- BMA.EHR.Leave/Controllers/LeaveController.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/BMA.EHR.Leave/Controllers/LeaveController.cs b/BMA.EHR.Leave/Controllers/LeaveController.cs index ebfd5c96..3346a4a4 100644 --- a/BMA.EHR.Leave/Controllers/LeaveController.cs +++ b/BMA.EHR.Leave/Controllers/LeaveController.cs @@ -3655,7 +3655,12 @@ namespace BMA.EHR.Leave.Service.Controllers if (req.CheckOutStatus == "NORMAL" ) { var checkOutTime = data.CheckOut != null ? data.CheckOut.Value : data.CheckIn; - data.CheckOut = DateTime.Parse($"{checkOutTime.Date.ToString("yyyy-MM-dd")} {duty.EndTimeAfternoon}"); + var oldCheckOutTime = data.CheckOut != null ? data.CheckOut.Value : DateTime.Now; + var roundCheckOutTime = DateTime.Parse($"{checkOutTime.Date.ToString("yyyy-MM-dd")} {duty.EndTimeAfternoon}"); + if (oldCheckOutTime < roundCheckOutTime) + { + data.CheckOut = roundCheckOutTime; + } } } From c1d689ebfafe6f8687b484966c866038ec214ac9 Mon Sep 17 00:00:00 2001 From: Suphonchai Phoonsawat Date: Fri, 23 Jan 2026 21:25:34 +0700 Subject: [PATCH 065/183] Update LeaveController to adjust check-in/check-out times based on location for meetings --- BMA.EHR.Leave/Controllers/LeaveController.cs | 24 +++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/BMA.EHR.Leave/Controllers/LeaveController.cs b/BMA.EHR.Leave/Controllers/LeaveController.cs index 3346a4a4..9de17c1f 100644 --- a/BMA.EHR.Leave/Controllers/LeaveController.cs +++ b/BMA.EHR.Leave/Controllers/LeaveController.cs @@ -3637,7 +3637,8 @@ namespace BMA.EHR.Leave.Service.Controllers if (data.CheckInStatus == "NORMAL" || data.CheckOutStatus == "NORMAL") { - var profile = await _userProfileRepository.GetProfileByKeycloakIdNewAsync(id, AccessToken); + var userId = UserId == null ? Guid.Empty : Guid.Parse(UserId); + var profile = await _userProfileRepository.GetProfileByKeycloakIdNewAsync(userId, AccessToken); var defaultRound = await _dutyTimeRepository.GetDefaultAsync(); if (defaultRound == null) { @@ -3650,13 +3651,30 @@ namespace BMA.EHR.Leave.Service.Controllers var duty = userRound ?? defaultRound; if (req.CheckInStatus == "NORMAL") { - data.CheckIn = DateTime.Parse($"{data.CheckIn.Date.ToString("yyyy-MM-dd")} {duty.StartTimeMorning}"); + if(data.CheckInLocationName == "ไปประชุม / อบรม / สัมมนา") + { + data.CheckIn = DateTime.Parse($"{data.CheckIn.Date.ToString("yyyy-MM-dd")} 10:30"); + } + else + { + data.CheckIn = DateTime.Parse($"{data.CheckIn.Date.ToString("yyyy-MM-dd")} {duty.StartTimeMorning}"); + } + } if (req.CheckOutStatus == "NORMAL" ) { var checkOutTime = data.CheckOut != null ? data.CheckOut.Value : data.CheckIn; var oldCheckOutTime = data.CheckOut != null ? data.CheckOut.Value : DateTime.Now; - var roundCheckOutTime = DateTime.Parse($"{checkOutTime.Date.ToString("yyyy-MM-dd")} {duty.EndTimeAfternoon}"); + var roundCheckOutTime = DateTime.Now; + if(data.CheckOutLocationName == "ไปประชุม / อบรม / สัมมนา") + { + roundCheckOutTime = DateTime.Parse($"{checkOutTime.Date.ToString("yyyy-MM-dd")} 14:30"); + } + else + { + roundCheckOutTime = DateTime.Parse($"{checkOutTime.Date.ToString("yyyy-MM-dd")} {duty.EndTimeAfternoon}"); + } + if (oldCheckOutTime < roundCheckOutTime) { data.CheckOut = roundCheckOutTime; From 22a7a8c17c6e7051ed96b2922363733196c55f5e Mon Sep 17 00:00:00 2001 From: Suphonchai Phoonsawat Date: Mon, 26 Jan 2026 12:04:58 +0700 Subject: [PATCH 066/183] Update LeaveController to refine check-in/check-out logic based on meeting location #2223 --- BMA.EHR.Leave/Controllers/LeaveController.cs | 79 ++++++++++---------- 1 file changed, 39 insertions(+), 40 deletions(-) diff --git a/BMA.EHR.Leave/Controllers/LeaveController.cs b/BMA.EHR.Leave/Controllers/LeaveController.cs index 9de17c1f..4787de39 100644 --- a/BMA.EHR.Leave/Controllers/LeaveController.cs +++ b/BMA.EHR.Leave/Controllers/LeaveController.cs @@ -3635,53 +3635,52 @@ namespace BMA.EHR.Leave.Service.Controllers if (data == null) return Error(GlobalMessages.DataNotFound); - if (data.CheckInStatus == "NORMAL" || data.CheckOutStatus == "NORMAL") +  //if (data.CheckInStatus == "NORMAL" || data.CheckOutStatus == "NORMAL") + + var userId = UserId == null ? Guid.Empty : Guid.Parse(UserId); + var profile = await _userProfileRepository.GetProfileByKeycloakIdNewAsync(userId, AccessToken); + var defaultRound = await _dutyTimeRepository.GetDefaultAsync(); + if (defaultRound == null) { - var userId = UserId == null ? Guid.Empty : Guid.Parse(UserId); - var profile = await _userProfileRepository.GetProfileByKeycloakIdNewAsync(userId, AccessToken); - var defaultRound = await _dutyTimeRepository.GetDefaultAsync(); - if (defaultRound == null) - { - return Error("ไม่พบรอบการลงเวลา Default", StatusCodes.Status404NotFound); - } - var effectiveDate = await _userDutyTimeRepository.GetLastEffectRound(profile!.Id); - var roundId = effectiveDate != null ? effectiveDate.DutyTimeId : Guid.Empty; - var userRound = await _dutyTimeRepository.GetByIdAsync(roundId); + return Error("ไม่พบรอบการลงเวลา Default", StatusCodes.Status404NotFound); + } + var effectiveDate = await _userDutyTimeRepository.GetLastEffectRound(profile!.Id); + var roundId = effectiveDate != null ? effectiveDate.DutyTimeId : Guid.Empty; + var userRound = await _dutyTimeRepository.GetByIdAsync(roundId); - var duty = userRound ?? defaultRound; - if (req.CheckInStatus == "NORMAL") + var duty = userRound ?? defaultRound; + if (req.CheckInStatus == "NORMAL") + { + if(data.CheckInLocationName == "ไปประชุม / อบรม / สัมมนา") { - if(data.CheckInLocationName == "ไปประชุม / อบรม / สัมมนา") - { - data.CheckIn = DateTime.Parse($"{data.CheckIn.Date.ToString("yyyy-MM-dd")} 10:30"); - } - else - { - data.CheckIn = DateTime.Parse($"{data.CheckIn.Date.ToString("yyyy-MM-dd")} {duty.StartTimeMorning}"); - } - + data.CheckIn = DateTime.Parse($"{data.CheckIn.Date.ToString("yyyy-MM-dd")} 10:30"); } - if (req.CheckOutStatus == "NORMAL" ) + else { - var checkOutTime = data.CheckOut != null ? data.CheckOut.Value : data.CheckIn; - var oldCheckOutTime = data.CheckOut != null ? data.CheckOut.Value : DateTime.Now; - var roundCheckOutTime = DateTime.Now; - if(data.CheckOutLocationName == "ไปประชุม / อบรม / สัมมนา") - { - roundCheckOutTime = DateTime.Parse($"{checkOutTime.Date.ToString("yyyy-MM-dd")} 14:30"); - } - else - { - roundCheckOutTime = DateTime.Parse($"{checkOutTime.Date.ToString("yyyy-MM-dd")} {duty.EndTimeAfternoon}"); - } - - if (oldCheckOutTime < roundCheckOutTime) - { - data.CheckOut = roundCheckOutTime; - } + data.CheckIn = DateTime.Parse($"{data.CheckIn.Date.ToString("yyyy-MM-dd")} {duty.StartTimeMorning}"); + } + + } + if (req.CheckOutStatus == "NORMAL" ) + { + var checkOutTime = data.CheckOut != null ? data.CheckOut.Value : data.CheckIn; + var oldCheckOutTime = data.CheckOut != null ? data.CheckOut.Value : DateTime.Now; + var roundCheckOutTime = DateTime.Now; + if(data.CheckOutLocationName == "ไปประชุม / อบรม / สัมมนา") + { + roundCheckOutTime = DateTime.Parse($"{checkOutTime.Date.ToString("yyyy-MM-dd")} 14:30"); + } + else + { + roundCheckOutTime = DateTime.Parse($"{checkOutTime.Date.ToString("yyyy-MM-dd")} {duty.EndTimeAfternoon}"); + } + + if (oldCheckOutTime < roundCheckOutTime) + { + data.CheckOut = roundCheckOutTime; } } - + data.CheckInStatus = req.CheckInStatus; data.CheckOutStatus = req.CheckOutStatus; data.EditReason = req.Reason; From 2e6a81ff31b186b6964da750f51b27765a16c297 Mon Sep 17 00:00:00 2001 From: harid Date: Mon, 26 Jan 2026 18:21:33 +0700 Subject: [PATCH 067/183] =?UTF-8?q?Task=20#2233=20=E0=B8=A3=E0=B8=B2?= =?UTF-8?q?=E0=B8=A2=E0=B8=87=E0=B8=B2=E0=B8=99=E0=B9=83=E0=B8=9A=E0=B8=A5?= =?UTF-8?q?=E0=B8=B2=20=E0=B9=81=E0=B8=AA=E0=B8=94=E0=B8=87=E0=B8=AA?= =?UTF-8?q?=E0=B8=B3=E0=B8=99=E0=B8=B1=E0=B8=81=E0=B8=87=E0=B8=B2=E0=B8=99?= =?UTF-8?q?=20=E0=B8=81.=E0=B8=81.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Repositories/UserProfileRepository.cs | 30 ++ .../Leaves/GetProfileLeaveByKeycloakDto.cs | 10 +- .../Controllers/LeaveReportController.cs | 302 +++++++++++------- 3 files changed, 227 insertions(+), 115 deletions(-) diff --git a/BMA.EHR.Application/Repositories/UserProfileRepository.cs b/BMA.EHR.Application/Repositories/UserProfileRepository.cs index 9ef3f76e..b868a4e9 100644 --- a/BMA.EHR.Application/Repositories/UserProfileRepository.cs +++ b/BMA.EHR.Application/Repositories/UserProfileRepository.cs @@ -233,6 +233,36 @@ namespace BMA.EHR.Application.Repositories } } + public async Task GetProfileLeaveReportByKeycloakIdAsync(Guid keycloakId, string? accessToken, string? report) + { + try + { + var apiPath = $"{_configuration["API"]}/org/dotnet/profile-leave/keycloak"; + var apiKey = _configuration["API_KEY"]; + var body = new + { + keycloakId = keycloakId, + report = report + }; + + var profiles = new List(); + + var apiResult = await PostExternalAPIAsync(apiPath, accessToken, body, apiKey); + if (apiResult != null) + { + var raw = JsonConvert.DeserializeObject(apiResult); + if (raw != null) + return raw.Result; + } + + return null; + } + catch + { + throw; + } + } + public async Task GetProfileByProfileIdAsync(Guid profileId, string? accessToken) { try diff --git a/BMA.EHR.Application/Responses/Leaves/GetProfileLeaveByKeycloakDto.cs b/BMA.EHR.Application/Responses/Leaves/GetProfileLeaveByKeycloakDto.cs index 52a9b948..c6c79bfd 100644 --- a/BMA.EHR.Application/Responses/Leaves/GetProfileLeaveByKeycloakDto.cs +++ b/BMA.EHR.Application/Responses/Leaves/GetProfileLeaveByKeycloakDto.cs @@ -2,6 +2,11 @@ { public class GetProfileLeaveByKeycloakDto { + public string ProfileType { get; set; } + public string Prefix { get; set; } + public string FirstName { get; set; } + public string LastName { get; set; } + public string CitizenId { get; set; } public DateTime BirthDate { get; set; } public DateTime RetireDate { get; set; } public string GovAge { get; set; } = string.Empty; @@ -10,11 +15,14 @@ public DateTime DateCurrent { get; set; } public int Amount { get; set; } public string? TelephoneNumber { get; set; } = string.Empty; - public string PositionName { get; set; } = string.Empty; + public string Position { get; set; } = string.Empty; public string PosLevel { get; set; } = string.Empty; public string PosType { get; set; } = string.Empty; + public string? PositionLeaveName { get; set; } + public string? PosExecutiveName { get; set; } public string CurrentAddress { get; set; } = string.Empty; public string Oc { get; set; } = string.Empty; + public bool isCommission { get; set; } = false; public string Root { get; set; } = string.Empty; public string? Child1 { get; set; } public string? Child2 { get; set; } diff --git a/BMA.EHR.Leave/Controllers/LeaveReportController.cs b/BMA.EHR.Leave/Controllers/LeaveReportController.cs index 5dad15fa..f8c22956 100644 --- a/BMA.EHR.Leave/Controllers/LeaveReportController.cs +++ b/BMA.EHR.Leave/Controllers/LeaveReportController.cs @@ -137,7 +137,12 @@ namespace BMA.EHR.Leave.Service.Controllers private async Task GetReport01(LeaveRequest data) { - var profile = await _userProfileRepository.GetProfileByKeycloakIdAsync(data.KeycloakUserId, AccessToken); + //var profile = await _userProfileRepository.GetProfileByKeycloakIdAsync(data.KeycloakUserId, AccessToken); + //if (profile == null) + //{ + // return Error(GlobalMessages.DataNotFound, StatusCodes.Status404NotFound); + //} + var profile = await _userProfileRepository.GetProfileLeaveReportByKeycloakIdAsync(data.KeycloakUserId, AccessToken, "leave9"); if (profile == null) { return Error(GlobalMessages.DataNotFound, StatusCodes.Status404NotFound); @@ -152,7 +157,11 @@ namespace BMA.EHR.Leave.Service.Controllers var startFiscalYear = new DateTime(data.LeaveStartDate.Year - 1, 10, 1); var endFiscalYear = data.LeaveStartDate.Date.AddDays(-1); // นับจากวันที่ยื่นลา var sumLeave = await _leaveRequestRepository.GetSumApproveLeaveTotalByTypeAndRangeForUser(data.KeycloakUserId, data.Type.Id, startFiscalYear, endFiscalYear); - var approveResult = await GetApproverData(data.Approvers); + var Oc = profile.isCommission == false + ? profile.Oc.ToThaiNumber() + : profile.Oc.Replace("สำนักงานคณะกรรมการข้าราชการกรุงเทพมหานคร", "สำนักงาน ก.ก.").ToThaiNumber(); + var approveResult = await GetApproverData(data.Approvers, profile.isCommission); + return new { template = "leave9", @@ -183,10 +192,10 @@ namespace BMA.EHR.Leave.Service.Controllers organizationName = profile!.Oc!.ToThaiNumber(), posExOrg = !string.IsNullOrEmpty(profile.PositionLeaveName) && (profile.PositionLeaveName.Contains("อำนวยการ") || profile.PositionLeaveName.Contains("บริหาร")) - ? new[] { (profile!.Oc!.ToThaiNumber()) } + ? new[] { Oc } : !string.IsNullOrEmpty(profile.PosExecutiveName) - ? new[] { (profile.PosExecutiveName.ToThaiNumber()), (profile!.Oc!.ToThaiNumber()) } - : new[] { (profile!.Oc!.ToThaiNumber()) }, + ? new[] { profile.PosExecutiveName.ToThaiNumber(), Oc } + : new[] { Oc }, leaveDetail = data.LeaveDetail.ToThaiNumber(), leaveDateStart = data.LeaveStartDate.Date.ToThaiShortDate().ToThaiNumber(), leaveDateEnd = data.LeaveEndDate.Date.ToThaiShortDate().ToThaiNumber(), @@ -227,14 +236,22 @@ namespace BMA.EHR.Leave.Service.Controllers private async Task GetReport02(LeaveRequest data) { - var profile = await _userProfileRepository.GetProfileByKeycloakIdAsync(data.KeycloakUserId, AccessToken); + //var profile = await _userProfileRepository.GetProfileByKeycloakIdAsync(data.KeycloakUserId, AccessToken); + //if (profile == null) + //{ + // return Error(GlobalMessages.DataNotFound, StatusCodes.Status404NotFound); + //} + var profile = await _userProfileRepository.GetProfileLeaveReportByKeycloakIdAsync(data.KeycloakUserId, AccessToken, "leave10"); if (profile == null) { return Error(GlobalMessages.DataNotFound, StatusCodes.Status404NotFound); } var fullName = $"{profile!.Prefix}{profile!.FirstName} {profile!.LastName}"; - var approveResult = await GetApproverData(data.Approvers); + var Oc = profile.isCommission == false + ? profile.Oc.ToThaiNumber() + : profile.Oc.Replace("สำนักงานคณะกรรมการข้าราชการกรุงเทพมหานคร", "สำนักงาน ก.ก.").ToThaiNumber(); + var approveResult = await GetApproverData(data.Approvers, profile.isCommission); return new { template = "leave10", @@ -265,10 +282,10 @@ namespace BMA.EHR.Leave.Service.Controllers organizationName = profile!.Oc!.ToThaiNumber(), posExOrg = !string.IsNullOrEmpty(profile.PositionLeaveName) && (profile.PositionLeaveName.Contains("อำนวยการ") || profile.PositionLeaveName.Contains("บริหาร")) - ? new[] { (profile!.Oc!.ToThaiNumber()) } + ? new[] { Oc } : !string.IsNullOrEmpty(profile.PosExecutiveName) - ? new[] { (profile.PosExecutiveName.ToThaiNumber()), (profile!.Oc!.ToThaiNumber()) } - : new[] { (profile!.Oc!.ToThaiNumber()) }, + ? new[] { profile.PosExecutiveName.ToThaiNumber(), Oc } + : new[] { Oc }, wifeDayName = data.WifeDayName ?? "", wifeDayDateBorn = data.WifeDayDateBorn == null || data.WifeDayDateBorn == "" ? "" : DateTime.Parse(data.WifeDayDateBorn).ToThaiShortDate().ToThaiNumber(), leaveDateStart = data.LeaveStartDate.Date.ToThaiShortDate().ToThaiNumber(), @@ -289,7 +306,12 @@ namespace BMA.EHR.Leave.Service.Controllers private async Task GetReport03(LeaveRequest data) { - var profile = await _userProfileRepository.GetProfileByKeycloakIdAsync(data.KeycloakUserId, AccessToken); + //var profile = await _userProfileRepository.GetProfileByKeycloakIdAsync(data.KeycloakUserId, AccessToken); + //if (profile == null) + //{ + // return Error(GlobalMessages.DataNotFound, StatusCodes.Status404NotFound); + //} + var profile = await _userProfileRepository.GetProfileLeaveReportByKeycloakIdAsync(data.KeycloakUserId, AccessToken, "leave11"); if (profile == null) { return Error(GlobalMessages.DataNotFound, StatusCodes.Status404NotFound); @@ -313,8 +335,10 @@ namespace BMA.EHR.Leave.Service.Controllers var sumLeave = leaveData == null ? 0.0 : leaveData.LeaveDaysUsed; var leaveLimit = leaveData == null ? 0.0 : leaveData.LeaveDays; var extendLeave = leaveLimit - 10; - - var approveResult = await GetApproverData(data.Approvers); + var Oc = profile.isCommission == false + ? profile.Oc.ToThaiNumber() + : profile.Oc.Replace("สำนักงานคณะกรรมการข้าราชการกรุงเทพมหานคร", "สำนักงาน ก.ก.").ToThaiNumber(); + var approveResult = await GetApproverData(data.Approvers, profile.isCommission); return new { template = "leave11", @@ -345,10 +369,10 @@ namespace BMA.EHR.Leave.Service.Controllers organizationName = profile!.Oc!.ToThaiNumber(), posExOrg = !string.IsNullOrEmpty(profile.PositionLeaveName) && (profile.PositionLeaveName.Contains("อำนวยการ") || profile.PositionLeaveName.Contains("บริหาร")) - ? new[] { (profile!.Oc!.ToThaiNumber()) } + ? new[] { Oc } : !string.IsNullOrEmpty(profile.PosExecutiveName) - ? new[] { (profile.PosExecutiveName.ToThaiNumber()), (profile!.Oc!.ToThaiNumber()) } - : new[] { (profile!.Oc!.ToThaiNumber()) }, + ? new[] { profile.PosExecutiveName.ToThaiNumber(), Oc } + : new[] { Oc }, restDayOldTotal = extendLeave.ToString().ToThaiNumber(), restDayCurrentTotal = (10).ToString().ToThaiNumber(), @@ -379,7 +403,12 @@ namespace BMA.EHR.Leave.Service.Controllers private async Task GetReport04(LeaveRequest data, bool isHajj = false) { - var profile = await _userProfileRepository.GetProfileByKeycloakIdAsync(data.KeycloakUserId, AccessToken); + //var profile = await _userProfileRepository.GetProfileByKeycloakIdAsync(data.KeycloakUserId, AccessToken); + //if (profile == null) + //{ + // return Error(GlobalMessages.DataNotFound, StatusCodes.Status404NotFound); + //} + var profile = await _userProfileRepository.GetProfileLeaveReportByKeycloakIdAsync(data.KeycloakUserId, AccessToken, isHajj ? "leave13": "leave12"); if (profile == null) { return Error(GlobalMessages.DataNotFound, StatusCodes.Status404NotFound); @@ -399,7 +428,10 @@ namespace BMA.EHR.Leave.Service.Controllers { isHajj = true; } - var approveResult = await GetApproverData(data.Approvers); + var Oc = profile.isCommission == false + ? profile.Oc.ToThaiNumber() + : profile.Oc.Replace("สำนักงานคณะกรรมการข้าราชการกรุงเทพมหานคร", "สำนักงาน ก.ก.").ToThaiNumber(); + var approveResult = await GetApproverData(data.Approvers, profile.isCommission); if (isHajj == true) { return new @@ -432,11 +464,10 @@ namespace BMA.EHR.Leave.Service.Controllers organizationName = profile!.Oc!.ToThaiNumber(), posExOrg = !string.IsNullOrEmpty(profile.PositionLeaveName) && (profile.PositionLeaveName.Contains("อำนวยการ") || profile.PositionLeaveName.Contains("บริหาร")) - ? new[] { (profile!.Oc!.ToThaiNumber()) } + ? new[] { Oc } : !string.IsNullOrEmpty(profile.PosExecutiveName) - ? new[] { (profile.PosExecutiveName.ToThaiNumber()), (profile!.Oc!.ToThaiNumber()) } - : new[] { (profile!.Oc!.ToThaiNumber()) }, - + ? new[] { profile.PosExecutiveName.ToThaiNumber(), Oc } + : new[] { Oc }, leavegovernmentDate = data.LeaveGovernmentDate == null ? "" : data.LeaveGovernmentDate.Value.Date.ToThaiShortDate().ToThaiNumber(), hajjDayStatus = data.HajjDayStatus, @@ -487,10 +518,10 @@ namespace BMA.EHR.Leave.Service.Controllers organizationName = profile!.Oc!.ToThaiNumber(), posExOrg = !string.IsNullOrEmpty(profile.PositionLeaveName) && (profile.PositionLeaveName.Contains("อำนวยการ") || profile.PositionLeaveName.Contains("บริหาร")) - ? new[] { (profile!.Oc!.ToThaiNumber()) } + ? new[] { Oc } : !string.IsNullOrEmpty(profile.PosExecutiveName) - ? new[] { (profile.PosExecutiveName.ToThaiNumber()), (profile!.Oc!.ToThaiNumber()) } - : new[] { (profile!.Oc!.ToThaiNumber()) }, + ? new[] { profile.PosExecutiveName.ToThaiNumber(), Oc } + : new[] { Oc }, leavebirthDate = data.LeaveBirthDate == null ? "" : data.LeaveBirthDate.Value.Date.ToThaiShortDate().ToThaiNumber(), leavegovernmentDate = data.LeaveGovernmentDate == null ? "" : data.LeaveGovernmentDate.Value.Date.ToThaiShortDate().ToThaiNumber(), @@ -520,12 +551,16 @@ namespace BMA.EHR.Leave.Service.Controllers private async Task GetReport05(LeaveRequest data) { - var profile = await _userProfileRepository.GetProfileByKeycloakIdAsync(data.KeycloakUserId, AccessToken); + //var profile = await _userProfileRepository.GetProfileByKeycloakIdAsync(data.KeycloakUserId, AccessToken); + //if (profile == null) + //{ + // return Error(GlobalMessages.DataNotFound, StatusCodes.Status404NotFound); + //} + var profile = await _userProfileRepository.GetProfileLeaveReportByKeycloakIdAsync(data.KeycloakUserId, AccessToken, "leave14"); if (profile == null) { return Error(GlobalMessages.DataNotFound, StatusCodes.Status404NotFound); } - var fullName = $"{profile!.Prefix}{profile!.FirstName} {profile!.LastName}"; //var rootOc = _userProfileRepository.GetRootOcId(profile.OcId ?? Guid.Empty, AccessToken); @@ -536,7 +571,10 @@ namespace BMA.EHR.Leave.Service.Controllers // if (list.Count > 0) // approver = list.First().Name; //} - var approveResult = await GetApproverData(data.Approvers); + var Oc = profile.isCommission == false + ? profile.Oc.ToThaiNumber() + : profile.Oc.Replace("สำนักงานคณะกรรมการข้าราชการกรุงเทพมหานคร", "สำนักงาน ก.ก.").ToThaiNumber(); + var approveResult = await GetApproverData(data.Approvers, profile.isCommission); return new { template = "leave14", @@ -567,10 +605,10 @@ namespace BMA.EHR.Leave.Service.Controllers organizationName = profile!.Oc!.ToThaiNumber(), posExOrg = !string.IsNullOrEmpty(profile.PositionLeaveName) && (profile.PositionLeaveName.Contains("อำนวยการ") || profile.PositionLeaveName.Contains("บริหาร")) - ? new[] { (profile!.Oc!.ToThaiNumber()) } + ? new[] { Oc } : !string.IsNullOrEmpty(profile.PosExecutiveName) - ? new[] { (profile.PosExecutiveName.ToThaiNumber()), (profile!.Oc!.ToThaiNumber()) } - : new[] { (profile!.Oc!.ToThaiNumber()) }, + ? new[] { profile.PosExecutiveName.ToThaiNumber(), Oc } + : new[] { Oc }, absentDaySummon = data.AbsentDaySummon.ToThaiNumber(), absentDayLocation = data.AbsentDayLocation.ToThaiNumber(), @@ -594,12 +632,16 @@ namespace BMA.EHR.Leave.Service.Controllers private async Task GetReport06(LeaveRequest data) { - var profile = await _userProfileRepository.GetProfileByKeycloakIdAsync(data.KeycloakUserId, AccessToken); + //var profile = await _userProfileRepository.GetProfileByKeycloakIdAsync(data.KeycloakUserId, AccessToken); + //if (profile == null) + //{ + // return Error(GlobalMessages.DataNotFound, StatusCodes.Status404NotFound); + //} + var profile = await _userProfileRepository.GetProfileLeaveReportByKeycloakIdAsync(data.KeycloakUserId, AccessToken, "leave15"); if (profile == null) { return Error(GlobalMessages.DataNotFound, StatusCodes.Status404NotFound); } - var fullName = $"{profile!.Prefix}{profile!.FirstName} {profile!.LastName}"; //var rootOc = _userProfileRepository.GetRootOcId(profile.OcId ?? Guid.Empty, AccessToken); @@ -610,7 +652,10 @@ namespace BMA.EHR.Leave.Service.Controllers // if (list.Count > 0) // approver = list.First().Name; //} - var approveResult = await GetApproverData(data.Approvers); + var Oc = profile.isCommission == false + ? profile.Oc.ToThaiNumber() + : profile.Oc.Replace("สำนักงานคณะกรรมการข้าราชการกรุงเทพมหานคร", "สำนักงาน ก.ก.").ToThaiNumber(); + var approveResult = await GetApproverData(data.Approvers, profile.isCommission); return new { template = "leave15", @@ -641,10 +686,10 @@ namespace BMA.EHR.Leave.Service.Controllers organizationName = profile!.Oc!.ToThaiNumber(), posExOrg = !string.IsNullOrEmpty(profile.PositionLeaveName) && (profile.PositionLeaveName.Contains("อำนวยการ") || profile.PositionLeaveName.Contains("บริหาร")) - ? new[] { (profile!.Oc!.ToThaiNumber()) } + ? new[] { Oc } : !string.IsNullOrEmpty(profile.PosExecutiveName) - ? new[] { (profile.PosExecutiveName.ToThaiNumber()), (profile!.Oc!.ToThaiNumber()) } - : new[] { (profile!.Oc!.ToThaiNumber()) }, + ? new[] { profile.PosExecutiveName.ToThaiNumber(), Oc } + : new[] { Oc }, leavebirthDate = data.LeaveBirthDate == null ? "" : data.LeaveBirthDate.Value.Date.ToThaiShortDate().ToThaiNumber(), leavegovernmentDate = data.LeaveGovernmentDate == null ? "" : data.LeaveGovernmentDate.Value.Date.ToThaiShortDate().ToThaiNumber(), @@ -684,18 +729,22 @@ namespace BMA.EHR.Leave.Service.Controllers private async Task GetReport07(LeaveRequest data) { - var profile = await _userProfileRepository.GetProfileByKeycloakIdAsync(data.KeycloakUserId, AccessToken); + //var profile = await _userProfileRepository.GetProfileByKeycloakIdAsync(data.KeycloakUserId, AccessToken); + //if (profile == null) + //{ + // return Error(GlobalMessages.DataNotFound, StatusCodes.Status404NotFound); + //} + + //var profileLeave = await _userProfileRepository.GetProfileLeaveByKeycloakIdAsync(data.KeycloakUserId, AccessToken); + //if (profileLeave == null) + //{ + // return Error(GlobalMessages.DataNotFound, StatusCodes.Status404NotFound); + //} + var profile = await _userProfileRepository.GetProfileLeaveReportByKeycloakIdAsync(data.KeycloakUserId, AccessToken, "leave16"); if (profile == null) { return Error(GlobalMessages.DataNotFound, StatusCodes.Status404NotFound); } - - var profileLeave = await _userProfileRepository.GetProfileLeaveByKeycloakIdAsync(data.KeycloakUserId, AccessToken); - if (profileLeave == null) - { - return Error(GlobalMessages.DataNotFound, StatusCodes.Status404NotFound); - } - var fullName = $"{profile!.Prefix}{profile!.FirstName} {profile!.LastName}"; //var rootOc = _userProfileRepository.GetRootOcId(profile.OcId ?? Guid.Empty, AccessToken); @@ -706,7 +755,10 @@ namespace BMA.EHR.Leave.Service.Controllers // if (list.Count > 0) // approver = list.First().Name; //} - var approveResult = await GetApproverData(data.Approvers); + var Oc = profile.isCommission == false + ? profile.Oc.ToThaiNumber() + : profile.Oc.Replace("สำนักงานคณะกรรมการข้าราชการกรุงเทพมหานคร", "สำนักงาน ก.ก.").ToThaiNumber(); + var approveResult = await GetApproverData(data.Approvers, profile.isCommission); return new { template = "leave16", @@ -738,36 +790,36 @@ namespace BMA.EHR.Leave.Service.Controllers organizationName = profile!.Oc!.ToThaiNumber(), posExOrg = !string.IsNullOrEmpty(profile.PositionLeaveName) && (profile.PositionLeaveName.Contains("อำนวยการ") || profile.PositionLeaveName.Contains("บริหาร")) - ? new[] { (profile!.Oc!.ToThaiNumber()) } + ? new[] { Oc } : !string.IsNullOrEmpty(profile.PosExecutiveName) - ? new[] { (profile.PosExecutiveName.ToThaiNumber()), (profile!.Oc!.ToThaiNumber()) } - : new[] { (profile!.Oc!.ToThaiNumber()) }, + ? new[] { profile.PosExecutiveName.ToThaiNumber(), Oc } + : new[] { Oc }, leaveDateStart = data.LeaveStartDate.Date.ToThaiShortDate().ToThaiNumber(), leaveDateEnd = data.LeaveEndDate.Date.ToThaiShortDate().ToThaiNumber(), profileType = profile.ProfileType, - birthDate = profileLeave.BirthDate.ToThaiShortDate().ToThaiNumber(), - retireDate = profileLeave.RetireDate.ToThaiShortDate().ToThaiNumber(), - govAge = profileLeave.GovAge.ToThaiNumber(), - age = profileLeave.Age.ToThaiNumber(), - dateAppoint = profileLeave.DateAppoint.ToThaiShortDate().ToThaiNumber(), - dateCurrent = profileLeave.DateCurrent.ToThaiShortDate().ToThaiNumber(), - amount = ((double)profileLeave.Amount).ToNumericText().ToThaiNumber(), - telephoneNumber = profileLeave.TelephoneNumber == null ? "" : profileLeave.TelephoneNumber.ToThaiNumber(), + birthDate = profile.BirthDate.ToThaiShortDate().ToThaiNumber(), + retireDate = profile.RetireDate.ToThaiShortDate().ToThaiNumber(), + govAge = profile.GovAge.ToThaiNumber(), + age = profile.Age.ToThaiNumber(), + dateAppoint = profile.DateAppoint.ToThaiShortDate().ToThaiNumber(), + dateCurrent = profile.DateCurrent.ToThaiShortDate().ToThaiNumber(), + amount = ((double)profile.Amount).ToNumericText().ToThaiNumber(), + telephoneNumber = profile.TelephoneNumber == null ? "" : profile.TelephoneNumber.ToThaiNumber(), - posLevel = profileLeave.PosLevel.ToThaiNumber(), - posType = profileLeave.PosType.ToThaiNumber(), - currentAddress = profileLeave.CurrentAddress.ToThaiNumber(), - oc = profileLeave.Oc.ToThaiNumber(), - root = profileLeave.Root.ToThaiNumber(), - child1 = profileLeave.Child1 == null ? "" : profileLeave.Child1!.ToThaiNumber(), - child2 = profileLeave.Child2 == null ? "" : profileLeave.Child2!.ToThaiNumber(), - child3 = profileLeave.Child3 == null ? "" : profileLeave.Child3!.ToThaiNumber(), - child4 = profileLeave.Child4 == null ? "" : profileLeave.Child4!.ToThaiNumber(), + posLevel = profile.PosLevel.ToThaiNumber(), + posType = profile.PosType.ToThaiNumber(), + currentAddress = profile.CurrentAddress.ToThaiNumber(), + oc = profile.Oc.ToThaiNumber(), + root = profile.Root.ToThaiNumber(), + child1 = profile.Child1 == null ? "" : profile.Child1!.ToThaiNumber(), + child2 = profile.Child2 == null ? "" : profile.Child2!.ToThaiNumber(), + child3 = profile.Child3 == null ? "" : profile.Child3!.ToThaiNumber(), + child4 = profile.Child4 == null ? "" : profile.Child4!.ToThaiNumber(), - positions = profileLeave.Positions.Select(x => new + positions = profile.Positions.Select(x => new { positionName = x.PositionName == null ? "" : x.PositionName.ToThaiNumber(), dateStart = x.DateStart.ToThaiShortDate().ToThaiNumber(), @@ -780,7 +832,7 @@ namespace BMA.EHR.Leave.Service.Controllers orgChild3 = x.OrgChild3 == null ? "" : x.OrgChild3.ToThaiNumber(), orgChild4 = x.OrgChild4 == null ? "" : x.OrgChild4.ToThaiNumber(), }).ToList(), - educations = profileLeave.Educations.Select(x => new + educations = profile.Educations.Select(x => new { educationLevel = x.EducationLevel == null ? "" : x.EducationLevel.ToThaiNumber(), institute = x.Institute == null ? "" : x.Institute.ToThaiNumber(), @@ -802,12 +854,16 @@ namespace BMA.EHR.Leave.Service.Controllers private async Task GetReport08(LeaveRequest data) { - var profile = await _userProfileRepository.GetProfileByKeycloakIdAsync(data.KeycloakUserId, AccessToken); + //var profile = await _userProfileRepository.GetProfileByKeycloakIdAsync(data.KeycloakUserId, AccessToken); + //if (profile == null) + //{ + // return Error(GlobalMessages.DataNotFound, StatusCodes.Status404NotFound); + //} + var profile = await _userProfileRepository.GetProfileLeaveReportByKeycloakIdAsync(data.KeycloakUserId, AccessToken, "leave17"); if (profile == null) { return Error(GlobalMessages.DataNotFound, StatusCodes.Status404NotFound); } - var fullName = $"{profile!.Prefix}{profile!.FirstName} {profile!.LastName}"; //var rootOc = _userProfileRepository.GetRootOcId(profile.OcId ?? Guid.Empty, AccessToken); @@ -818,7 +874,10 @@ namespace BMA.EHR.Leave.Service.Controllers // if (list.Count > 0) // approver = list.First().Name; //} - var approveResult = await GetApproverData(data.Approvers); + var Oc = profile.isCommission == false + ? profile.Oc.ToThaiNumber() + : profile.Oc.Replace("สำนักงานคณะกรรมการข้าราชการกรุงเทพมหานคร", "สำนักงาน ก.ก.").ToThaiNumber(); + var approveResult = await GetApproverData(data.Approvers, profile.isCommission); return new { template = "leave17", @@ -849,10 +908,10 @@ namespace BMA.EHR.Leave.Service.Controllers organizationName = profile!.Oc!.ToThaiNumber(), posExOrg = !string.IsNullOrEmpty(profile.PositionLeaveName) && (profile.PositionLeaveName.Contains("อำนวยการ") || profile.PositionLeaveName.Contains("บริหาร")) - ? new[] { (profile!.Oc!.ToThaiNumber()) } + ? new[] { Oc } : !string.IsNullOrEmpty(profile.PosExecutiveName) - ? new[] { (profile.PosExecutiveName.ToThaiNumber()), (profile!.Oc!.ToThaiNumber()) } - : new[] { (profile!.Oc!.ToThaiNumber()) }, + ? new[] { profile.PosExecutiveName.ToThaiNumber(), Oc } + : new[] { Oc }, leaveSalary = data.LeaveSalary == null ? "" : data.LeaveSalary.Value.ToNumericText().ToThaiNumber(), leaveSalaryText = data.LeaveSalaryText.ToThaiNumber(), @@ -884,14 +943,19 @@ namespace BMA.EHR.Leave.Service.Controllers private async Task GetReport09(LeaveRequest data) { - var profile = await _userProfileRepository.GetProfileByKeycloakIdAsync(data.KeycloakUserId, AccessToken); - if (profile == null) - { - return Error(GlobalMessages.DataNotFound, StatusCodes.Status404NotFound); - } + //var profile = await _userProfileRepository.GetProfileByKeycloakIdAsync(data.KeycloakUserId, AccessToken); + //if (profile == null) + //{ + // return Error(GlobalMessages.DataNotFound, StatusCodes.Status404NotFound); + //} - var profileLeave = await _userProfileRepository.GetProfileLeaveByKeycloakIdAsync(data.KeycloakUserId, AccessToken); - if (profileLeave == null) + //var profileLeave = await _userProfileRepository.GetProfileLeaveByKeycloakIdAsync(data.KeycloakUserId, AccessToken); + //if (profileLeave == null) + //{ + // return Error(GlobalMessages.DataNotFound, StatusCodes.Status404NotFound); + //} + var profile = await _userProfileRepository.GetProfileLeaveReportByKeycloakIdAsync(data.KeycloakUserId, AccessToken, "leave18"); + if (profile == null) { return Error(GlobalMessages.DataNotFound, StatusCodes.Status404NotFound); } @@ -906,7 +970,10 @@ namespace BMA.EHR.Leave.Service.Controllers // if (list.Count > 0) // approver = list.First().Name; //} - var approveResult = await GetApproverData(data.Approvers); + var Oc = profile.isCommission == false + ? profile.Oc.ToThaiNumber() + : profile.Oc.Replace("สำนักงานคณะกรรมการข้าราชการกรุงเทพมหานคร", "สำนักงาน ก.ก.").ToThaiNumber(); + var approveResult = await GetApproverData(data.Approvers, profile.isCommission); return new { template = "leave18", @@ -937,36 +1004,36 @@ namespace BMA.EHR.Leave.Service.Controllers organizationName = profile!.Oc!.ToThaiNumber(), posExOrg = !string.IsNullOrEmpty(profile.PositionLeaveName) && (profile.PositionLeaveName.Contains("อำนวยการ") || profile.PositionLeaveName.Contains("บริหาร")) - ? new[] { (profile!.Oc!.ToThaiNumber()) } + ? new[] { Oc } : !string.IsNullOrEmpty(profile.PosExecutiveName) - ? new[] { (profile.PosExecutiveName.ToThaiNumber()), (profile!.Oc!.ToThaiNumber()) } - : new[] { (profile!.Oc!.ToThaiNumber()) }, + ? new[] { profile.PosExecutiveName.ToThaiNumber(), Oc } + : new[] { Oc }, leaveDateStart = data.LeaveStartDate.Date.ToThaiShortDate().ToThaiNumber(), leaveDateEnd = data.LeaveEndDate.Date.ToThaiShortDate().ToThaiNumber(), profileType = profile.ProfileType, - birthDate = profileLeave.BirthDate.ToThaiShortDate().ToThaiNumber(), - retireDate = profileLeave.RetireDate.ToThaiShortDate().ToThaiNumber(), - govAge = profileLeave.GovAge.ToThaiNumber(), - age = profileLeave.Age.ToThaiNumber(), - dateAppoint = profileLeave.DateAppoint.ToThaiShortDate().ToThaiNumber(), - dateCurrent = profileLeave.DateCurrent.ToThaiShortDate().ToThaiNumber(), - amount = ((double)profileLeave.Amount).ToNumericText().ToThaiNumber(), - telephoneNumber = profileLeave.TelephoneNumber == null ? "" : profileLeave.TelephoneNumber.ToThaiNumber(), + birthDate = profile.BirthDate.ToThaiShortDate().ToThaiNumber(), + retireDate = profile.RetireDate.ToThaiShortDate().ToThaiNumber(), + govAge = profile.GovAge.ToThaiNumber(), + age = profile.Age.ToThaiNumber(), + dateAppoint = profile.DateAppoint.ToThaiShortDate().ToThaiNumber(), + dateCurrent = profile.DateCurrent.ToThaiShortDate().ToThaiNumber(), + amount = ((double)profile.Amount).ToNumericText().ToThaiNumber(), + telephoneNumber = profile.TelephoneNumber == null ? "" : profile.TelephoneNumber.ToThaiNumber(), - posLevel = profileLeave.PosLevel.ToThaiNumber(), - posType = profileLeave.PosType.ToThaiNumber(), - currentAddress = profileLeave.CurrentAddress.ToThaiNumber(), - oc = profileLeave.Oc.ToThaiNumber(), - root = profileLeave.Root.ToThaiNumber(), - child1 = profileLeave.Child1 == null ? "" : profileLeave.Child1!.ToThaiNumber(), - child2 = profileLeave.Child2 == null ? "" : profileLeave.Child2!.ToThaiNumber(), - child3 = profileLeave.Child3 == null ? "" : profileLeave.Child3!.ToThaiNumber(), - child4 = profileLeave.Child4 == null ? "" : profileLeave.Child4!.ToThaiNumber(), + posLevel = profile.PosLevel.ToThaiNumber(), + posType = profile.PosType.ToThaiNumber(), + currentAddress = profile.CurrentAddress.ToThaiNumber(), + oc = profile.Oc.ToThaiNumber(), + root = profile.Root.ToThaiNumber(), + child1 = profile.Child1 == null ? "" : profile.Child1!.ToThaiNumber(), + child2 = profile.Child2 == null ? "" : profile.Child2!.ToThaiNumber(), + child3 = profile.Child3 == null ? "" : profile.Child3!.ToThaiNumber(), + child4 = profile.Child4 == null ? "" : profile.Child4!.ToThaiNumber(), - positions = profileLeave.Positions.Select(x => new + positions = profile.Positions.Select(x => new { positionName = x.PositionName == null ? "" : x.PositionName.ToThaiNumber(), dateStart = x.DateStart.ToThaiShortDate().ToThaiNumber(), @@ -980,7 +1047,7 @@ namespace BMA.EHR.Leave.Service.Controllers orgChild4 = x.OrgChild4 == null ? "" : x.OrgChild4.ToThaiNumber(), }).ToList(), - educations = profileLeave.Educations.Select(x => new + educations = profile.Educations.Select(x => new { educationLevel = x.EducationLevel == null ? "" : x.EducationLevel.ToThaiNumber(), institute = x.Institute == null ? "" : x.Institute.ToThaiNumber(), @@ -1109,12 +1176,16 @@ namespace BMA.EHR.Leave.Service.Controllers return Error(GlobalMessages.DataNotFound, StatusCodes.Status404NotFound); } - var profile = await _userProfileRepository.GetProfileByKeycloakIdAsync(data.KeycloakUserId, AccessToken); + //var profile = await _userProfileRepository.GetProfileByKeycloakIdAsync(data.KeycloakUserId, AccessToken); + //if (profile == null) + //{ + // return Error(GlobalMessages.DataNotFound, StatusCodes.Status404NotFound); + //} + var profile = await _userProfileRepository.GetProfileLeaveReportByKeycloakIdAsync(data.KeycloakUserId, AccessToken, "leave9"); if (profile == null) { return Error(GlobalMessages.DataNotFound, StatusCodes.Status404NotFound); } - var fullName = $"{profile!.Prefix}{profile!.FirstName} {profile!.LastName}"; //var rootOc = _userProfileRepository.GetRootOcId(profile.OcId ?? Guid.Empty, AccessToken); @@ -1125,7 +1196,10 @@ namespace BMA.EHR.Leave.Service.Controllers // if (list.Count > 0) // approver = list.First().Name; //} - var approveResult = await GetApproverData(data.Approvers); + var Oc = profile.isCommission == false + ? profile.Oc.ToThaiNumber() + : profile.Oc.Replace("สำนักงานคณะกรรมการข้าราชการกรุงเทพมหานคร", "สำนักงาน ก.ก.").ToThaiNumber(); + var approveResult = await GetApproverData(data.Approvers, profile.isCommission); var result = new { template = "แบบใบขอยกเลิกวันลา", @@ -1154,10 +1228,10 @@ namespace BMA.EHR.Leave.Service.Controllers organizationName = profile!.Oc!.ToThaiNumber(), posExOrg = !string.IsNullOrEmpty(profile.PositionLeaveName) && (profile.PositionLeaveName.Contains("อำนวยการ") || profile.PositionLeaveName.Contains("บริหาร")) - ? new[] { (profile!.Oc!.ToThaiNumber()) } + ? new[] { Oc } : !string.IsNullOrEmpty(profile.PosExecutiveName) - ? new[] { (profile.PosExecutiveName.ToThaiNumber()), (profile!.Oc!.ToThaiNumber()) } - : new[] { (profile!.Oc!.ToThaiNumber()) }, + ? new[] { profile.PosExecutiveName.ToThaiNumber(), Oc } + : new[] { Oc }, leaveDateStart = data.LeaveStartDate.Date.ToThaiShortDate().ToThaiNumber(), leaveDateEnd = data.LeaveEndDate.Date.ToThaiShortDate().ToThaiNumber(), dear = data.CommanderPosition == null ? data.Dear : data.CommanderPosition.ToThaiNumber(), @@ -2811,7 +2885,7 @@ namespace BMA.EHR.Leave.Service.Controllers } #endregion - private async Task GetApproverData(List list) + private async Task GetApproverData(List list, bool? isCommission = false) { var _default = new { @@ -2842,10 +2916,10 @@ namespace BMA.EHR.Leave.Service.Controllers : $"{x.PositionName}{x.PositionLevelName}").ToThaiNumber(), posExOrg = !string.IsNullOrEmpty(x.PositionLevelName) && (x.PositionLevelName.Contains("อำนวยการ") || x.PositionLevelName.Contains("บริหาร")) - ? new[] { (x!.OrganizationName!.ToThaiNumber()) } + ? new[] { (isCommission == false ? x!.OrganizationName : x!.OrganizationName.Replace("สำนักงานคณะกรรมการข้าราชการกรุงเทพมหานคร", "สำนักงาน ก.ก.")).ToThaiNumber() } : !string.IsNullOrEmpty(x.PosExecutiveName) - ? new[] { (x.PosExecutiveName.ToThaiNumber()), (x!.OrganizationName!.ToThaiNumber()) } - : new[] { (x!.OrganizationName!.ToThaiNumber()) }, + ? new[] { (x.PosExecutiveName.ToThaiNumber()), (isCommission == false ? x!.OrganizationName : x!.OrganizationName.Replace("สำนักงานคณะกรรมการข้าราชการกรุงเทพมหานคร", "สำนักงาน ก.ก.")).ToThaiNumber() } + : new[] { (isCommission == false ? x!.OrganizationName : x!.OrganizationName.Replace("สำนักงานคณะกรรมการข้าราชการกรุงเทพมหานคร", "สำนักงาน ก.ก.")).ToThaiNumber() }, positionSign = !string.IsNullOrEmpty(x.PositionSign) ? x.PositionSign.Replace("\r", "").Replace("\n", " ") : "............................................", From 982dfc33d119e46b4ff15177f90464196982bd2a Mon Sep 17 00:00:00 2001 From: Suphonchai Phoonsawat Date: Mon, 26 Jan 2026 22:35:24 +0700 Subject: [PATCH 068/183] Refactor LeaveReportController and LeaveRequestController to calculate leave days using repository methods #2246 #2247 --- BMA.EHR.Leave/Controllers/LeaveReportController.cs | 5 ++++- BMA.EHR.Leave/Controllers/LeaveRequestController.cs | 12 +++++++++--- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/BMA.EHR.Leave/Controllers/LeaveReportController.cs b/BMA.EHR.Leave/Controllers/LeaveReportController.cs index f8c22956..eb59f663 100644 --- a/BMA.EHR.Leave/Controllers/LeaveReportController.cs +++ b/BMA.EHR.Leave/Controllers/LeaveReportController.cs @@ -332,7 +332,10 @@ namespace BMA.EHR.Leave.Service.Controllers //var sumLeave = await _leaveRequestRepository.GetSumApproveLeaveTotalByTypeAndRangeForUser(data.KeycloakUserId, data.Type.Id, startFiscalYear, endFiscalYear); - var sumLeave = leaveData == null ? 0.0 : leaveData.LeaveDaysUsed; + + var sumLeave = await _leaveRequestRepository.GetSumApproveLeaveTotalByTypeAndRangeForUser(data.KeycloakUserId, data.Type.Id, startFiscalYear, endFiscalYear); + + //var sumLeave = leaveData == null ? 0.0 : leaveData.LeaveDaysUsed; var leaveLimit = leaveData == null ? 0.0 : leaveData.LeaveDays; var extendLeave = leaveLimit - 10; var Oc = profile.isCommission == false diff --git a/BMA.EHR.Leave/Controllers/LeaveRequestController.cs b/BMA.EHR.Leave/Controllers/LeaveRequestController.cs index dc3afc62..d8da2c15 100644 --- a/BMA.EHR.Leave/Controllers/LeaveRequestController.cs +++ b/BMA.EHR.Leave/Controllers/LeaveRequestController.cs @@ -2388,8 +2388,10 @@ namespace BMA.EHR.Leave.Service.Controllers } } } - var thisYear = DateTime.Now.Year; - var toDay = DateTime.Now.Date; + var thisYear = rawData.LeaveStartDate.Year; + var toDay = rawData.LeaveStartDate.Date; + // 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; @@ -2441,7 +2443,11 @@ namespace BMA.EHR.Leave.Service.Controllers orgName += $" {rawData.Root}"; var leaveData = await _leaveBeginningRepository.GetByYearAndTypeIdForUser2Async(thisYear, rawData.Type.Id, rawData.KeycloakUserId); - var leaveSummary = leaveData == null ? 0.0 : leaveData.LeaveDaysUsed; + + var startFiscalYear = new DateTime(rawData.LeaveStartDate.Year - 1, 10, 1); + var endFiscalYear = rawData.LeaveStartDate.Date.AddDays(-1); // นับจากวันที่ยื่นลา + var leaveSummary = await _leaveRequestRepository.GetSumApproveLeaveTotalByTypeAndRangeForUser(rawData.KeycloakUserId, rawData.Type.Id, startFiscalYear, endFiscalYear); + //var leaveSummary = leaveData == null ? 0.0 : leaveData.LeaveDaysUsed; var extendLeave = 0.0; var leaveLimit = (double)rawData.Type.Limit; From 4f28b4e9e07c7ecdaac89035bff49c7f9b9351ab Mon Sep 17 00:00:00 2001 From: Suphonchai Phoonsawat Date: Mon, 26 Jan 2026 22:58:37 +0700 Subject: [PATCH 069/183] Add LeaveTotal property to LeaveRequest DTOs for better leave tracking #2245 --- BMA.EHR.Leave/Controllers/LeaveRequestController.cs | 4 +++- .../DTOs/LeaveRequest/GetLeaveRequestCalendarResultDto.cs | 2 ++ .../DTOs/LeaveRequest/GetLeaveRequestForAdminResultDto.cs | 2 ++ 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/BMA.EHR.Leave/Controllers/LeaveRequestController.cs b/BMA.EHR.Leave/Controllers/LeaveRequestController.cs index d8da2c15..fa8cbed3 100644 --- a/BMA.EHR.Leave/Controllers/LeaveRequestController.cs +++ b/BMA.EHR.Leave/Controllers/LeaveRequestController.cs @@ -1326,7 +1326,8 @@ namespace BMA.EHR.Leave.Service.Controllers FullName = $"{d.Prefix}{d.FirstName} {d.LastName}", LeaveEndDate = d.LeaveEndDate, LeaveStartDate = d.LeaveStartDate, - KeycloakId = d.KeycloakUserId + KeycloakId = d.KeycloakUserId, + LeaveTotal = d.LeaveTotal }) .ToList(); @@ -1750,6 +1751,7 @@ namespace BMA.EHR.Leave.Service.Controllers LeaveRange = item.LeaveRange ?? "ALL", LeaveRangeEnd = item.LeaveRangeEnd ?? "ALL", HajjDayStatus = item.HajjDayStatus, + LeaveTotal = item.LeaveTotal }; result.Add(res); diff --git a/BMA.EHR.Leave/DTOs/LeaveRequest/GetLeaveRequestCalendarResultDto.cs b/BMA.EHR.Leave/DTOs/LeaveRequest/GetLeaveRequestCalendarResultDto.cs index ef895786..be03ec4c 100644 --- a/BMA.EHR.Leave/DTOs/LeaveRequest/GetLeaveRequestCalendarResultDto.cs +++ b/BMA.EHR.Leave/DTOs/LeaveRequest/GetLeaveRequestCalendarResultDto.cs @@ -19,5 +19,7 @@ public DateTime LeaveEndDate { get; set; } public Guid KeycloakId { get; set; } + + public double LeaveTotal { get; set; } } } diff --git a/BMA.EHR.Leave/DTOs/LeaveRequest/GetLeaveRequestForAdminResultDto.cs b/BMA.EHR.Leave/DTOs/LeaveRequest/GetLeaveRequestForAdminResultDto.cs index 42a508ff..d723bd66 100644 --- a/BMA.EHR.Leave/DTOs/LeaveRequest/GetLeaveRequestForAdminResultDto.cs +++ b/BMA.EHR.Leave/DTOs/LeaveRequest/GetLeaveRequestForAdminResultDto.cs @@ -37,5 +37,7 @@ public bool? HajjDayStatus { get; set; } public string? ProfileType { get; set; } + + public double LeaveTotal { get; set; } } } From 54c8152752ef9998afa5703c0192620c5cb0afda Mon Sep 17 00:00:00 2001 From: Suphonchai Phoonsawat Date: Tue, 27 Jan 2026 16:55:23 +0700 Subject: [PATCH 070/183] Refactor LeaveRequestController to improve filtering logic and update appsettings.json to modify connection strings --- .../Controllers/LeaveRequestController.cs | 23 +++++++++++++------ BMA.EHR.Leave/appsettings.json | 6 ++--- 2 files changed, 19 insertions(+), 10 deletions(-) diff --git a/BMA.EHR.Leave/Controllers/LeaveRequestController.cs b/BMA.EHR.Leave/Controllers/LeaveRequestController.cs index fa8cbed3..49242df2 100644 --- a/BMA.EHR.Leave/Controllers/LeaveRequestController.cs +++ b/BMA.EHR.Leave/Controllers/LeaveRequestController.cs @@ -1906,11 +1906,20 @@ namespace BMA.EHR.Leave.Service.Controllers var rawData = await _leaveRequestRepository.GetCancelLeaveRequestForAdminAsync(req.Year, req.Type, req.Status, role, nodeId, profileAdmin?.Node); + var recCount = rawData.Count; + + if (req.Keyword != "") + rawData = rawData.Where(x => ($"{x.Prefix}{x.FirstName} {x.LastName}").Contains(req.Keyword)).ToList(); + if (!string.IsNullOrEmpty(req.ProfileType) && req.ProfileType.ToUpper() != "ALL") + rawData = rawData.Where(x => x.ProfileType.ToUpper().Contains(req.ProfileType.ToUpper())).ToList(); + + rawData = rawData.Skip((req.Page - 1) * req.PageSize).Take(req.PageSize).ToList(); + var result = new List(); foreach (var item in rawData) { - var profile = await _userProfileRepository.GetProfileByKeycloakIdAsync(item.KeycloakUserId, AccessToken); + var profile = await _userProfileRepository.GetProfileByKeycloakIdNewAsync(item.KeycloakUserId, AccessToken); var res = new GetLeaveCancelRequestResultDto { Id = item.Id, @@ -1925,13 +1934,13 @@ namespace BMA.EHR.Leave.Service.Controllers result.Add(res); } - if (req.Keyword != "") - result = result.Where(x => x.FullName.Contains(req.Keyword)).ToList(); - if (!string.IsNullOrEmpty(req.ProfileType) && req.ProfileType.ToUpper() != "ALL") - result = result.Where(x => x.ProfileType.ToUpper().Contains(req.ProfileType.ToUpper())).ToList(); - var pageResult = result.Skip((req.Page - 1) * req.PageSize).Take(req.PageSize).ToList(); + // if (req.Keyword != "") + // result = result.Where(x => x.FullName.Contains(req.Keyword)).ToList(); + // if (!string.IsNullOrEmpty(req.ProfileType) && req.ProfileType.ToUpper() != "ALL") + // result = result.Where(x => x.ProfileType.ToUpper().Contains(req.ProfileType.ToUpper())).ToList(); + // var pageResult = result.Skip((req.Page - 1) * req.PageSize).Take(req.PageSize).ToList(); - return Success(new { data = pageResult, total = result.Count }); + return Success(new { data = result, total = recCount }); } diff --git a/BMA.EHR.Leave/appsettings.json b/BMA.EHR.Leave/appsettings.json index f8562d6e..40b464cf 100644 --- a/BMA.EHR.Leave/appsettings.json +++ b/BMA.EHR.Leave/appsettings.json @@ -23,9 +23,9 @@ "ExamConnection": "server=192.168.1.63;user=root;password=12345678;port=3306;database=hrms_exam;Convert Zero Datetime=True;Allow User Variables=true;Pooling=True;", "LeaveConnection": "server=192.168.1.63;user=root;password=12345678;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;", - //"LeaveConnection": "server=172.27.17.68;user=user;password=cDldaqkwESWvuZ37Gr0n;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;", + // "LeaveConnection": "server=172.27.17.68;user=user;password=cDldaqkwESWvuZ37Gr0n;port=3306;database=hrms_leave;Convert Zero Datetime=True;Allow User Variables=true;Pooling=True;" }, "Jwt": { //"Key": "HP-FnQMUj9msHMSD3T9HtdEnphAKoCJLEl85CIqROFI", From e8ffb82eadb4a68642ce4ab4748fb143346f1996 Mon Sep 17 00:00:00 2001 From: Suphonchai Phoonsawat Date: Tue, 27 Jan 2026 17:00:22 +0700 Subject: [PATCH 071/183] Comment out profile retrieval and error handling in LeaveRequestController #2250 --- BMA.EHR.Leave/Controllers/LeaveRequestController.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/BMA.EHR.Leave/Controllers/LeaveRequestController.cs b/BMA.EHR.Leave/Controllers/LeaveRequestController.cs index 49242df2..67bb53b4 100644 --- a/BMA.EHR.Leave/Controllers/LeaveRequestController.cs +++ b/BMA.EHR.Leave/Controllers/LeaveRequestController.cs @@ -1967,12 +1967,12 @@ namespace BMA.EHR.Leave.Service.Controllers return Error(GlobalMessages.DataNotFound, StatusCodes.Status404NotFound); } - var profile = await _userProfileRepository.GetProfileByKeycloakIdAsync(rawData.KeycloakUserId, AccessToken); + // var profile = await _userProfileRepository.GetProfileByKeycloakIdAsync(rawData.KeycloakUserId, AccessToken); - if (profile == null) - { - return Error(GlobalMessages.DataNotFound, StatusCodes.Status404NotFound); - } + // if (profile == null) + // { + // return Error(GlobalMessages.DataNotFound, StatusCodes.Status404NotFound); + // } var result = new GetCancelLeaveRequestByIdDto { From cbc2a1a88dbfdc3b37a83a48bcdf5b08c110fc1d Mon Sep 17 00:00:00 2001 From: Suphonchai Phoonsawat Date: Tue, 27 Jan 2026 17:05:07 +0700 Subject: [PATCH 072/183] Comment out profile retrieval in LeaveRequestController to prevent unnecessary calls #2250 --- BMA.EHR.Leave/Controllers/LeaveRequestController.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BMA.EHR.Leave/Controllers/LeaveRequestController.cs b/BMA.EHR.Leave/Controllers/LeaveRequestController.cs index 67bb53b4..5d6c37c4 100644 --- a/BMA.EHR.Leave/Controllers/LeaveRequestController.cs +++ b/BMA.EHR.Leave/Controllers/LeaveRequestController.cs @@ -1919,7 +1919,7 @@ namespace BMA.EHR.Leave.Service.Controllers foreach (var item in rawData) { - var profile = await _userProfileRepository.GetProfileByKeycloakIdNewAsync(item.KeycloakUserId, AccessToken); + //var profile = await _userProfileRepository.GetProfileByKeycloakIdNewAsync(item.KeycloakUserId, AccessToken); var res = new GetLeaveCancelRequestResultDto { Id = item.Id, From 839c35784266ffd135bc9a212dac76b6bbc02ec8 Mon Sep 17 00:00:00 2001 From: Suphonchai Phoonsawat Date: Wed, 28 Jan 2026 11:47:10 +0700 Subject: [PATCH 073/183] Update LeaveController to use KeycloakUserId for profile retrieval #2253 --- BMA.EHR.Leave/Controllers/LeaveController.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/BMA.EHR.Leave/Controllers/LeaveController.cs b/BMA.EHR.Leave/Controllers/LeaveController.cs index 4787de39..809a28e3 100644 --- a/BMA.EHR.Leave/Controllers/LeaveController.cs +++ b/BMA.EHR.Leave/Controllers/LeaveController.cs @@ -3637,8 +3637,9 @@ namespace BMA.EHR.Leave.Service.Controllers  //if (data.CheckInStatus == "NORMAL" || data.CheckOutStatus == "NORMAL") - var userId = UserId == null ? Guid.Empty : Guid.Parse(UserId); - var profile = await _userProfileRepository.GetProfileByKeycloakIdNewAsync(userId, AccessToken); + //var userId = UserId == null ? Guid.Empty : Guid.Parse(UserId); + // แก้เป็นมาใช้งาน KeycloakUserId แทน + var profile = await _userProfileRepository.GetProfileByKeycloakIdNewAsync(data.KeycloakUserId, AccessToken); var defaultRound = await _dutyTimeRepository.GetDefaultAsync(); if (defaultRound == null) { From 02487d91ff3073a2293db6a7ef7a2fc146ca84c5 Mon Sep 17 00:00:00 2001 From: harid Date: Wed, 28 Jan 2026 13:27:25 +0700 Subject: [PATCH 074/183] =?UTF-8?q?=E0=B9=80=E0=B8=9B=E0=B8=A5=E0=B8=B5?= =?UTF-8?q?=E0=B9=88=E0=B8=A2=E0=B8=99=E0=B9=80=E0=B8=AA=E0=B9=89=E0=B8=99?= =?UTF-8?q?=20api=20=E0=B8=AA=E0=B8=B3=E0=B8=AB=E0=B8=A3=E0=B8=B1=E0=B8=9A?= =?UTF-8?q?=E0=B9=80=E0=B8=81=E0=B9=87=E0=B8=9A=20logs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Middlewares/CombinedErrorHandlerAndLoggingMiddleware.cs | 6 ++++-- BMA.EHR.Leave/Controllers/LeaveReportController.cs | 4 ++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/BMA.EHR.Domain/Middlewares/CombinedErrorHandlerAndLoggingMiddleware.cs b/BMA.EHR.Domain/Middlewares/CombinedErrorHandlerAndLoggingMiddleware.cs index 57ff69dd..15c88592 100644 --- a/BMA.EHR.Domain/Middlewares/CombinedErrorHandlerAndLoggingMiddleware.cs +++ b/BMA.EHR.Domain/Middlewares/CombinedErrorHandlerAndLoggingMiddleware.cs @@ -483,7 +483,8 @@ namespace BMA.EHR.Domain.Middlewares { logType = logType, ip = (string)contextData.RemoteIpAddress, - rootId = pf?.RootId, + //rootId = pf?.RootId, + rootId = pf?.RootDnaId, systemName = SystemName, startTimeStamp = startTime.ToString("o"), endTimeStamp = endTime.ToString("o"), @@ -684,7 +685,8 @@ namespace BMA.EHR.Domain.Middlewares { try { - var apiPath = $"{_configuration["API"]}/org/dotnet/by-keycloak/{keycloakId}"; + //var apiPath = $"{_configuration["API"]}/org/dotnet/by-keycloak/{keycloakId}"; + var apiPath = $"{_configuration["API"]}/org/dotnet/user-logs/{keycloakId}"; var apiKey = _configuration["API_KEY"]; var apiResult = await GetExternalAPIAsync(apiPath, accessToken ?? "", apiKey); diff --git a/BMA.EHR.Leave/Controllers/LeaveReportController.cs b/BMA.EHR.Leave/Controllers/LeaveReportController.cs index eb59f663..e71b4780 100644 --- a/BMA.EHR.Leave/Controllers/LeaveReportController.cs +++ b/BMA.EHR.Leave/Controllers/LeaveReportController.cs @@ -1248,7 +1248,7 @@ namespace BMA.EHR.Leave.Service.Controllers Type3 = "☐", approve = approveResult, approverComment = !string.IsNullOrEmpty(data.LeaveDirectorComment) - ? data.LeaveDirectorComment.Replace("\r", "").Replace("\n", "").Trim() + ? data.LeaveDirectorComment.Replace("\r", "").Replace("\n", "").Trim().ToThaiNumber() : "......................", approverUpdatedAt = data.LastUpdatedAt.HasValue ? data.LastUpdatedAt.Value.ToThaiShortDate().ToThaiNumber() @@ -2924,7 +2924,7 @@ namespace BMA.EHR.Leave.Service.Controllers ? new[] { (x.PosExecutiveName.ToThaiNumber()), (isCommission == false ? x!.OrganizationName : x!.OrganizationName.Replace("สำนักงานคณะกรรมการข้าราชการกรุงเทพมหานคร", "สำนักงาน ก.ก.")).ToThaiNumber() } : new[] { (isCommission == false ? x!.OrganizationName : x!.OrganizationName.Replace("สำนักงานคณะกรรมการข้าราชการกรุงเทพมหานคร", "สำนักงาน ก.ก.")).ToThaiNumber() }, positionSign = !string.IsNullOrEmpty(x.PositionSign) - ? x.PositionSign.Replace("\r", "").Replace("\n", " ") + ? x.PositionSign.Replace("\r", "").Replace("\n", " ").ToThaiNumber() : "............................................", updatedAt = x.LastUpdatedAt.HasValue ? x.LastUpdatedAt.Value.Date.ToThaiShortDate().ToThaiNumber() From b10ff45d07c0d2c2405edf5e340ac9bf1649c483 Mon Sep 17 00:00:00 2001 From: harid Date: Wed, 28 Jan 2026 15:06:55 +0700 Subject: [PATCH 075/183] =?UTF-8?q?=E0=B9=80=E0=B8=9B=E0=B8=A5=E0=B8=B5?= =?UTF-8?q?=E0=B9=88=E0=B8=A2=E0=B8=99=E0=B9=80=E0=B8=AA=E0=B9=89=E0=B8=99?= =?UTF-8?q?=20call=20api=20=E0=B8=AA=E0=B8=B3=E0=B8=AB=E0=B8=A3=E0=B8=B1?= =?UTF-8?q?=E0=B8=9A=E0=B9=80=E0=B8=8A=E0=B9=87=E0=B8=84=20profileId?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Repositories/MessageQueue/InboxRepository.cs | 3 ++- .../Repositories/MessageQueue/NotificationRepository.cs | 6 ++++-- .../Controllers/DisciplineComplaint_AppealController.cs | 6 ++++-- .../Controllers/PlacementOfficerController.cs | 3 ++- .../Controllers/PlacementTransferController.cs | 3 ++- .../Controllers/RetirementResignController.cs | 6 ++++-- 6 files changed, 18 insertions(+), 9 deletions(-) diff --git a/BMA.EHR.Application/Repositories/MessageQueue/InboxRepository.cs b/BMA.EHR.Application/Repositories/MessageQueue/InboxRepository.cs index f0d0bc0c..46019cfc 100644 --- a/BMA.EHR.Application/Repositories/MessageQueue/InboxRepository.cs +++ b/BMA.EHR.Application/Repositories/MessageQueue/InboxRepository.cs @@ -51,7 +51,8 @@ namespace BMA.EHR.Application.Repositories.MessageQueue // // throw new Exception(GlobalMessages.DataNotFound); // } - var apiUrl = $"{_configuration["API"]}/org/profile/keycloak/position"; + //var apiUrl = $"{_configuration["API"]}/org/profile/keycloak/position"; + var apiUrl = $"{_configuration["API"]}/org/dotnet/get-profileId"; var profileId = ""; using (var client = new HttpClient()) { diff --git a/BMA.EHR.Application/Repositories/MessageQueue/NotificationRepository.cs b/BMA.EHR.Application/Repositories/MessageQueue/NotificationRepository.cs index eeb1d71e..a9d5ceb7 100644 --- a/BMA.EHR.Application/Repositories/MessageQueue/NotificationRepository.cs +++ b/BMA.EHR.Application/Repositories/MessageQueue/NotificationRepository.cs @@ -55,7 +55,8 @@ namespace BMA.EHR.Application.Repositories.MessageQueue // // throw new Exception(GlobalMessages.DataNotFound); // } - var apiUrl = $"{_configuration["API"]}/org/profile/keycloak/position"; + //var apiUrl = $"{_configuration["API"]}/org/profile/keycloak/position"; + var apiUrl = $"{_configuration["API"]}/org/dotnet/get-profileId"; var profileId = ""; using (var client = new HttpClient()) { @@ -131,7 +132,8 @@ namespace BMA.EHR.Application.Repositories.MessageQueue // { // return 0; // } - var apiUrl = $"{_configuration["API"]}/org/profile/keycloak/position"; + //var apiUrl = $"{_configuration["API"]}/org/profile/keycloak/position"; + var apiUrl = $"{_configuration["API"]}/org/dotnet/get-profileId"; var profileId = ""; using (var client = new HttpClient()) { diff --git a/BMA.EHR.Discipline.Service/Controllers/DisciplineComplaint_AppealController.cs b/BMA.EHR.Discipline.Service/Controllers/DisciplineComplaint_AppealController.cs index a9d14a71..21204d6b 100644 --- a/BMA.EHR.Discipline.Service/Controllers/DisciplineComplaint_AppealController.cs +++ b/BMA.EHR.Discipline.Service/Controllers/DisciplineComplaint_AppealController.cs @@ -93,7 +93,8 @@ namespace BMA.EHR.DisciplineComplaint_Appeal.Service.Controllers public async Task> GetDisciplineUser(string status = "ALL", string type = "ALL", int year = 0, int page = 1, int pageSize = 25, string keyword = "", string? sortBy = null, bool descending = false) { var id = ""; - var apiUrl = $"{_configuration["API"]}/org/profile/keycloak/position"; + //var apiUrl = $"{_configuration["API"]}/org/profile/keycloak/position"; + var apiUrl = $"{_configuration["API"]}/org/dotnet/get-profileId"; using (var client = new HttpClient()) { client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token.Replace("Bearer ", "")); @@ -357,7 +358,8 @@ namespace BMA.EHR.DisciplineComplaint_Appeal.Service.Controllers [HttpPost()] public async Task> CreateDiscipline([FromForm] DisciplineComplaint_AppealRequest req) { - var apiUrl = $"{_configuration["API"]}/org/profile/keycloak/position"; + //var apiUrl = $"{_configuration["API"]}/org/profile/keycloak/position"; + var apiUrl = $"{_configuration["API"]}/org/dotnet/get-profileId"; var id = ""; var type = ""; using (var client = new HttpClient()) diff --git a/BMA.EHR.Placement.Service/Controllers/PlacementOfficerController.cs b/BMA.EHR.Placement.Service/Controllers/PlacementOfficerController.cs index 8638291e..19cc4e2d 100644 --- a/BMA.EHR.Placement.Service/Controllers/PlacementOfficerController.cs +++ b/BMA.EHR.Placement.Service/Controllers/PlacementOfficerController.cs @@ -223,7 +223,8 @@ namespace BMA.EHR.Placement.Service.Controllers [HttpGet("keycloak")] public async Task> GetListByKeycloak() { - var apiUrl = $"{_configuration["API"]}/org/profile/keycloak/position"; + // var apiUrl = $"{_configuration["API"]}/org/profile/keycloak/position"; + var apiUrl = $"{_configuration["API"]}/org/dotnet/get-profileId"; using (var client = new HttpClient()) { client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token.Replace("Bearer ", "")); diff --git a/BMA.EHR.Placement.Service/Controllers/PlacementTransferController.cs b/BMA.EHR.Placement.Service/Controllers/PlacementTransferController.cs index f2931e3d..68dd42a9 100644 --- a/BMA.EHR.Placement.Service/Controllers/PlacementTransferController.cs +++ b/BMA.EHR.Placement.Service/Controllers/PlacementTransferController.cs @@ -79,7 +79,8 @@ namespace BMA.EHR.Placement.Service.Controllers [HttpGet("user")] public async Task> GetListByProfile() { - var apiUrl = $"{_configuration["API"]}/org/profile/keycloak/position"; + // var apiUrl = $"{_configuration["API"]}/org/profile/keycloak/position"; + var apiUrl = $"{_configuration["API"]}/org/dotnet/get-profileId"; using (var client = new HttpClient()) { client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token.Replace("Bearer ", "")); diff --git a/BMA.EHR.Retirement.Service/Controllers/RetirementResignController.cs b/BMA.EHR.Retirement.Service/Controllers/RetirementResignController.cs index 2caa44a1..7b33e0fb 100644 --- a/BMA.EHR.Retirement.Service/Controllers/RetirementResignController.cs +++ b/BMA.EHR.Retirement.Service/Controllers/RetirementResignController.cs @@ -135,7 +135,8 @@ namespace BMA.EHR.Retirement.Service.Controllers [HttpGet("user")] public async Task> GetListByProfile() { - var apiUrl = $"{_configuration["API"]}/org/profile/keycloak/position"; + // var apiUrl = $"{_configuration["API"]}/org/profile/keycloak/position"; + var apiUrl = $"{_configuration["API"]}/org/dotnet/get-profileId"; using (var client = new HttpClient()) { client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token.Replace("Bearer ", "")); @@ -185,7 +186,8 @@ namespace BMA.EHR.Retirement.Service.Controllers [HttpGet("user-cancel")] public async Task> GetListByProfileCancel() { - var apiUrl = $"{_configuration["API"]}/org/profile/keycloak/position"; + // var apiUrl = $"{_configuration["API"]}/org/profile/keycloak/position"; + var apiUrl = $"{_configuration["API"]}/org/dotnet/get-profileId"; using (var client = new HttpClient()) { client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token.Replace("Bearer ", "")); From 90eb94cee327ac194378e4dd163404384bc6b9fd Mon Sep 17 00:00:00 2001 From: harid Date: Wed, 28 Jan 2026 16:24:59 +0700 Subject: [PATCH 076/183] =?UTF-8?q?=E0=B8=81=E0=B8=A3=E0=B8=AD=E0=B8=87?= =?UTF-8?q?=E0=B8=84=E0=B9=89=E0=B8=99=E0=B8=AB=E0=B8=B2=E0=B8=82=E0=B9=89?= =?UTF-8?q?=E0=B8=AD=E0=B8=A1=E0=B8=B9=E0=B8=A5=E0=B8=A3=E0=B8=B2=E0=B8=A2?= =?UTF-8?q?=E0=B8=81=E0=B8=B2=E0=B8=A3=E0=B8=A5=E0=B8=87=E0=B9=80=E0=B8=A7?= =?UTF-8?q?=E0=B8=A5=E0=B8=B2=E0=B8=81=E0=B8=A3=E0=B8=93=E0=B8=B5=E0=B8=9E?= =?UTF-8?q?=E0=B8=B4=E0=B9=80=E0=B8=A8=E0=B8=A9=20#2252?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../AdditionalCheckRequestRepository.cs | 13 ++++++++++++- BMA.EHR.Leave/Controllers/LeaveController.cs | 2 +- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/BMA.EHR.Application/Repositories/Leaves/TimeAttendants/AdditionalCheckRequestRepository.cs b/BMA.EHR.Application/Repositories/Leaves/TimeAttendants/AdditionalCheckRequestRepository.cs index 12f51207..64e1e745 100644 --- a/BMA.EHR.Application/Repositories/Leaves/TimeAttendants/AdditionalCheckRequestRepository.cs +++ b/BMA.EHR.Application/Repositories/Leaves/TimeAttendants/AdditionalCheckRequestRepository.cs @@ -144,7 +144,7 @@ namespace BMA.EHR.Application.Repositories.Leaves.TimeAttendants } } - public async Task> GetAdditionalCheckRequestsByAdminRole(int year, int month, string role, string nodeId, int? node) + public async Task> GetAdditionalCheckRequestsByAdminRole(int year, int month, string role, string nodeId, int? node, string? keyword) { try { @@ -152,6 +152,17 @@ namespace BMA.EHR.Application.Repositories.Leaves.TimeAttendants .Where(x => (x.CheckDate.Year == year && x.CheckDate.Month == month)) .OrderByDescending(x => x.CreatedAt.Date) .ToListAsync(); + + if (!string.IsNullOrEmpty(keyword)) + { + data = data.Where(x => + ( + (x.Prefix ?? "") + (x.FirstName ?? "") + " " + (x.LastName ?? "")).Contains(keyword) + || x.Description.Contains(keyword) + + ).ToList(); + } + if (role == "OWNER") { node = null; diff --git a/BMA.EHR.Leave/Controllers/LeaveController.cs b/BMA.EHR.Leave/Controllers/LeaveController.cs index 809a28e3..9d7c4581 100644 --- a/BMA.EHR.Leave/Controllers/LeaveController.cs +++ b/BMA.EHR.Leave/Controllers/LeaveController.cs @@ -2805,7 +2805,7 @@ namespace BMA.EHR.Leave.Service.Controllers } //var rawData = await _additionalCheckRequestRepository.GetAdditionalCheckRequests(year, month); - var rawData = await _additionalCheckRequestRepository.GetAdditionalCheckRequestsByAdminRole(year, month, role, nodeId, profileAdmin?.Node); + var rawData = await _additionalCheckRequestRepository.GetAdditionalCheckRequestsByAdminRole(year, month, role, nodeId, profileAdmin?.Node, keyword); var total = rawData.Count; From 4c189fdc4a5b08c797566ab117c35afd68107f7b Mon Sep 17 00:00:00 2001 From: Suphonchai Phoonsawat Date: Thu, 29 Jan 2026 10:07:29 +0700 Subject: [PATCH 077/183] Fix null reference for CheckIn and CheckOut location names in LeaveController --- BMA.EHR.Leave/Controllers/LeaveController.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/BMA.EHR.Leave/Controllers/LeaveController.cs b/BMA.EHR.Leave/Controllers/LeaveController.cs index 9d7c4581..56e79f48 100644 --- a/BMA.EHR.Leave/Controllers/LeaveController.cs +++ b/BMA.EHR.Leave/Controllers/LeaveController.cs @@ -1462,7 +1462,7 @@ namespace BMA.EHR.Leave.Service.Controllers "LATE" : "NORMAL", CheckInIsLocation = d.IsLocationCheckIn, - CheckInLocationName = d.CheckInLocationName, + CheckInLocationName = d.CheckInLocationName ?? "", CheckOutDate = d.CheckOut == null ? null : d.CheckOut.Value.Date, CheckOutTime = d.CheckOut == null ? "" : d.CheckOut.Value.ToString("HH:mm:ss"), CheckOutLocation = d.CheckOutPOI ?? "", @@ -1477,7 +1477,7 @@ namespace BMA.EHR.Leave.Service.Controllers "NORMAL", CheckOutIsLocation = d.IsLocationCheckOut, - CheckOutLocationName = d.CheckOutLocationName, + CheckOutLocationName = d.CheckOutLocationName ?? "", IsEdit = _processUserTimeStampRepository.IsEditRequest(userId, d.CheckIn.Date) From e80f89117ce3b6fa36636e78a459e318e030dced Mon Sep 17 00:00:00 2001 From: harid Date: Thu, 29 Jan 2026 13:22:41 +0700 Subject: [PATCH 078/183] Change Call Org --- .../LeaveRequests/LeaveRequestRepository.cs | 3 +- .../AdditionalCheckRequestRepository.cs | 3 +- .../Repositories/UserProfileRepository.cs | 23 +++++ .../DisciplineDirectorController.cs | 2 +- .../Controllers/LeaveRequestController.cs | 99 ++++++++++++------- 5 files changed, 93 insertions(+), 37 deletions(-) diff --git a/BMA.EHR.Application/Repositories/Leaves/LeaveRequests/LeaveRequestRepository.cs b/BMA.EHR.Application/Repositories/Leaves/LeaveRequests/LeaveRequestRepository.cs index e0e519e8..57572c33 100644 --- a/BMA.EHR.Application/Repositories/Leaves/LeaveRequests/LeaveRequestRepository.cs +++ b/BMA.EHR.Application/Repositories/Leaves/LeaveRequests/LeaveRequestRepository.cs @@ -253,7 +253,8 @@ namespace BMA.EHR.Application.Repositories.Leaves.LeaveRequests public async Task> GetLeaveRequestByYearAsync(int year, Guid userId) { - var profile = await _userProfileRepository.GetProfileByKeycloakIdAsync(userId, AccessToken); + // var profile = await _userProfileRepository.GetProfileByKeycloakIdAsync(userId, AccessToken); + var profile = await _userProfileRepository.GetProfileByKeycloakIdNewAsync(userId, AccessToken); if (profile == null) { diff --git a/BMA.EHR.Application/Repositories/Leaves/TimeAttendants/AdditionalCheckRequestRepository.cs b/BMA.EHR.Application/Repositories/Leaves/TimeAttendants/AdditionalCheckRequestRepository.cs index 64e1e745..3dca61e1 100644 --- a/BMA.EHR.Application/Repositories/Leaves/TimeAttendants/AdditionalCheckRequestRepository.cs +++ b/BMA.EHR.Application/Repositories/Leaves/TimeAttendants/AdditionalCheckRequestRepository.cs @@ -74,7 +74,8 @@ namespace BMA.EHR.Application.Repositories.Leaves.TimeAttendants await base.AddAsync(entity); var userId = UserId != null ? Guid.Parse(UserId) : Guid.Empty; - var profile = await _userProfileRepository.GetProfileByKeycloakIdAsync(userId, AccessToken ?? ""); + // var profile = await _userProfileRepository.GetProfileByKeycloakIdAsync(userId, AccessToken ?? ""); + var profile = await _userProfileRepository.GetProfileByKeycloakIdNew2Async(userId, AccessToken ?? ""); // fix issue : SIT ระบบบันทึกเวลาปฏิบัติงาน>>ลงเวลากรณีพิเศษ (ไม่มีแจ้งเตือนไปยังผู้บังคับบัญชา) #969 // send noti + inbox + mail diff --git a/BMA.EHR.Application/Repositories/UserProfileRepository.cs b/BMA.EHR.Application/Repositories/UserProfileRepository.cs index b868a4e9..ebf5113f 100644 --- a/BMA.EHR.Application/Repositories/UserProfileRepository.cs +++ b/BMA.EHR.Application/Repositories/UserProfileRepository.cs @@ -209,6 +209,29 @@ namespace BMA.EHR.Application.Repositories } } + public async Task GetProfileByKeycloakIdNew2Async(Guid keycloakId, string? accessToken) + { + try + { + var apiPath = $"{_configuration["API"]}/org/dotnet/by-keycloak2/{keycloakId}"; + var apiKey = _configuration["API_KEY"]; + + var apiResult = await GetExternalAPIAsync(apiPath, accessToken ?? "", apiKey); + if (apiResult != null) + { + var raw = JsonConvert.DeserializeObject(apiResult); + if (raw != null) + return raw.Result; + } + + return null; + } + catch + { + throw; + } + } + public async Task GetProfileLeaveByKeycloakIdAsync(Guid keycloakId, string? accessToken) { diff --git a/BMA.EHR.Discipline.Service/Controllers/DisciplineDirectorController.cs b/BMA.EHR.Discipline.Service/Controllers/DisciplineDirectorController.cs index 202eaa44..c4bd24ae 100644 --- a/BMA.EHR.Discipline.Service/Controllers/DisciplineDirectorController.cs +++ b/BMA.EHR.Discipline.Service/Controllers/DisciplineDirectorController.cs @@ -392,7 +392,7 @@ namespace BMA.EHR.DisciplineDirector.Service.Controllers return Error(new Exception(GlobalMessages.DataNotFound), StatusCodes.Status404NotFound); var userId = UserId == null ? Guid.Empty : Guid.Parse(UserId); - var profile = await _userProfileRepository.GetProfileByKeycloakIdAsync(userId, token.Replace("Bearer ", "")); + var profile = await _userProfileRepository.GetProfileByKeycloakIdNewAsync(userId, token.Replace("Bearer ", "")); if (profile == null) return Error(GlobalMessages.DataNotFound); diff --git a/BMA.EHR.Leave/Controllers/LeaveRequestController.cs b/BMA.EHR.Leave/Controllers/LeaveRequestController.cs index 5d6c37c4..d0edb337 100644 --- a/BMA.EHR.Leave/Controllers/LeaveRequestController.cs +++ b/BMA.EHR.Leave/Controllers/LeaveRequestController.cs @@ -212,7 +212,8 @@ namespace BMA.EHR.Leave.Service.Controllers var thisYear = DateTime.Now.Year; - var profile = await _userProfileRepository.GetProfileByKeycloakIdAsync(userId, AccessToken); + // var profile = await _userProfileRepository.GetProfileByKeycloakIdAsync(userId, AccessToken); + var profile = await _userProfileRepository.GetProfileByKeycloakIdNewAsync(userId, AccessToken); if (profile == null) { @@ -377,11 +378,15 @@ namespace BMA.EHR.Leave.Service.Controllers break; case "LV-008": { - var lastSalary = profile.ProfileSalary; - var lastSalaryAmount = lastSalary == null ? 0 : lastSalary.Amount == null ? 0 : lastSalary.Amount; - var lastSalaryAmountText = lastSalary == null ? "" : lastSalary.Amount == null ? "" : ((int)lastSalary.Amount).ToThaiBahtText(false); - leaveRequest.LeaveSalary = (int)lastSalaryAmount; - leaveRequest.LeaveSalaryText = lastSalaryAmountText; + // var lastSalary = profile.ProfileSalary; + // var lastSalaryAmount = lastSalary == null ? 0 : lastSalary.Amount == null ? 0 : lastSalary.Amount; + // var lastSalaryAmountText = lastSalary == null ? "" : lastSalary.Amount == null ? "" : ((int)lastSalary.Amount).ToThaiBahtText(false); + // leaveRequest.LeaveSalary = (int)lastSalaryAmount; + // leaveRequest.LeaveSalaryText = lastSalaryAmountText; + leaveRequest.LeaveSalary = profile.Amount.HasValue && profile.Amount > 0 + ? (int)profile.Amount : 0; + leaveRequest.LeaveSalaryText = profile.Amount.HasValue && profile.Amount > 0 + ? ((int)profile.Amount).ToThaiBahtText(false) : ""; //leaveRequest.LeaveSalary = lastSalary == null ? 0 : (int)lastSalary.Amount.Value; //leaveRequest.LeaveSalaryText = // lastSalary == null ? "" : ((int)lastSalary.Amount.Value).ToThaiBahtText(false); @@ -401,11 +406,15 @@ namespace BMA.EHR.Leave.Service.Controllers break; case "LV-010": { - var lastSalary = profile.ProfileSalary; - var lastSalaryAmount = lastSalary == null ? 0 : lastSalary.Amount == null ? 0 : lastSalary.Amount; - var lastSalaryAmountText = lastSalary == null ? "" : lastSalary.Amount == null ? "" : ((int)lastSalary.Amount).ToThaiBahtText(false); - leaveRequest.LeaveSalary = (int)lastSalaryAmount; - leaveRequest.LeaveSalaryText = lastSalaryAmountText; + // var lastSalary = profile.ProfileSalary; + // var lastSalaryAmount = lastSalary == null ? 0 : lastSalary.Amount == null ? 0 : lastSalary.Amount; + // var lastSalaryAmountText = lastSalary == null ? "" : lastSalary.Amount == null ? "" : ((int)lastSalary.Amount).ToThaiBahtText(false); + // leaveRequest.LeaveSalary = (int)lastSalaryAmount; + // leaveRequest.LeaveSalaryText = lastSalaryAmountText; + leaveRequest.LeaveSalary = profile.Amount.HasValue && profile.Amount > 0 + ? (int)profile.Amount : 0; + leaveRequest.LeaveSalaryText = profile.Amount.HasValue && profile.Amount > 0 + ? ((int)profile.Amount).ToThaiBahtText(false) : ""; //leaveRequest.LeaveSalary = lastSalary == null ? 0 : (int)lastSalary.Amount.Value; //leaveRequest.LeaveSalaryText = // lastSalary == null ? "" : ((int)lastSalary.Amount.Value).ToThaiBahtText(false); @@ -491,7 +500,8 @@ namespace BMA.EHR.Leave.Service.Controllers foreach (var leave in leaves) { - var profile = await _userProfileRepository.GetProfileByKeycloakIdAsync(leave.KeycloakUserId, AccessToken); + // var profile = await _userProfileRepository.GetProfileByKeycloakIdAsync(leave.KeycloakUserId, AccessToken); + var profile = await _userProfileRepository.GetProfileByKeycloakIdNewAsync(leave.KeycloakUserId, AccessToken); if (profile != null) { leave.Prefix = profile.Prefix; @@ -551,7 +561,8 @@ namespace BMA.EHR.Leave.Service.Controllers // return Error("ไม่สามารถขอลาในช่วงเวลาเดียวกันได้"); // } - var profile = await _userProfileRepository.GetProfileByKeycloakIdAsync(userId, AccessToken); + // var profile = await _userProfileRepository.GetProfileByKeycloakIdAsync(userId, AccessToken); + var profile = await _userProfileRepository.GetProfileByKeycloakIdNewAsync(userId, AccessToken); if (profile == null) { @@ -676,11 +687,15 @@ namespace BMA.EHR.Leave.Service.Controllers break; case "LV-008": { - var lastSalary = profile.ProfileSalary; + // var lastSalary = profile.ProfileSalary; - oldData.LeaveSalary = lastSalary == null ? 0 : (int)lastSalary.Amount.Value; - oldData.LeaveSalaryText = - lastSalary == null ? "" : ((int)lastSalary.Amount.Value).ToThaiBahtText(false); + // oldData.LeaveSalary = lastSalary == null ? 0 : (int)lastSalary.Amount.Value; + // oldData.LeaveSalaryText = + // lastSalary == null ? "" : ((int)lastSalary.Amount.Value).ToThaiBahtText(false); + oldData.LeaveSalary = profile.Amount.HasValue && profile.Amount > 0 + ? (int)profile.Amount : 0; + oldData.LeaveSalaryText = profile.Amount.HasValue && profile.Amount > 0 + ? ((int)profile.Amount).ToThaiBahtText(false) : ""; oldData.LeaveBirthDate = profile.BirthDate; oldData.LeaveGovernmentDate = profile.DateStart; @@ -696,12 +711,15 @@ namespace BMA.EHR.Leave.Service.Controllers break; case "LV-010": { - var lastSalary = profile.ProfileSalary; - - oldData.LeaveSalary = lastSalary == null ? 0 : (int)lastSalary.Amount.Value; - oldData.LeaveSalaryText = - lastSalary == null ? "" : ((int)lastSalary.Amount.Value).ToThaiBahtText(false); + // var lastSalary = profile.ProfileSalary; + // oldData.LeaveSalary = lastSalary == null ? 0 : (int)lastSalary.Amount.Value; + // oldData.LeaveSalaryText = + // lastSalary == null ? "" : ((int)lastSalary.Amount.Value).ToThaiBahtText(false); + oldData.LeaveSalary = profile.Amount.HasValue && profile.Amount > 0 + ? (int)profile.Amount : 0; + oldData.LeaveSalaryText = profile.Amount.HasValue && profile.Amount > 0 + ? ((int)profile.Amount).ToThaiBahtText(false) : ""; oldData.CoupleDayName = req.CoupleDayName ?? ""; oldData.CoupleDayPosition = req.CoupleDayPosition ?? ""; oldData.CoupleDayLevel = req.CoupleDayLevel ?? ""; @@ -844,7 +862,8 @@ namespace BMA.EHR.Leave.Service.Controllers if (toDay >= startFiscalDate && toDay <= endFiscalDate) thisYear = thisYear + 1; - var profile = await _userProfileRepository.GetProfileByKeycloakIdAsync(userId, AccessToken); + // var profile = await _userProfileRepository.GetProfileByKeycloakIdAsync(userId, AccessToken); + var profile = await _userProfileRepository.GetProfileByKeycloakIdNew2Async(userId, AccessToken); if (profile == null) { return Error(GlobalMessages.DataNotFound, StatusCodes.Status404NotFound); @@ -887,7 +906,7 @@ namespace BMA.EHR.Leave.Service.Controllers var restCurrentDay = 10.0; var sumLeave = leaveData == null ? 0 : leaveData.LeaveDaysUsed; - var lastSalary = profile.ProfileSalary; + // var lastSalary = profile.ProfileSalary; var leaveLast = await _leaveRequestRepository.GetLeaveLastByTypeForUserAsync(userId, req.Type); @@ -912,8 +931,12 @@ namespace BMA.EHR.Leave.Service.Controllers RestDayTotalCurrent = restCurrentDay,// 10 วันเสมอ (LV-005) BirthDate = profile.BirthDate.Date, DateAppoint = profile.DateAppoint == null ? null : profile.DateAppoint.Value.Date, - Salary = lastSalary == null ? 0 : lastSalary.Amount == null ? 0 : (int)lastSalary.Amount.Value, - SalaryText = lastSalary == null ? "" : lastSalary.Amount == null ? "" : ((int)lastSalary.Amount.Value).ToThaiBahtText(false), + // Salary = lastSalary == null ? 0 : lastSalary.Amount == null ? 0 : (int)lastSalary.Amount.Value, + // SalaryText = lastSalary == null ? "" : lastSalary.Amount == null ? "" : ((int)lastSalary.Amount.Value).ToThaiBahtText(false), + Salary = profile.Amount.HasValue && profile.Amount > 0 + ? (int)profile.Amount : 0, + SalaryText = profile.Amount.HasValue && profile.Amount > 0 + ? ((int)profile.Amount).ToThaiBahtText(false) : "", LeaveLast = leaveLast == null ? null : leaveLast, TelephoneNumber = profile.TelephoneNumber ?? "", @@ -945,7 +968,8 @@ namespace BMA.EHR.Leave.Service.Controllers var userId = UserId == null ? Guid.Empty : Guid.Parse(UserId); - var profile = await _userProfileRepository.GetProfileByKeycloakIdAsync(userId, AccessToken); + // var profile = await _userProfileRepository.GetProfileByKeycloakIdAsync(userId, AccessToken); + var profile = await _userProfileRepository.GetProfileByKeycloakIdNewAsync(userId, AccessToken); var govAge = (profile?.DateStart?.Date ?? DateTime.Now.Date).DiffDay(DateTime.Now.Date); var thisYear = DateTime.Now.Year; @@ -1495,7 +1519,8 @@ namespace BMA.EHR.Leave.Service.Controllers return Error(GlobalMessages.DataNotFound, StatusCodes.Status404NotFound); } - var profile = await _userProfileRepository.GetProfileByKeycloakIdAsync(rawData.KeycloakUserId, AccessToken); + // var profile = await _userProfileRepository.GetProfileByKeycloakIdAsync(rawData.KeycloakUserId, AccessToken); + var profile = await _userProfileRepository.GetProfileByKeycloakIdNewAsync(rawData.KeycloakUserId, AccessToken); if (profile == null) { @@ -1507,8 +1532,8 @@ namespace BMA.EHR.Leave.Service.Controllers var userCalendar = await _userCalendarRepository.GetExist(profile.Id); var category = userCalendar == null ? "NORMAL" : userCalendar.Calendar; - var lastSalary = profile.ProfileSalary; - var lastSalaryAmount = lastSalary == null ? 0 : lastSalary.Amount ?? 0; + // var lastSalary = profile.ProfileSalary; + // var lastSalaryAmount = lastSalary == null ? 0 : lastSalary.Amount ?? 0; var lastLeaveRequest = await _leaveRequestRepository.GetLastLeaveRequestByTypeForUserAsync(rawData.KeycloakUserId, @@ -1573,8 +1598,12 @@ namespace BMA.EHR.Leave.Service.Controllers LeaveBirthDate = profile.BirthDate, LeaveGovernmentDate = profile.DateAppoint == null ? null : profile.DateAppoint.Value, - LeaveSalary = lastSalary == null ? 0 : lastSalaryAmount, - LeaveSalaryText = lastSalary == null ? "" : ((int)lastSalaryAmount).ToThaiBahtText(false), + // LeaveSalary = lastSalary == null ? 0 : lastSalaryAmount, + // LeaveSalaryText = lastSalary == null ? "" : ((int)lastSalaryAmount).ToThaiBahtText(false), + LeaveSalary = profile.Amount.HasValue && profile.Amount > 0 + ? (int)profile.Amount : 0, + LeaveSalaryText = profile.Amount.HasValue && profile.Amount > 0 + ? ((int)profile.Amount).ToThaiBahtText(false) : "", WifeDayName = rawData.WifeDayName, WifeDayDateBorn = rawData.WifeDayDateBorn, @@ -2024,7 +2053,8 @@ namespace BMA.EHR.Leave.Service.Controllers return Error(jsonData["message"]?.ToString(), StatusCodes.Status403Forbidden); } - var profile = await _userProfileRepository.GetProfileByKeycloakIdAsync(Guid.Parse(UserId!), AccessToken); + // var profile = await _userProfileRepository.GetProfileByKeycloakIdAsync(Guid.Parse(UserId!), AccessToken); + var profile = await _userProfileRepository.GetProfileByKeycloakIdNewAsync(Guid.Parse(UserId!), AccessToken); if (profile == null) { @@ -2649,7 +2679,8 @@ namespace BMA.EHR.Leave.Service.Controllers var sendList = await _leaveRequestRepository.GetSumSendLeaveAsync(thisYear); var rejectList = await _leaveRequestRepository.GetSumRejectLeaveAsync(thisYear); var deleteList = await _leaveRequestRepository.GetSumDeleteLeaveAsync(thisYear); - var pf = await _userProfileRepository.GetProfileByKeycloakIdAsync(userId, AccessToken); + // var pf = await _userProfileRepository.GetProfileByKeycloakIdAsync(userId, AccessToken); + var pf = await _userProfileRepository.GetProfileByKeycloakIdNewAsync(userId, AccessToken); if (pf == null) { From 46504c9e30a692e5c1836efb722b0849e4db20ec Mon Sep 17 00:00:00 2001 From: harid Date: Thu, 29 Jan 2026 13:37:38 +0700 Subject: [PATCH 079/183] Change Call Org --- .../LeaveRequests/LeaveBeginingRepository.cs | 9 ++++++--- .../LeaveRequests/LeaveRequestRepository.cs | 18 ++++++++++++------ 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/BMA.EHR.Application/Repositories/Leaves/LeaveRequests/LeaveBeginingRepository.cs b/BMA.EHR.Application/Repositories/Leaves/LeaveRequests/LeaveBeginingRepository.cs index 575a85bb..dfb5897d 100644 --- a/BMA.EHR.Application/Repositories/Leaves/LeaveRequests/LeaveBeginingRepository.cs +++ b/BMA.EHR.Application/Repositories/Leaves/LeaveRequests/LeaveBeginingRepository.cs @@ -79,7 +79,8 @@ namespace BMA.EHR.Application.Repositories.Leaves.LeaveRequests public async Task UpdateLeaveUsageAsync(int year, Guid typeId, Guid userId, double day) { - var pf = await _userProfileRepository.GetProfileByKeycloakIdAsync(userId, AccessToken); + // var pf = await _userProfileRepository.GetProfileByKeycloakIdAsync(userId, AccessToken); + var pf = await _userProfileRepository.GetProfileByKeycloakIdNewAsync(userId, AccessToken); if (pf == null) { throw new Exception(GlobalMessages.DataNotFound); @@ -100,7 +101,8 @@ namespace BMA.EHR.Application.Repositories.Leaves.LeaveRequests public async Task GetByYearAndTypeIdForUserAsync(int year, Guid typeId, Guid userId) { - var pf = await _userProfileRepository.GetProfileByKeycloakIdAsync(userId, AccessToken); + // var pf = await _userProfileRepository.GetProfileByKeycloakIdAsync(userId, AccessToken); + var pf = await _userProfileRepository.GetProfileByKeycloakIdNewAsync(userId, AccessToken); if (pf == null) { throw new Exception(GlobalMessages.DataNotFound); @@ -238,7 +240,8 @@ namespace BMA.EHR.Application.Repositories.Leaves.LeaveRequests public async Task GetByYearAndTypeIdForUser2Async(int year, Guid typeId, Guid userId) { - var pf = await _userProfileRepository.GetProfileByKeycloakIdAsync(userId, AccessToken); + // var pf = await _userProfileRepository.GetProfileByKeycloakIdAsync(userId, AccessToken); + var pf = await _userProfileRepository.GetProfileByKeycloakIdNewAsync(userId, AccessToken); if (pf == null) { return null; diff --git a/BMA.EHR.Application/Repositories/Leaves/LeaveRequests/LeaveRequestRepository.cs b/BMA.EHR.Application/Repositories/Leaves/LeaveRequests/LeaveRequestRepository.cs index 57572c33..b97441be 100644 --- a/BMA.EHR.Application/Repositories/Leaves/LeaveRequests/LeaveRequestRepository.cs +++ b/BMA.EHR.Application/Repositories/Leaves/LeaveRequests/LeaveRequestRepository.cs @@ -496,7 +496,8 @@ namespace BMA.EHR.Application.Repositories.Leaves.LeaveRequests public async Task GetSumLeaveByTypeForUserAsync(Guid keycloakUserId, Guid leaveTypeId, int year) { - var pf = await _userProfileRepository.GetProfileByKeycloakIdAsync(keycloakUserId, AccessToken); + // var pf = await _userProfileRepository.GetProfileByKeycloakIdAsync(keycloakUserId, AccessToken); + var pf = await _userProfileRepository.GetProfileByKeycloakIdNewAsync(keycloakUserId, AccessToken); if (pf == null) throw new Exception(GlobalMessages.DataNotFound); @@ -632,7 +633,8 @@ namespace BMA.EHR.Application.Repositories.Leaves.LeaveRequests { try { - var profile = await _userProfileRepository.GetProfileByKeycloakIdAsync(data.KeycloakUserId, AccessToken ?? ""); + // var profile = await _userProfileRepository.GetProfileByKeycloakIdAsync(data.KeycloakUserId, AccessToken ?? ""); + var profile = await _userProfileRepository.GetProfileByKeycloakIdNewAsync(data.KeycloakUserId, AccessToken ?? ""); if (profile == null) { throw new Exception(GlobalMessages.DataNotFound); @@ -705,7 +707,8 @@ namespace BMA.EHR.Application.Repositories.Leaves.LeaveRequests throw new Exception(GlobalMessages.DataNotFound); } - var profile = await _userProfileRepository.GetProfileByKeycloakIdAsync(rawData.KeycloakUserId, AccessToken ?? ""); + // var profile = await _userProfileRepository.GetProfileByKeycloakIdAsync(rawData.KeycloakUserId, AccessToken ?? ""); + var profile = await _userProfileRepository.GetProfileByKeycloakIdNewAsync(rawData.KeycloakUserId, AccessToken ?? ""); if (profile == null) { throw new Exception(GlobalMessages.DataNotFound); @@ -791,7 +794,8 @@ namespace BMA.EHR.Application.Repositories.Leaves.LeaveRequests throw new Exception(GlobalMessages.DataNotFound); } - var profile = await _userProfileRepository.GetProfileByKeycloakIdAsync(rawData.KeycloakUserId, AccessToken ?? ""); + // var profile = await _userProfileRepository.GetProfileByKeycloakIdAsync(rawData.KeycloakUserId, AccessToken ?? ""); + var profile = await _userProfileRepository.GetProfileByKeycloakIdNewAsync(rawData.KeycloakUserId, AccessToken ?? ""); if (profile == null) { throw new Exception(GlobalMessages.DataNotFound); @@ -1215,7 +1219,8 @@ namespace BMA.EHR.Application.Repositories.Leaves.LeaveRequests } else { - var profile = await _userProfileRepository.GetProfileByKeycloakIdAsync(rawData.KeycloakUserId, AccessToken); + // var profile = await _userProfileRepository.GetProfileByKeycloakIdAsync(rawData.KeycloakUserId, AccessToken); + var profile = await _userProfileRepository.GetProfileByKeycloakIdNewAsync(rawData.KeycloakUserId, AccessToken); if (profile == null) { throw new Exception(GlobalMessages.DataNotFound); @@ -1378,7 +1383,8 @@ namespace BMA.EHR.Application.Repositories.Leaves.LeaveRequests } else { - var profile = await _userProfileRepository.GetProfileByKeycloakIdAsync(rawData.KeycloakUserId, AccessToken); + // var profile = await _userProfileRepository.GetProfileByKeycloakIdAsync(rawData.KeycloakUserId, AccessToken); + var profile = await _userProfileRepository.GetProfileByKeycloakIdNewAsync(rawData.KeycloakUserId, AccessToken); if (profile == null) { throw new Exception(GlobalMessages.DataNotFound); From 06b53ddeaa41c96e3d6e62df6fe01daae2b214fa Mon Sep 17 00:00:00 2001 From: harid Date: Thu, 29 Jan 2026 15:09:14 +0700 Subject: [PATCH 080/183] Fix Bug Leave Report Issue #2233 --- .../Controllers/LeaveReportController.cs | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/BMA.EHR.Leave/Controllers/LeaveReportController.cs b/BMA.EHR.Leave/Controllers/LeaveReportController.cs index e71b4780..b89f22a4 100644 --- a/BMA.EHR.Leave/Controllers/LeaveReportController.cs +++ b/BMA.EHR.Leave/Controllers/LeaveReportController.cs @@ -160,7 +160,7 @@ namespace BMA.EHR.Leave.Service.Controllers var Oc = profile.isCommission == false ? profile.Oc.ToThaiNumber() : profile.Oc.Replace("สำนักงานคณะกรรมการข้าราชการกรุงเทพมหานคร", "สำนักงาน ก.ก.").ToThaiNumber(); - var approveResult = await GetApproverData(data.Approvers, profile.isCommission); + var approveResult = await GetApproverData(data.Approvers); return new { @@ -251,7 +251,7 @@ namespace BMA.EHR.Leave.Service.Controllers var Oc = profile.isCommission == false ? profile.Oc.ToThaiNumber() : profile.Oc.Replace("สำนักงานคณะกรรมการข้าราชการกรุงเทพมหานคร", "สำนักงาน ก.ก.").ToThaiNumber(); - var approveResult = await GetApproverData(data.Approvers, profile.isCommission); + var approveResult = await GetApproverData(data.Approvers); return new { template = "leave10", @@ -341,7 +341,7 @@ namespace BMA.EHR.Leave.Service.Controllers var Oc = profile.isCommission == false ? profile.Oc.ToThaiNumber() : profile.Oc.Replace("สำนักงานคณะกรรมการข้าราชการกรุงเทพมหานคร", "สำนักงาน ก.ก.").ToThaiNumber(); - var approveResult = await GetApproverData(data.Approvers, profile.isCommission); + var approveResult = await GetApproverData(data.Approvers); return new { template = "leave11", @@ -434,7 +434,7 @@ namespace BMA.EHR.Leave.Service.Controllers var Oc = profile.isCommission == false ? profile.Oc.ToThaiNumber() : profile.Oc.Replace("สำนักงานคณะกรรมการข้าราชการกรุงเทพมหานคร", "สำนักงาน ก.ก.").ToThaiNumber(); - var approveResult = await GetApproverData(data.Approvers, profile.isCommission); + var approveResult = await GetApproverData(data.Approvers); if (isHajj == true) { return new @@ -577,7 +577,7 @@ namespace BMA.EHR.Leave.Service.Controllers var Oc = profile.isCommission == false ? profile.Oc.ToThaiNumber() : profile.Oc.Replace("สำนักงานคณะกรรมการข้าราชการกรุงเทพมหานคร", "สำนักงาน ก.ก.").ToThaiNumber(); - var approveResult = await GetApproverData(data.Approvers, profile.isCommission); + var approveResult = await GetApproverData(data.Approvers); return new { template = "leave14", @@ -658,7 +658,7 @@ namespace BMA.EHR.Leave.Service.Controllers var Oc = profile.isCommission == false ? profile.Oc.ToThaiNumber() : profile.Oc.Replace("สำนักงานคณะกรรมการข้าราชการกรุงเทพมหานคร", "สำนักงาน ก.ก.").ToThaiNumber(); - var approveResult = await GetApproverData(data.Approvers, profile.isCommission); + var approveResult = await GetApproverData(data.Approvers); return new { template = "leave15", @@ -761,7 +761,7 @@ namespace BMA.EHR.Leave.Service.Controllers var Oc = profile.isCommission == false ? profile.Oc.ToThaiNumber() : profile.Oc.Replace("สำนักงานคณะกรรมการข้าราชการกรุงเทพมหานคร", "สำนักงาน ก.ก.").ToThaiNumber(); - var approveResult = await GetApproverData(data.Approvers, profile.isCommission); + var approveResult = await GetApproverData(data.Approvers); return new { template = "leave16", @@ -880,7 +880,7 @@ namespace BMA.EHR.Leave.Service.Controllers var Oc = profile.isCommission == false ? profile.Oc.ToThaiNumber() : profile.Oc.Replace("สำนักงานคณะกรรมการข้าราชการกรุงเทพมหานคร", "สำนักงาน ก.ก.").ToThaiNumber(); - var approveResult = await GetApproverData(data.Approvers, profile.isCommission); + var approveResult = await GetApproverData(data.Approvers); return new { template = "leave17", @@ -976,7 +976,7 @@ namespace BMA.EHR.Leave.Service.Controllers var Oc = profile.isCommission == false ? profile.Oc.ToThaiNumber() : profile.Oc.Replace("สำนักงานคณะกรรมการข้าราชการกรุงเทพมหานคร", "สำนักงาน ก.ก.").ToThaiNumber(); - var approveResult = await GetApproverData(data.Approvers, profile.isCommission); + var approveResult = await GetApproverData(data.Approvers); return new { template = "leave18", @@ -1202,7 +1202,7 @@ namespace BMA.EHR.Leave.Service.Controllers var Oc = profile.isCommission == false ? profile.Oc.ToThaiNumber() : profile.Oc.Replace("สำนักงานคณะกรรมการข้าราชการกรุงเทพมหานคร", "สำนักงาน ก.ก.").ToThaiNumber(); - var approveResult = await GetApproverData(data.Approvers, profile.isCommission); + var approveResult = await GetApproverData(data.Approvers); var result = new { template = "แบบใบขอยกเลิกวันลา", @@ -2888,7 +2888,7 @@ namespace BMA.EHR.Leave.Service.Controllers } #endregion - private async Task GetApproverData(List list, bool? isCommission = false) + private async Task GetApproverData(List list) { var _default = new { @@ -2919,10 +2919,10 @@ namespace BMA.EHR.Leave.Service.Controllers : $"{x.PositionName}{x.PositionLevelName}").ToThaiNumber(), posExOrg = !string.IsNullOrEmpty(x.PositionLevelName) && (x.PositionLevelName.Contains("อำนวยการ") || x.PositionLevelName.Contains("บริหาร")) - ? new[] { (isCommission == false ? x!.OrganizationName : x!.OrganizationName.Replace("สำนักงานคณะกรรมการข้าราชการกรุงเทพมหานคร", "สำนักงาน ก.ก.")).ToThaiNumber() } + ? new[] { (x!.OrganizationName.Replace("สำนักงานคณะกรรมการข้าราชการกรุงเทพมหานคร", "สำนักงาน ก.ก.")).ToThaiNumber() } : !string.IsNullOrEmpty(x.PosExecutiveName) - ? new[] { (x.PosExecutiveName.ToThaiNumber()), (isCommission == false ? x!.OrganizationName : x!.OrganizationName.Replace("สำนักงานคณะกรรมการข้าราชการกรุงเทพมหานคร", "สำนักงาน ก.ก.")).ToThaiNumber() } - : new[] { (isCommission == false ? x!.OrganizationName : x!.OrganizationName.Replace("สำนักงานคณะกรรมการข้าราชการกรุงเทพมหานคร", "สำนักงาน ก.ก.")).ToThaiNumber() }, + ? new[] { x.PosExecutiveName.ToThaiNumber(), x!.OrganizationName.Replace("สำนักงานคณะกรรมการข้าราชการกรุงเทพมหานคร", "สำนักงาน ก.ก.").ToThaiNumber() } + : new[] { x!.OrganizationName.Replace("สำนักงานคณะกรรมการข้าราชการกรุงเทพมหานคร", "สำนักงาน ก.ก.").ToThaiNumber() }, positionSign = !string.IsNullOrEmpty(x.PositionSign) ? x.PositionSign.Replace("\r", "").Replace("\n", " ").ToThaiNumber() : "............................................", From 5c05f1123a393f05a7978e3be208587504c1b461 Mon Sep 17 00:00:00 2001 From: Suphonchai Phoonsawat Date: Fri, 30 Jan 2026 09:49:54 +0700 Subject: [PATCH 081/183] Increase HttpClient timeout for long-running operations --- BMA.EHR.Leave/Controllers/LeaveController.cs | 499 ++++++++++--------- BMA.EHR.Leave/Program.cs | 6 +- 2 files changed, 259 insertions(+), 246 deletions(-) diff --git a/BMA.EHR.Leave/Controllers/LeaveController.cs b/BMA.EHR.Leave/Controllers/LeaveController.cs index 56e79f48..062ceab1 100644 --- a/BMA.EHR.Leave/Controllers/LeaveController.cs +++ b/BMA.EHR.Leave/Controllers/LeaveController.cs @@ -948,93 +948,114 @@ namespace BMA.EHR.Leave.Service.Controllers var fileName = $"{_bucketName}/{userId}/{currentDate.ToString("dd-MM-yyyy")}/{check_status}/{data.CheckInFileName}"; using (var ms = new MemoryStream(data.CheckInFileBytes ?? new byte[0])) { - await _minIOService.UploadFileAsync(fileName, ms); + try + { + await _minIOService.UploadFileAsync(fileName, ms); + } + catch (Exception ex) + { + await _checkInJobStatusRepository.UpdateToFailedAsync(taskId, $"ไม่สามารถอัปโหลดรูปภาพได้: {ex.Message}"); + return Error($"ไม่สามารถอัปโหลดรูปภาพได้: {ex.Message}", StatusCodes.Status500InternalServerError); + } + } - var defaultRound = await _dutyTimeRepository.GetDefaultAsync(); - if (defaultRound == null) - { - return Error("ไม่พบรอบการลงเวลาทำงาน Default", StatusCodes.Status404NotFound); - } - - var effectiveDate = await _userDutyTimeRepository.GetLastEffectRound(profile.Id, currentDate); - var roundId = effectiveDate != null ? effectiveDate.DutyTimeId : Guid.Empty; - var userRound = await _dutyTimeRepository.GetByIdAsync(roundId); - - // TODO : รอดุึงรอบที่ผูกกับ user - var duty = userRound ?? defaultRound; - - // create check in object - if (data.CheckInId == null) - { - // validate duplicate check in - var currentCheckIn = await _userTimeStampRepository.GetTimestampByDateAsync(userId, currentDate); - - if (currentCheckIn != null) + var defaultRound = await _dutyTimeRepository.GetDefaultAsync(); + if (defaultRound == null) { - return Error(new Exception("ไม่สามารถลงเวลาได้ เนื่องจากมีการลงเวลาในวันนี้แล้ว!"), StatusCodes.Status400BadRequest); + await _checkInJobStatusRepository.UpdateToFailedAsync(taskId, "ไม่พบรอบการลงเวลาทำงาน Default"); + return Error("ไม่พบรอบการลงเวลาทำงาน Default", StatusCodes.Status404NotFound); } - var checkin = new UserTimeStamp + var effectiveDate = await _userDutyTimeRepository.GetLastEffectRound(profile.Id, currentDate); + var roundId = effectiveDate != null ? effectiveDate.DutyTimeId : Guid.Empty; + var userRound = await _dutyTimeRepository.GetByIdAsync(roundId); + + // TODO : รอดุึงรอบที่ผูกกับ user + var duty = userRound ?? defaultRound; + + // create check in object + if (data.CheckInId == null) { - KeycloakUserId = userId, - CheckInLat = data.Lat, - CheckInLon = data.Lon, - IsLocationCheckIn = data.IsLocation, - CheckInLocationName = data.LocationName, - CheckInPOI = data.POI, - CheckInRemark = data.Remark, - CheckInImageUrl = fileName, - CheckIn = currentDate, - Prefix = profile.Prefix, - FirstName = profile.FirstName, - LastName = profile.LastName, - CitizenId = profile.CitizenId, + // validate duplicate check in + var currentCheckIn = await _userTimeStampRepository.GetTimestampByDateAsync(userId, currentDate); - Root = profile.Root, - Child1 = profile.Child1, - Child2 = profile.Child2, - Child3 = profile.Child3, - Child4 = profile.Child4, + if (currentCheckIn != null) + { + return Error(new Exception("ไม่สามารถลงเวลาได้ เนื่องจากมีการลงเวลาในวันนี้แล้ว!"), StatusCodes.Status400BadRequest); + } - RootId = profile.RootId, - Child1Id = profile.Child1Id, - Child2Id = profile.Child2Id, - Child3Id = profile.Child3Id, - Child4Id = profile.Child4Id, - Gender = profile.Gender, + var checkin = new UserTimeStamp + { + KeycloakUserId = userId, + CheckInLat = data.Lat, + CheckInLon = data.Lon, + IsLocationCheckIn = data.IsLocation, + CheckInLocationName = data.LocationName, + CheckInPOI = data.POI, + CheckInRemark = data.Remark, + CheckInImageUrl = fileName, + CheckIn = currentDate, + Prefix = profile.Prefix, + FirstName = profile.FirstName, + LastName = profile.LastName, + CitizenId = profile.CitizenId, - ProfileId = profile.Id, - ProfileType = profile.ProfileType, + Root = profile.Root, + Child1 = profile.Child1, + Child2 = profile.Child2, + Child3 = profile.Child3, + Child4 = profile.Child4, - RootDnaId = profile.RootDnaId, - Child1DnaId = profile.Child1DnaId, - Child2DnaId = profile.Child2DnaId, - Child3DnaId = profile.Child3DnaId, - Child4DnaId = profile.Child4DnaId, - }; + RootId = profile.RootId, + Child1Id = profile.Child1Id, + Child2Id = profile.Child2Id, + Child3Id = profile.Child3Id, + Child4Id = profile.Child4Id, + Gender = profile.Gender, - var startTime = ""; - var endTime = ""; - if (!data.IsLocation && data.LocationName == "ไปประชุม / อบรม / สัมมนา") - { - //startTime = "09:30"; - startTime = "10:30"; - endTime = "12:00"; - } - else - { - startTime = duty.StartTimeMorning; - endTime = duty.EndTimeMorning; - } + ProfileId = profile.Id, + ProfileType = profile.ProfileType, - string checkInStatus = "NORMAL"; - var leaveReq = await _leaveRequestRepository.GetLeavePeriodAsync(userId, currentDate.Date); - if (leaveReq != null) - { - var leaveRange = leaveReq.LeaveRange == null ? "" : leaveReq.LeaveRange.ToUpper(); - if (leaveRange == "MORNING" || leaveRange == "ALL") - checkInStatus = "NORMAL"; + RootDnaId = profile.RootDnaId, + Child1DnaId = profile.Child1DnaId, + Child2DnaId = profile.Child2DnaId, + Child3DnaId = profile.Child3DnaId, + Child4DnaId = profile.Child4DnaId, + }; + + var startTime = ""; + var endTime = ""; + if (!data.IsLocation && data.LocationName == "ไปประชุม / อบรม / สัมมนา") + { + //startTime = "09:30"; + startTime = "10:30"; + endTime = "12:00"; + } + else + { + startTime = duty.StartTimeMorning; + endTime = duty.EndTimeMorning; + } + + string checkInStatus = "NORMAL"; + var leaveReq = await _leaveRequestRepository.GetLeavePeriodAsync(userId, currentDate.Date); + if (leaveReq != null) + { + var leaveRange = leaveReq.LeaveRange == null ? "" : leaveReq.LeaveRange.ToUpper(); + if (leaveRange == "MORNING" || leaveRange == "ALL") + checkInStatus = "NORMAL"; + else + { + checkInStatus = DateTime.Parse(currentDate.ToString("yyyy-MM-dd HH:mm")) > + DateTime.Parse($"{currentDate.ToString("yyyy-MM-dd")} {startTime}") ? + DateTime.Parse(currentDate.ToString("yyyy-MM-dd HH:mm")) > + DateTime.Parse($"{currentDate.ToString("yyyy-MM-dd")} {duty.EndTimeMorning}") ? + "ABSENT" : + "LATE" : + "NORMAL"; + } + } else { checkInStatus = DateTime.Parse(currentDate.ToString("yyyy-MM-dd HH:mm")) > @@ -1045,123 +1066,127 @@ namespace BMA.EHR.Leave.Service.Controllers "LATE" : "NORMAL"; } - } - else - { - checkInStatus = DateTime.Parse(currentDate.ToString("yyyy-MM-dd HH:mm")) > - DateTime.Parse($"{currentDate.ToString("yyyy-MM-dd")} {startTime}") ? - DateTime.Parse(currentDate.ToString("yyyy-MM-dd HH:mm")) > - DateTime.Parse($"{currentDate.ToString("yyyy-MM-dd")} {duty.EndTimeMorning}") ? - "ABSENT" : - "LATE" : - "NORMAL"; - } - + - // process - รอทำใน queue - var checkin_process = new ProcessUserTimeStamp - { - KeycloakUserId = userId, - CheckInLat = data.Lat, - CheckInLon = data.Lon, - IsLocationCheckIn = data.IsLocation, - CheckInLocationName = data.LocationName, - CheckInPOI = data.POI, - CheckInRemark = data.Remark, - CheckInImageUrl = fileName, - CheckIn = currentDate, - CheckInStatus = checkInStatus, - Prefix = profile.Prefix, - FirstName = profile.FirstName, - LastName = profile.LastName, - CitizenId = profile.CitizenId, - - Root = profile.Root, - Child1 = profile.Child1, - Child2 = profile.Child2, - Child3 = profile.Child3, - Child4 = profile.Child4, - - RootId = profile.RootId, - Child1Id = profile.Child1Id, - Child2Id = profile.Child2Id, - Child3Id = profile.Child3Id, - Child4Id = profile.Child4Id, - Gender = profile.Gender, - - ProfileId = profile.Id, - ProfileType = profile.ProfileType, - - - RootDnaId = profile.RootDnaId, - Child1DnaId = profile.Child1DnaId, - Child2DnaId = profile.Child2DnaId, - Child3DnaId = profile.Child3DnaId, - Child4DnaId = profile.Child4DnaId, - }; - - await _userTimeStampRepository.AddAsync(checkin); - await _processUserTimeStampRepository.AddAsync(checkin_process); - } - else - { - var checkout = await _userTimeStampRepository.GetByIdAsync(data.CheckInId.Value); - - var currentCheckInProcess = await _processUserTimeStampRepository.GetTimestampByDateAsync(userId, checkout.CheckIn.Date); - - var checkout_process = await _processUserTimeStampRepository.GetByIdAsync(currentCheckInProcess.Id); - - - if (checkout != null) - { - checkout.CheckOutLat = data.Lat; - checkout.CheckOutLon = data.Lon; - checkout.IsLocationCheckOut = data.IsLocation; - checkout.CheckOutLocationName = data.LocationName; - checkout.CheckOutPOI = data.POI; - checkout.CheckOutRemark = data.Remark; - checkout.CheckOutImageUrl = fileName; - checkout.CheckOut = currentDate; - - await _userTimeStampRepository.UpdateAsync(checkout); - } - else - { - return Error(new Exception(GlobalMessages.DataNotFound), StatusCodes.Status404NotFound); - } - - var endTime = ""; - var startTime = ""; - var endTimeMorning = ""; - if (!data.IsLocation && data.LocationName == "ไปประชุม / อบรม / สัมมนา") - { - startTime = "13:00"; - endTime = "14:30"; - endTimeMorning = "12:00"; - } - else - { - endTime = duty.EndTimeAfternoon; - startTime = duty.StartTimeAfternoon; - endTimeMorning = duty.EndTimeMorning; - } - - - - string checkOutStatus = "NORMAL"; - var leaveReq = await _leaveRequestRepository.GetLeavePeriodAsync(userId, currentDate.Date); - if (leaveReq != null) - { - var leaveRange = leaveReq.LeaveRangeEnd == null ? "" : leaveReq.LeaveRangeEnd.ToUpper(); - if (leaveRange == "AFTERNOON" || leaveRange == "ALL") + // process - รอทำใน queue + var checkin_process = new ProcessUserTimeStamp { - if(DateTime.Parse(currentDate.ToString("yyyy-MM-dd HH:mm")) < - DateTime.Parse($"{currentDate.ToString("yyyy-MM-dd")} {endTimeMorning}")) - checkOutStatus = "ABSENT"; - else - checkOutStatus = "NORMAL"; + KeycloakUserId = userId, + CheckInLat = data.Lat, + CheckInLon = data.Lon, + IsLocationCheckIn = data.IsLocation, + CheckInLocationName = data.LocationName, + CheckInPOI = data.POI, + CheckInRemark = data.Remark, + CheckInImageUrl = fileName, + CheckIn = currentDate, + CheckInStatus = checkInStatus, + Prefix = profile.Prefix, + FirstName = profile.FirstName, + LastName = profile.LastName, + CitizenId = profile.CitizenId, + + Root = profile.Root, + Child1 = profile.Child1, + Child2 = profile.Child2, + Child3 = profile.Child3, + Child4 = profile.Child4, + + RootId = profile.RootId, + Child1Id = profile.Child1Id, + Child2Id = profile.Child2Id, + Child3Id = profile.Child3Id, + Child4Id = profile.Child4Id, + Gender = profile.Gender, + + ProfileId = profile.Id, + ProfileType = profile.ProfileType, + + + RootDnaId = profile.RootDnaId, + Child1DnaId = profile.Child1DnaId, + Child2DnaId = profile.Child2DnaId, + Child3DnaId = profile.Child3DnaId, + Child4DnaId = profile.Child4DnaId, + }; + + await _userTimeStampRepository.AddAsync(checkin); + await _processUserTimeStampRepository.AddAsync(checkin_process); + } + else + { + var checkout = await _userTimeStampRepository.GetByIdAsync(data.CheckInId.Value); + + var currentCheckInProcess = await _processUserTimeStampRepository.GetTimestampByDateAsync(userId, checkout.CheckIn.Date); + + var checkout_process = await _processUserTimeStampRepository.GetByIdAsync(currentCheckInProcess.Id); + + + if (checkout != null) + { + checkout.CheckOutLat = data.Lat; + checkout.CheckOutLon = data.Lon; + checkout.IsLocationCheckOut = data.IsLocation; + checkout.CheckOutLocationName = data.LocationName; + checkout.CheckOutPOI = data.POI; + checkout.CheckOutRemark = data.Remark; + checkout.CheckOutImageUrl = fileName; + checkout.CheckOut = currentDate; + + await _userTimeStampRepository.UpdateAsync(checkout); } else + { + await _checkInJobStatusRepository.UpdateToFailedAsync(taskId, "ไม่พบข้อมูลการลงเวลาทำงาน"); + return Error(new Exception(GlobalMessages.DataNotFound), StatusCodes.Status404NotFound); + } + + var endTime = ""; + var startTime = ""; + var endTimeMorning = ""; + if (!data.IsLocation && data.LocationName == "ไปประชุม / อบรม / สัมมนา") + { + startTime = "13:00"; + endTime = "14:30"; + endTimeMorning = "12:00"; + } + else + { + endTime = duty.EndTimeAfternoon; + startTime = duty.StartTimeAfternoon; + endTimeMorning = duty.EndTimeMorning; + } + string checkOutStatus = "NORMAL"; + var leaveReq = await _leaveRequestRepository.GetLeavePeriodAsync(userId, currentDate.Date); + if (leaveReq != null) + { + var leaveRange = leaveReq.LeaveRangeEnd == null ? "" : leaveReq.LeaveRangeEnd.ToUpper(); + if (leaveRange == "AFTERNOON" || leaveRange == "ALL") + { + if(DateTime.Parse(currentDate.ToString("yyyy-MM-dd HH:mm")) < + DateTime.Parse($"{currentDate.ToString("yyyy-MM-dd")} {endTimeMorning}")) + checkOutStatus = "ABSENT"; + else + checkOutStatus = "NORMAL"; + } + else + { + // fix issue : SIT ระบบบันทึกเวลาปฏิบัติงาน>>ลงเวลาเข้า-ออกงาน (กรณีลงเวลาออกอีกวัน) #921 + checkOutStatus = DateTime.Parse(currentDate.ToString("yyyy-MM-dd HH:mm")) < + DateTime.Parse($"{currentDate.ToString("yyyy-MM-dd")} {duty.EndTimeAfternoon}") ? + // "ABSENT" : + checkout.CheckIn.Date < currentDate.Date ? "NORMAL" : + DateTime.Parse(currentDate.ToString("yyyy-MM-dd HH:mm")) >= + DateTime.Parse($"{currentDate.ToString("yyyy-MM-dd")} {endTime}") ? + "NORMAL" : + "ABSENT" : + DateTime.Parse(currentDate.ToString("yyyy-MM-dd HH:mm")) < + DateTime.Parse($"{currentDate.ToString("yyyy-MM-dd")} {endTimeMorning}") ? + "ABSENT" : + "NORMAL"; + } + } + else { // fix issue : SIT ระบบบันทึกเวลาปฏิบัติงาน>>ลงเวลาเข้า-ออกงาน (กรณีลงเวลาออกอีกวัน) #921 checkOutStatus = DateTime.Parse(currentDate.ToString("yyyy-MM-dd HH:mm")) < @@ -1176,71 +1201,55 @@ namespace BMA.EHR.Leave.Service.Controllers DateTime.Parse($"{currentDate.ToString("yyyy-MM-dd")} {endTimeMorning}") ? "ABSENT" : "NORMAL"; - } - } - else - { - // fix issue : SIT ระบบบันทึกเวลาปฏิบัติงาน>>ลงเวลาเข้า-ออกงาน (กรณีลงเวลาออกอีกวัน) #921 - checkOutStatus = DateTime.Parse(currentDate.ToString("yyyy-MM-dd HH:mm")) < - DateTime.Parse($"{currentDate.ToString("yyyy-MM-dd")} {duty.EndTimeAfternoon}") ? - // "ABSENT" : - checkout.CheckIn.Date < currentDate.Date ? "NORMAL" : - DateTime.Parse(currentDate.ToString("yyyy-MM-dd HH:mm")) >= - DateTime.Parse($"{currentDate.ToString("yyyy-MM-dd")} {endTime}") ? - "NORMAL" : - "ABSENT" : - DateTime.Parse(currentDate.ToString("yyyy-MM-dd HH:mm")) < - DateTime.Parse($"{currentDate.ToString("yyyy-MM-dd")} {endTimeMorning}") ? - "ABSENT" : - "NORMAL"; - } + } - if (checkout_process != null) - { - checkout_process.CheckOutLat = data.Lat; - checkout_process.CheckOutLon = data.Lon; - checkout_process.IsLocationCheckOut = data.IsLocation; - checkout_process.CheckOutLocationName = data.LocationName; - checkout_process.CheckOutPOI = data.POI; - checkout_process.CheckOutRemark = data.Remark; - checkout_process.CheckOutImageUrl = fileName; - checkout_process.CheckOut = currentDate; - checkout_process.CheckOutStatus = checkOutStatus; + if (checkout_process != null) + { + checkout_process.CheckOutLat = data.Lat; + checkout_process.CheckOutLon = data.Lon; + checkout_process.IsLocationCheckOut = data.IsLocation; + checkout_process.CheckOutLocationName = data.LocationName; + checkout_process.CheckOutPOI = data.POI; + checkout_process.CheckOutRemark = data.Remark; + checkout_process.CheckOutImageUrl = fileName; + checkout_process.CheckOut = currentDate; + checkout_process.CheckOutStatus = checkOutStatus; + + await _processUserTimeStampRepository.UpdateAsync(checkout_process); + } + else + { + await _checkInJobStatusRepository.UpdateToFailedAsync(taskId, "ไม่พบข้อมูลการประมวลผลเวลาทำงาน"); + return Error(new Exception(GlobalMessages.DataNotFound), StatusCodes.Status404NotFound); + } - await _processUserTimeStampRepository.UpdateAsync(checkout_process); - } - else - { - return Error(new Exception(GlobalMessages.DataNotFound), StatusCodes.Status404NotFound); } - } - - // อัปเดตสถานะเป็น COMPLETED - if (taskId != Guid.Empty) - { - var additionalData = JsonConvert.SerializeObject(new + // อัปเดตสถานะเป็น COMPLETED + if (taskId != Guid.Empty) { - CheckInType = data.CheckInId == null ? "check-in" : "check-out", - FileName = fileName, - ProcessedDate = currentDate - }); - await _checkInJobStatusRepository.UpdateToCompletedAsync(taskId, additionalData); - } + var additionalData = JsonConvert.SerializeObject(new + { + CheckInType = data.CheckInId == null ? "check-in" : "check-out", + FileName = fileName, + ProcessedDate = currentDate + }); + await _checkInJobStatusRepository.UpdateToCompletedAsync(taskId, additionalData); + } - var checkInType = data.CheckInId == null ? "check-in" : "check-out"; - return Success(new { user = $"{profile.FirstName} {profile.LastName}", date = currentDate, type = checkInType }); ; - } - catch (Exception ex) - { - // อัปเดตสถานะเป็น FAILED - if (taskId != Guid.Empty) - { - await _checkInJobStatusRepository.UpdateToFailedAsync(taskId, ex.Message); + var checkInType = data.CheckInId == null ? "check-in" : "check-out"; + return Success(new { user = $"{profile.FirstName} {profile.LastName}", date = currentDate, type = checkInType }); ; + } + catch (Exception ex) + { + // อัปเดตสถานะเป็น FAILED + if (taskId != Guid.Empty) + { + await _checkInJobStatusRepository.UpdateToFailedAsync(taskId, ex.Message); + } + throw; } - throw; } - } /// /// LV1_005 - ลงเวลาเข้า-ออกงาน (USER) diff --git a/BMA.EHR.Leave/Program.cs b/BMA.EHR.Leave/Program.cs index 928c8fc2..1b868cfb 100644 --- a/BMA.EHR.Leave/Program.cs +++ b/BMA.EHR.Leave/Program.cs @@ -96,7 +96,11 @@ builder.Services.AddPersistence(builder.Configuration); builder.Services.AddLeavePersistence(builder.Configuration); builder.Services.AddTransient(); -builder.Services.AddHttpClient(); +// Configure HttpClient with increased timeout for long-running operations (e.g., RabbitMQ Management API) +builder.Services.AddHttpClient(client => +{ + client.Timeout = TimeSpan.FromMinutes(10); // Set timeout to 10 minutes +}); builder.Services.AddControllers(options => { From 0a170fd259414cdd37aaeb2fd594277aadb4b3a7 Mon Sep 17 00:00:00 2001 From: Suphonchai Phoonsawat Date: Fri, 30 Jan 2026 10:09:37 +0700 Subject: [PATCH 082/183] Configure HttpClient to use a 10-minute timeout for long-running operations --- BMA.EHR.Leave/Program.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/BMA.EHR.Leave/Program.cs b/BMA.EHR.Leave/Program.cs index 1b868cfb..e25a59fe 100644 --- a/BMA.EHR.Leave/Program.cs +++ b/BMA.EHR.Leave/Program.cs @@ -97,9 +97,12 @@ builder.Services.AddLeavePersistence(builder.Configuration); builder.Services.AddTransient(); // Configure HttpClient with increased timeout for long-running operations (e.g., RabbitMQ Management API) -builder.Services.AddHttpClient(client => +builder.Services.AddHttpClient(); +builder.Services.AddTransient(sp => { - client.Timeout = TimeSpan.FromMinutes(10); // Set timeout to 10 minutes + var httpClient = sp.GetRequiredService().CreateClient(); + httpClient.Timeout = TimeSpan.FromMinutes(10); // Set timeout to 10 minutes + return httpClient; }); builder.Services.AddControllers(options => From 659e06a08d9f7e03ad50bfddf4c443e69fe02187 Mon Sep 17 00:00:00 2001 From: Suphonchai Phoonsawat Date: Fri, 30 Jan 2026 13:35:58 +0700 Subject: [PATCH 083/183] Add cancellation token support and extend timeout to 30 minutes for external API calls --- .../Repositories/GenericRepository.cs | 29 +++++-- .../TimeAttendants/DutyTimeRepository.cs | 7 +- .../TimeAttendants/UserDutyTimeRepository.cs | 7 +- .../TimeAttendants/UserTimeStampRepository.cs | 8 +- .../Repositories/UserProfileRepository.cs | 4 +- BMA.EHR.Leave/Controllers/LeaveController.cs | 86 ++++++++----------- dotnet_leave_test.js | 11 +-- 7 files changed, 76 insertions(+), 76 deletions(-) diff --git a/BMA.EHR.Application/Repositories/GenericRepository.cs b/BMA.EHR.Application/Repositories/GenericRepository.cs index fcc2aef0..805b88ba 100644 --- a/BMA.EHR.Application/Repositories/GenericRepository.cs +++ b/BMA.EHR.Application/Repositories/GenericRepository.cs @@ -53,15 +53,18 @@ namespace BMA.EHR.Application.Repositories #region " For Call External API " - protected async Task GetExternalAPIAsync(string apiPath, string accessToken, string apiKey) + protected async Task GetExternalAPIAsync(string apiPath, string accessToken, string apiKey, CancellationToken cancellationToken = default) { try { + // กำหนด timeout เป็น 30 นาที + using var timeoutCts = new CancellationTokenSource(TimeSpan.FromMinutes(30)); + using var combinedCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, timeoutCts.Token); using (var client = new HttpClient()) { client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken.Replace("Bearer ", "")); client.DefaultRequestHeaders.Add("api-key", apiKey); - var _res = await client.GetAsync(apiPath); + var _res = await client.GetAsync(apiPath,cancellationToken: combinedCts.Token); if (_res.IsSuccessStatusCode) { var _result = await _res.Content.ReadAsStringAsync(); @@ -77,10 +80,13 @@ namespace BMA.EHR.Application.Repositories } } - protected async Task SendExternalAPIAsync(HttpMethod method, string apiPath, string accessToken, object? body, string apiKey) + protected async Task SendExternalAPIAsync(HttpMethod method, string apiPath, string accessToken, object? body, string apiKey, CancellationToken cancellationToken = default) { try { + // กำหนด timeout เป็น 30 นาที + using var timeoutCts = new CancellationTokenSource(TimeSpan.FromMinutes(30)); + using var combinedCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, timeoutCts.Token); // สร้าง request message var request = new HttpRequestMessage(method, apiPath); @@ -92,7 +98,7 @@ namespace BMA.EHR.Application.Repositories { client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken.Replace("Bearer ", "")); client.DefaultRequestHeaders.Add("api-key", apiKey); - var _res = await client.SendAsync(request); + var _res = await client.SendAsync(request, combinedCts.Token); if (_res.IsSuccessStatusCode) { var _result = await _res.Content.ReadAsStringAsync(); @@ -109,11 +115,13 @@ namespace BMA.EHR.Application.Repositories } - protected async Task PostExternalAPIAsync(string apiPath, string accessToken, object? body, string apiKey) + protected async Task PostExternalAPIAsync(string apiPath, string accessToken, object? body, string apiKey, CancellationToken cancellationToken = default) { try { - + // กำหนด timeout เป็น 30 นาที + using var timeoutCts = new CancellationTokenSource(TimeSpan.FromMinutes(30)); + using var combinedCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, timeoutCts.Token); var json = JsonConvert.SerializeObject(body); var stringContent = new StringContent(json, Encoding.UTF8, "application/json"); //stringContent.Headers.ContentType = new MediaTypeHeaderValue("application/json"); @@ -122,7 +130,7 @@ namespace BMA.EHR.Application.Repositories { client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken.Replace("Bearer ", "")); client.DefaultRequestHeaders.Add("api-key", apiKey); - var _res = await client.PostAsync(apiPath, stringContent); + var _res = await client.PostAsync(apiPath, stringContent, combinedCts.Token); if (_res.IsSuccessStatusCode) { var _result = await _res.Content.ReadAsStringAsync(); @@ -138,10 +146,13 @@ namespace BMA.EHR.Application.Repositories } } - protected async Task PostExternalAPIBooleanAsync(string apiPath, string accessToken, object? body, string apiKey) + protected async Task PostExternalAPIBooleanAsync(string apiPath, string accessToken, object? body, string apiKey, CancellationToken cancellationToken = default) { try { + // กำหนด timeout เป็น 30 นาที + using var timeoutCts = new CancellationTokenSource(TimeSpan.FromMinutes(30)); + using var combinedCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, timeoutCts.Token); var json = JsonConvert.SerializeObject(body); var stringContent = new StringContent(json, UnicodeEncoding.UTF8, "application/json"); stringContent.Headers.ContentType = new MediaTypeHeaderValue("application/json"); @@ -150,7 +161,7 @@ namespace BMA.EHR.Application.Repositories { client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken.Replace("Bearer ", "")); client.DefaultRequestHeaders.Add("api-key", apiKey); - var _res = await client.PostAsync(apiPath, stringContent); + var _res = await client.PostAsync(apiPath, stringContent, combinedCts.Token); return _res.IsSuccessStatusCode; } } diff --git a/BMA.EHR.Application/Repositories/Leaves/TimeAttendants/DutyTimeRepository.cs b/BMA.EHR.Application/Repositories/Leaves/TimeAttendants/DutyTimeRepository.cs index 2db889ac..e96c35a9 100644 --- a/BMA.EHR.Application/Repositories/Leaves/TimeAttendants/DutyTimeRepository.cs +++ b/BMA.EHR.Application/Repositories/Leaves/TimeAttendants/DutyTimeRepository.cs @@ -61,9 +61,12 @@ namespace BMA.EHR.Application.Repositories.Leaves.TimeAttendants return await _dbContext.Set().Where(x => x.IsActive).ToListAsync(); } - public async Task GetDefaultAsync() + public async Task GetDefaultAsync(CancellationToken cancellationToken = default) { - return await _dbContext.Set().Where(x => x.IsDefault).FirstOrDefaultAsync(); + // กำหนด timeout เป็น 30 นาที + using var timeoutCts = new CancellationTokenSource(TimeSpan.FromMinutes(30)); + using var combinedCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, timeoutCts.Token); + return await _dbContext.Set().Where(x => x.IsDefault).FirstOrDefaultAsync(combinedCts.Token); } #endregion diff --git a/BMA.EHR.Application/Repositories/Leaves/TimeAttendants/UserDutyTimeRepository.cs b/BMA.EHR.Application/Repositories/Leaves/TimeAttendants/UserDutyTimeRepository.cs index 5ca823d9..b34bc4db 100644 --- a/BMA.EHR.Application/Repositories/Leaves/TimeAttendants/UserDutyTimeRepository.cs +++ b/BMA.EHR.Application/Repositories/Leaves/TimeAttendants/UserDutyTimeRepository.cs @@ -101,14 +101,17 @@ namespace BMA.EHR.Application.Repositories.Leaves.TimeAttendants return data; } - public async Task GetLastEffectRound(Guid profileId, DateTime? effectiveDate = null) + public async Task GetLastEffectRound(Guid profileId, DateTime? effectiveDate = null, CancellationToken cancellationToken = default) { + // กำหนด timeout เป็น 30 นาที + using var timeoutCts = new CancellationTokenSource(TimeSpan.FromMinutes(30)); + using var combinedCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, timeoutCts.Token); effectiveDate ??= DateTime.Now; var data = await _dbContext.Set() .Where(x => x.ProfileId == profileId) .Where(x => x.EffectiveDate.Value.Date <= effectiveDate.Value.Date) .OrderByDescending(x => x.EffectiveDate) - .FirstOrDefaultAsync(); + .FirstOrDefaultAsync(combinedCts.Token); return data; } diff --git a/BMA.EHR.Application/Repositories/Leaves/TimeAttendants/UserTimeStampRepository.cs b/BMA.EHR.Application/Repositories/Leaves/TimeAttendants/UserTimeStampRepository.cs index b7a766e9..2598dace 100644 --- a/BMA.EHR.Application/Repositories/Leaves/TimeAttendants/UserTimeStampRepository.cs +++ b/BMA.EHR.Application/Repositories/Leaves/TimeAttendants/UserTimeStampRepository.cs @@ -74,12 +74,16 @@ namespace BMA.EHR.Application.Repositories.Leaves.TimeAttendants return data; } - public async Task GetLastRecord(Guid keycloakId) + public async Task GetLastRecord(Guid keycloakId, CancellationToken cancellationToken = default) { + // กำหนด timeout เป็น 30 นาที + using var timeoutCts = new CancellationTokenSource(TimeSpan.FromMinutes(30)); + using var combinedCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, timeoutCts.Token); + var data = await _dbContext.Set() .Where(u => u.KeycloakUserId == keycloakId) .OrderByDescending(u => u.CheckIn) - .FirstOrDefaultAsync(); + .FirstOrDefaultAsync(combinedCts.Token); return data; } diff --git a/BMA.EHR.Application/Repositories/UserProfileRepository.cs b/BMA.EHR.Application/Repositories/UserProfileRepository.cs index ebf5113f..4e0e986f 100644 --- a/BMA.EHR.Application/Repositories/UserProfileRepository.cs +++ b/BMA.EHR.Application/Repositories/UserProfileRepository.cs @@ -186,14 +186,14 @@ namespace BMA.EHR.Application.Repositories } } - public async Task GetProfileByKeycloakIdNewAsync(Guid keycloakId, string? accessToken) + public async Task GetProfileByKeycloakIdNewAsync(Guid keycloakId, string? accessToken,CancellationToken cancellationToken = default) { try { var apiPath = $"{_configuration["API"]}/org/dotnet/by-keycloak/{keycloakId}"; var apiKey = _configuration["API_KEY"]; - var apiResult = await GetExternalAPIAsync(apiPath, accessToken ?? "", apiKey); + var apiResult = await GetExternalAPIAsync(apiPath, accessToken ?? "", apiKey, cancellationToken); if (apiResult != null) { var raw = JsonConvert.DeserializeObject(apiResult); diff --git a/BMA.EHR.Leave/Controllers/LeaveController.cs b/BMA.EHR.Leave/Controllers/LeaveController.cs index 062ceab1..9819ecd0 100644 --- a/BMA.EHR.Leave/Controllers/LeaveController.cs +++ b/BMA.EHR.Leave/Controllers/LeaveController.cs @@ -428,18 +428,26 @@ namespace BMA.EHR.Leave.Service.Controllers [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status401Unauthorized)] [ProducesResponseType(StatusCodes.Status500InternalServerError)] - public async Task> CheckTimeAsync() + public async Task> CheckTimeAsync(CancellationToken cancellationToken = default) { var userId = UserId == null ? Guid.Empty : Guid.Parse(UserId); - var data = await _userTimeStampRepository.GetLastRecord(userId); + + // Get user's last check-in record and profile in parallel + var dataTask = _userTimeStampRepository.GetLastRecord(userId); + var profileTask = _userProfileRepository.GetProfileByKeycloakIdNewAsync(userId, AccessToken); + var defaultRoundTask = _dutyTimeRepository.GetDefaultAsync(); + + await Task.WhenAll(dataTask, profileTask, defaultRoundTask); + + var data = await dataTask; + var profile = await profileTask; + var getDefaultRound = await defaultRoundTask; - var profile = await _userProfileRepository.GetProfileByKeycloakIdNewAsync(userId, AccessToken); if (profile == null) { throw new Exception(GlobalMessages.DataNotFound); } - var getDefaultRound = await _dutyTimeRepository.GetDefaultAsync(); if (getDefaultRound == null) { return Error("ไม่พบรอบลงเวลา Default", StatusCodes.Status404NotFound); @@ -451,65 +459,43 @@ namespace BMA.EHR.Leave.Service.Controllers var duty = userRound ?? getDefaultRound; - // TODO : รอดุึงรอบที่ผูกกับ user - //var duty = await _dutyTimeRepository.GetDefaultAsync(); - CheckInResultDto ret; - - if (data == null) - { - ret = new CheckInResultDto - { - StartTimeMorning = duty == null ? "00:00" : duty.StartTimeMorning, - EndTimeMorning = duty == null ? "00:00" : duty.EndTimeMorning, - StartTimeAfternoon = duty == null ? "00:00" : duty.StartTimeAfternoon, - EndTimeAfternoon = duty == null ? "00:00" : duty.EndTimeAfternoon, - Description = duty == null ? "-" : duty.Description, - CheckInTime = null, - CheckInId = null, - }; - } - else + // Determine check-in status and data + DateTime? checkInTime = null; + Guid? checkInId = null; + + if (data != null) { if (data.CheckOut != null) { // fix issue SIT ระบบบันทึกเวลาปฏิบัติงาน>>ลงเวลาเข้า-ออกงาน (กรณีลงเวลาออกอีกวัน) #921 - var cur_date = DateTime.Now.Date; - // ถ้า check-in + check-out ไปแล้ว - if (data.CheckIn.Date == cur_date && data.CheckOut.Value.Date == cur_date) + var currentDate = DateTime.Now.Date; + // ถ้า check-in + check-out ไปแล้วในวันเดียวกัน + if (data.CheckIn.Date == currentDate && data.CheckOut.Value.Date == currentDate) { return Error("คุณได้ทำการลงเวลาเข้าและออกเรียบร้อยแล้ว คุณจะสามารถลงเวลาได้อีกครั้งในวันถัดไป"); } - else - { - ret = new CheckInResultDto - { - StartTimeMorning = duty == null ? "00:00" : duty.StartTimeMorning, - EndTimeMorning = duty == null ? "00:00" : duty.EndTimeMorning, - StartTimeAfternoon = duty == null ? "00:00" : duty.StartTimeAfternoon, - EndTimeAfternoon = duty == null ? "00:00" : duty.EndTimeAfternoon, - Description = duty == null ? "-" : duty.Description, - CheckInTime = null, - CheckInId = null, - }; - } + // ถ้า check-out คนละวัน ให้แสดงว่ายังไม่ได้ check-in วันนี้ } else { - ret = new CheckInResultDto - { - StartTimeMorning = duty == null ? "00:00" : duty.StartTimeMorning, - EndTimeMorning = duty == null ? "00:00" : duty.EndTimeMorning, - StartTimeAfternoon = duty == null ? "00:00" : duty.StartTimeAfternoon, - EndTimeAfternoon = duty == null ? "00:00" : duty.EndTimeAfternoon, - Description = duty == null ? "-" : duty.Description, - CheckInTime = data.CheckIn, - CheckInId = data.Id, - }; + // มี check-in แต่ยังไม่ check-out + checkInTime = data.CheckIn; + checkInId = data.Id; } - - } + // Create response DTO (duty is never null here due to fallback logic) + var ret = new CheckInResultDto + { + StartTimeMorning = duty.StartTimeMorning, + EndTimeMorning = duty.EndTimeMorning, + StartTimeAfternoon = duty.StartTimeAfternoon, + EndTimeAfternoon = duty.EndTimeAfternoon, + Description = duty.Description, + CheckInTime = checkInTime, + CheckInId = checkInId, + }; + return Success(ret); } diff --git a/dotnet_leave_test.js b/dotnet_leave_test.js index 5add397d..658848ec 100644 --- a/dotnet_leave_test.js +++ b/dotnet_leave_test.js @@ -31,18 +31,11 @@ export default function () { // ตัวเลือก headers let headers = { "Content-Type": "application/json", - Authorization: - "Bearer eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJ4WTJWUi1FRnZ2TlBzTXMzOXU4b29WQldRTDZtUHdyTkpPaDNrb0pGVGdVIn0.eyJleHAiOjE3NzYyMTkxNjgsImlhdCI6MTc2ODQ0MzE2OCwianRpIjoiZDQxMmI5MWEtZmZhMi00N2JiLTliZDUtZDE5NTdmMDFjYzQyIiwiaXNzIjoiaHR0cHM6Ly9ocm1zLWlkLmJhbmdrb2suZ28udGgvcmVhbG1zL2hybXMiLCJhdWQiOiJhY2NvdW50Iiwic3ViIjoiYmFmYzU3OTUtYmVmYy00ZDNmLWE0NjEtMzUzM2MzOGE1ZmMxIiwidHlwIjoiQmVhcmVyIiwiYXpwIjoiZ2V0dG9rZW4tY2hlY2tpbiIsInNpZCI6IjBkNzdiY2Y5LTE4YWQtNGQyMS1hYjBjLTI4Y2ZiZjUyZGZiNCIsImFjciI6IjEiLCJhbGxvd2VkLW9yaWdpbnMiOlsiaHR0cHM6Ly9ocm1zLmJhbmdrb2suZ28udGgiLCJodHRwczovL2hybXMtY2hlY2tpbi5iYW5na29rLmdvLnRoIl0sInJlYWxtX2FjY2VzcyI6eyJyb2xlcyI6WyJTVVBFUl9BRE1JTiIsInN0b3JhZ2VfbWFuYWdlbWVudCIsIm9mZmxpbmVfYWNjZXNzIiwiU1RBRkYiLCJkZWZhdWx0LXJvbGVzLWhybXMiLCJ1bWFfYXV0aG9yaXphdGlvbiIsIkFETUlOIiwiVVNFUiJdfSwicmVzb3VyY2VfYWNjZXNzIjp7ImFjY291bnQiOnsicm9sZXMiOlsibWFuYWdlLWFjY291bnQiLCJtYW5hZ2UtYWNjb3VudC1saW5rcyIsInZpZXctcHJvZmlsZSJdfX0sInNjb3BlIjoiZW1haWwgb3BlbmlkIHByb2ZpbGUiLCJlbWFpbF92ZXJpZmllZCI6ZmFsc2UsInJvbGUiOlsiU1VQRVJfQURNSU4iLCJzdG9yYWdlX21hbmFnZW1lbnQiLCJvZmZsaW5lX2FjY2VzcyIsIlNUQUZGIiwiZGVmYXVsdC1yb2xlcy1ocm1zIiwidW1hX2F1dGhvcml6YXRpb24iLCJBRE1JTiIsIlVTRVIiXSwibmFtZSI6IuC4p-C4seC4meC5gOC4ieC4peC4tOC4oSDguInguLHguJXguKPguJfguK3guIciLCJwcmVmZXJyZWRfdXNlcm5hbWUiOiIzMTIwMjAwNDI0OTc1IiwiZ2l2ZW5fbmFtZSI6IuC4p-C4seC4meC5gOC4ieC4peC4tOC4oSIsImZhbWlseV9uYW1lIjoi4LiJ4Lix4LiV4Lij4LiX4Lit4LiHIn0.UhMn0NEkymPxMAcb4noZedHCSqXotCyD2RziBtLYHn5OhA9yk1915Rrt9iV4wVaebr74iZ2eZMpBwp8YVy8-3cPXSv9T3vzbXwFP7IeICPCDDf4bOPFEHP5FYow2s9v48qG81wnu01AG7_EL2-CQKh1sBVrCVUUlATlf-P4lT_lHeHOCKNXTmw4V0IWm96ec6pk-jFY3KH2JdRSWR7wq8g-KVxhLOxk_pF72kMwOpdvcr_99byg28zzj6QfeNYXLt61koHXnZppUqytt86mQQgfamv2FNVywCEzbRITUceu2rmJnwQE8ubeoCh4UOsYauUuSKd7RPqvvXxL_Vg__8Q", - //"Bearer eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJTT2wwWmFidm9rRzZET3pDZVBtT09Kek5haTdMUldkci1zV3lEYjRELTc0In0.eyJleHAiOjE3Njg4ODAzMjgsImlhdCI6MTc2ODc5MzkyOCwianRpIjoiMDYxODBlMWYtNTQzYy00MjU0LWFmN2QtYWI1NDA5NzFmNWY2IiwiaXNzIjoiaHR0cHM6Ly9ocm1zYmtrLWlkLmNhc2UtY29sbGVjdGlvbi5jb20vcmVhbG1zL2hybXMiLCJhdWQiOlsiYWNjb3VudCIsImdldHRva2VuIl0sInN1YiI6IjQzOWZhMzZkLTZiYzUtNGVmNS05NWFhLWVmMjllNjRkMmU5ZiIsInR5cCI6IkJlYXJlciIsImF6cCI6ImdldHRva2VuIiwic2lkIjoiZGI2YzUxNjItNzZhYS00MmVmLWI0ZDMtYThmOTk2N2NjZWM2IiwiYWNyIjoiMSIsImFsbG93ZWQtb3JpZ2lucyI6WyIqIl0sInJlYWxtX2FjY2VzcyI6eyJyb2xlcyI6WyJTVVBFUl9BRE1JTiIsIm9mZmxpbmVfYWNjZXNzIiwiU1RBRkYiLCJkZWZhdWx0LXJvbGVzLWhybXMiLCJ1bWFfYXV0aG9yaXphdGlvbiIsIkFETUlOIiwiVVNFUiJdfSwicmVzb3VyY2VfYWNjZXNzIjp7ImFjY291bnQiOnsicm9sZXMiOlsibWFuYWdlLWFjY291bnQiLCJtYW5hZ2UtYWNjb3VudC1saW5rcyIsInZpZXctcHJvZmlsZSJdfX0sInNjb3BlIjoiZW1haWwgb3BlbmlkIHByb2ZpbGUiLCJlbWFpbF92ZXJpZmllZCI6ZmFsc2UsInJvbGUiOlsiU1VQRVJfQURNSU4iLCJvZmZsaW5lX2FjY2VzcyIsIlNUQUZGIiwiZGVmYXVsdC1yb2xlcy1ocm1zIiwidW1hX2F1dGhvcml6YXRpb24iLCJBRE1JTiIsIlVTRVIiXSwibmFtZSI6IuC4p-C4seC4meC5gOC4ieC4peC4tOC4oSDguInguLHguJXguKPguJfguK3guIciLCJwcmVmZXJyZWRfdXNlcm5hbWUiOiIzMTIwMjAwNDI0OTc1IiwiZ2l2ZW5fbmFtZSI6IuC4p-C4seC4meC5gOC4ieC4peC4tOC4oSIsImZhbWlseV9uYW1lIjoi4LiJ4Lix4LiV4Lij4LiX4Lit4LiHIn0.fHdMzpHMD4JcbzYnUrfM473FSXka2Z4lz_S3HI2c-dPXfO5ATpijqsi12C6-ExE0RJRXUK671erMuyVXL6u2qj-FvdliBL3ubKy4J3jIT3svkcZxZL2ib16dRg375dITefvqd-J4vw6MR4bq8YAGPbqRIy6BQ2pdEiZgNiwUUihHAFwZlVER1lNbaqlbL6vk_L4k-g25DBVnDr756BFvrw7zEDbawkKZ31EZF5_DYk4RWej0wvWrGHQWLw-RyzYVSBB_AooqHkncHn_CwLBGC5juOEfFO4a2ThuKwoxYCstjtBj-zmjpHFs-Hh3CBTWJCGFcKst1Ey28StlKtNkLiw", + Authorization: "Bearer {Token}", }; // ส่ง GET request - let response = http.get( - //"https://bma-hrms.bangkok.go.th/api/v1/leave/fake-check-in", - //"https://hrmsbkk.case-collection.com/api/v1/org/dotnet/keycloak/439fa36d-6bc5-4ef5-95aa-ef29e64d2e9f", - "https://hrms.bangkok.go.th/api/v1/leave/check-time", - { headers: headers }, - ); + let response = http.get("https://{URL}", { headers: headers }); // ตรวจสอบการตอบสนอง check(response, { From c25bef067265c6e8d95dddff2edb1a1ced116d4b Mon Sep 17 00:00:00 2001 From: Suphonchai Phoonsawat Date: Tue, 3 Feb 2026 20:46:20 +0700 Subject: [PATCH 084/183] Update leave calculations to use DateAppoint and adjust leave limits based on government age #2266 --- .../LeaveRequests/LeaveBeginingRepository.cs | 10 ++++++---- .../Controllers/LeaveReportController.cs | 1 + .../Controllers/LeaveRequestController.cs | 18 ++++++++++++++---- 3 files changed, 21 insertions(+), 8 deletions(-) diff --git a/BMA.EHR.Application/Repositories/Leaves/LeaveRequests/LeaveBeginingRepository.cs b/BMA.EHR.Application/Repositories/Leaves/LeaveRequests/LeaveBeginingRepository.cs index dfb5897d..c2d08f70 100644 --- a/BMA.EHR.Application/Repositories/Leaves/LeaveRequests/LeaveBeginingRepository.cs +++ b/BMA.EHR.Application/Repositories/Leaves/LeaveRequests/LeaveBeginingRepository.cs @@ -108,7 +108,7 @@ namespace BMA.EHR.Application.Repositories.Leaves.LeaveRequests throw new Exception(GlobalMessages.DataNotFound); } - var govAge = (pf?.DateStart?.Date ?? DateTime.Now.Date).DiffDay(DateTime.Now.Date); + var govAge = (pf?.DateAppoint?.Date ?? DateTime.Now.Date).DiffDay(DateTime.Now.Date); var leaveType = await _dbContext.Set().FirstOrDefaultAsync(x => x.Id == typeId); @@ -174,7 +174,7 @@ namespace BMA.EHR.Application.Repositories.Leaves.LeaveRequests public async Task GetByYearAndTypeIdForUser(int year, Guid typeId, GetProfileByKeycloakIdDto? pf) { - var govAge = (pf?.DateStart?.Date ?? DateTime.Now.Date).DiffDay(DateTime.Now.Date); + var govAge = (pf?.DateAppoint?.Date ?? DateTime.Now.Date).DiffDay(DateTime.Now.Date); var leaveType = await _dbContext.Set().FirstOrDefaultAsync(x => x.Id == typeId); @@ -247,7 +247,7 @@ namespace BMA.EHR.Application.Repositories.Leaves.LeaveRequests return null; } - var govAge = (pf?.DateStart?.Date ?? DateTime.Now.Date).DiffDay(DateTime.Now.Date); + var govAge = (pf?.DateAppoint?.Date ?? DateTime.Now.Date).DiffDay(DateTime.Now.Date); var leaveType = await _dbContext.Set().FirstOrDefaultAsync(x => x.Id == typeId); @@ -337,7 +337,7 @@ namespace BMA.EHR.Application.Repositories.Leaves.LeaveRequests return null; } - var govAge = (pf?.DateStart?.Date ?? DateTime.Now.Date).DiffDay(DateTime.Now.Date); + var govAge = (pf?.DateAppoint?.Date ?? DateTime.Now.Date).DiffDay(DateTime.Now.Date); var leaveType = await _dbContext.Set().FirstOrDefaultAsync(x => x.Id == typeId); @@ -416,5 +416,7 @@ namespace BMA.EHR.Application.Repositories.Leaves.LeaveRequests public string LastName { get; set; } = string.Empty; public DateTime? DateStart { get; set; } = null; + + public DateTime? DateAppoint { get; set; } = null; } } diff --git a/BMA.EHR.Leave/Controllers/LeaveReportController.cs b/BMA.EHR.Leave/Controllers/LeaveReportController.cs index b89f22a4..2a113d94 100644 --- a/BMA.EHR.Leave/Controllers/LeaveReportController.cs +++ b/BMA.EHR.Leave/Controllers/LeaveReportController.cs @@ -1360,6 +1360,7 @@ namespace BMA.EHR.Leave.Service.Controllers FirstName = x.FirstName ?? "", LastName = x.LastName ?? "", DateStart = x.DateStart ?? x.DateAppoint, + DateAppoint = x.DateAppoint, }).Distinct().ToList(); var beginningData = await _leaveBeginningRepository.GetAllByYearAsync(year); diff --git a/BMA.EHR.Leave/Controllers/LeaveRequestController.cs b/BMA.EHR.Leave/Controllers/LeaveRequestController.cs index d0edb337..61ac7c4f 100644 --- a/BMA.EHR.Leave/Controllers/LeaveRequestController.cs +++ b/BMA.EHR.Leave/Controllers/LeaveRequestController.cs @@ -869,7 +869,7 @@ namespace BMA.EHR.Leave.Service.Controllers return Error(GlobalMessages.DataNotFound, StatusCodes.Status404NotFound); } - var govAge = (profile?.DateStart?.Date ?? DateTime.Now.Date).DiffDay(DateTime.Now.Date); + var govAge = (profile?.DateAppoint?.Date ?? DateTime.Now.Date).DiffDay(DateTime.Now.Date); var leaveType = await _leaveTypeRepository.GetByIdAsync(req.Type); if (leaveType == null) @@ -897,13 +897,23 @@ namespace BMA.EHR.Leave.Service.Controllers if (leaveType.Code.Trim().ToUpper() == "LV-005") { - leaveLimit = leaveData == null ? 0 : leaveData.LeaveDays; + if (govAge < 180) + leaveLimit = 0; + else + leaveLimit = leaveData == null ? 0 : leaveData.LeaveDays; } else leaveLimit = leaveType.Limit; var restOldDay = leaveData == null ? 0 : leaveData.LeaveDays - 10; var restCurrentDay = 10.0; + if (govAge < 180) + { + restOldDay = 0; + restCurrentDay = 0; + } + if(restOldDay < 0) + restOldDay = 0; var sumLeave = leaveData == null ? 0 : leaveData.LeaveDaysUsed; // var lastSalary = profile.ProfileSalary; @@ -970,7 +980,7 @@ namespace BMA.EHR.Leave.Service.Controllers // var profile = await _userProfileRepository.GetProfileByKeycloakIdAsync(userId, AccessToken); var profile = await _userProfileRepository.GetProfileByKeycloakIdNewAsync(userId, AccessToken); - var govAge = (profile?.DateStart?.Date ?? DateTime.Now.Date).DiffDay(DateTime.Now.Date); + var govAge = (profile?.DateAppoint?.Date ?? DateTime.Now.Date).DiffDay(DateTime.Now.Date); var thisYear = DateTime.Now.Year; var message = string.Empty; @@ -1527,7 +1537,7 @@ namespace BMA.EHR.Leave.Service.Controllers return Error(GlobalMessages.DataNotFound, StatusCodes.Status404NotFound); } - var govAge = (profile?.DateStart?.Date ?? DateTime.Now.Date).DiffDay(DateTime.Now.Date); + var govAge = (profile?.DateAppoint?.Date ?? DateTime.Now.Date).DiffDay(DateTime.Now.Date); var userCalendar = await _userCalendarRepository.GetExist(profile.Id); var category = userCalendar == null ? "NORMAL" : userCalendar.Calendar; From 1a0e712a1c6681dfc227d5fa892bc5067618e3f4 Mon Sep 17 00:00:00 2001 From: Suphonchai Phoonsawat Date: Tue, 3 Feb 2026 21:03:40 +0700 Subject: [PATCH 085/183] Update leave limit logic and add GovAge property to user leave profile DTO --- BMA.EHR.Leave/Controllers/LeaveRequestController.cs | 7 ++++++- .../DTOs/LeaveRequest/GetUserLeaveProfileResultDto.cs | 2 ++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/BMA.EHR.Leave/Controllers/LeaveRequestController.cs b/BMA.EHR.Leave/Controllers/LeaveRequestController.cs index 61ac7c4f..a961bfa2 100644 --- a/BMA.EHR.Leave/Controllers/LeaveRequestController.cs +++ b/BMA.EHR.Leave/Controllers/LeaveRequestController.cs @@ -900,7 +900,11 @@ namespace BMA.EHR.Leave.Service.Controllers if (govAge < 180) leaveLimit = 0; else - leaveLimit = leaveData == null ? 0 : leaveData.LeaveDays; + { + leaveLimit = leaveData == null ? + govAge < 180 ? 0 : 10 + : leaveData.LeaveDays; + } } else leaveLimit = leaveType.Limit; @@ -955,6 +959,7 @@ namespace BMA.EHR.Leave.Service.Controllers CurrentDistrict = profile.CurrentDistrict ?? "", CurrentProvince = profile.CurrentProvince ?? "", CurrentZipCode = profile.CurrentZipCode ?? "", + GovAge = govAge }; return Success(result); diff --git a/BMA.EHR.Leave/DTOs/LeaveRequest/GetUserLeaveProfileResultDto.cs b/BMA.EHR.Leave/DTOs/LeaveRequest/GetUserLeaveProfileResultDto.cs index 6ddc93cb..e64b5ac3 100644 --- a/BMA.EHR.Leave/DTOs/LeaveRequest/GetUserLeaveProfileResultDto.cs +++ b/BMA.EHR.Leave/DTOs/LeaveRequest/GetUserLeaveProfileResultDto.cs @@ -51,5 +51,7 @@ public string? CurrentProvince { get; set; } public string? CurrentZipCode { get; set; } + + public int GovAge { get; set; } = 0; } } From 19000b2e42dfe441c3f7c31c82d0166fe75ad032 Mon Sep 17 00:00:00 2001 From: Suphonchai Phoonsawat Date: Wed, 4 Feb 2026 10:18:44 +0700 Subject: [PATCH 086/183] Refactor check-out status logic to improve clarity and handle edge cases for same-day and next-day check-outs --- BMA.EHR.Leave/Controllers/LeaveController.cs | 102 ++++++++++++++----- 1 file changed, 78 insertions(+), 24 deletions(-) diff --git a/BMA.EHR.Leave/Controllers/LeaveController.cs b/BMA.EHR.Leave/Controllers/LeaveController.cs index 9819ecd0..3f8c9536 100644 --- a/BMA.EHR.Leave/Controllers/LeaveController.cs +++ b/BMA.EHR.Leave/Controllers/LeaveController.cs @@ -1158,35 +1158,89 @@ namespace BMA.EHR.Leave.Service.Controllers else { // fix issue : SIT ระบบบันทึกเวลาปฏิบัติงาน>>ลงเวลาเข้า-ออกงาน (กรณีลงเวลาออกอีกวัน) #921 - checkOutStatus = DateTime.Parse(currentDate.ToString("yyyy-MM-dd HH:mm")) < - DateTime.Parse($"{currentDate.ToString("yyyy-MM-dd")} {duty.EndTimeAfternoon}") ? - // "ABSENT" : - checkout.CheckIn.Date < currentDate.Date ? "NORMAL" : - DateTime.Parse(currentDate.ToString("yyyy-MM-dd HH:mm")) >= - DateTime.Parse($"{currentDate.ToString("yyyy-MM-dd")} {endTime}") ? - "NORMAL" : - "ABSENT" : - DateTime.Parse(currentDate.ToString("yyyy-MM-dd HH:mm")) < - DateTime.Parse($"{currentDate.ToString("yyyy-MM-dd")} {endTimeMorning}") ? - "ABSENT" : - "NORMAL"; + var currentDateTime = DateTime.Parse(currentDate.ToString("yyyy-MM-dd HH:mm")); + var dutyEndTimeAfternoon = DateTime.Parse($"{checkout.CheckIn.ToString("yyyy-MM-dd")} {endTime}"); + var dutyEndTimeMorning = DateTime.Parse($"{checkout.CheckIn.ToString("yyyy-MM-dd")} {endTimeMorning}"); + + + if(currentDateTime.Date > checkout.CheckIn.Date) + { + // ถ้า check-out เป็นวันถัดไป สถานะปกติเสมอ + checkOutStatus = "NORMAL"; + } + else + { + // ถ้า check-out เป็นวันเดียวกับ check-in + // ตรวจสอบเวลาว่าสิ้นสุดก่อนบ่ายหรือไม่ + if(currentDateTime < dutyEndTimeMorning) // ถ้าออกก่อนเวลาสิ้นสุดตอนเช้า ขาดราชการ + { + checkOutStatus = "ABSENT"; + } + else if(currentDateTime >= dutyEndTimeAfternoon) // ถ้าออกหลังเวลาสิ้นสุดตอนบ่าย ปกติ + { + checkOutStatus = "NORMAL"; + } + else + { + checkOutStatus = "ABSENT"; + } + } + // checkOutStatus = DateTime.Parse(currentDate.ToString("yyyy-MM-dd HH:mm")) < + // DateTime.Parse($"{checkout.CheckIn.ToString("yyyy-MM-dd")} {duty.EndTimeAfternoon}") ? + // // "ABSENT" : + // checkout.CheckIn.Date < currentDate.Date ? "NORMAL" : + // DateTime.Parse(currentDate.ToString("yyyy-MM-dd HH:mm")) >= + // DateTime.Parse($"{checkout.CheckIn.ToString("yyyy-MM-dd")} {endTime}") ? + // "NORMAL" : + // "ABSENT" : + // DateTime.Parse(currentDate.ToString("yyyy-MM-dd HH:mm")) < + // DateTime.Parse($"{currentDate.ToString("yyyy-MM-dd")} {endTimeMorning}") ? + // "ABSENT" : + // "NORMAL"; } } else { // fix issue : SIT ระบบบันทึกเวลาปฏิบัติงาน>>ลงเวลาเข้า-ออกงาน (กรณีลงเวลาออกอีกวัน) #921 - checkOutStatus = DateTime.Parse(currentDate.ToString("yyyy-MM-dd HH:mm")) < - DateTime.Parse($"{currentDate.ToString("yyyy-MM-dd")} {duty.EndTimeAfternoon}") ? - // "ABSENT" : - checkout.CheckIn.Date < currentDate.Date ? "NORMAL" : - DateTime.Parse(currentDate.ToString("yyyy-MM-dd HH:mm")) >= - DateTime.Parse($"{currentDate.ToString("yyyy-MM-dd")} {endTime}") ? - "NORMAL" : - "ABSENT" : - DateTime.Parse(currentDate.ToString("yyyy-MM-dd HH:mm")) < - DateTime.Parse($"{currentDate.ToString("yyyy-MM-dd")} {endTimeMorning}") ? - "ABSENT" : - "NORMAL"; + var currentDateTime = DateTime.Parse(currentDate.ToString("yyyy-MM-dd HH:mm")); + var dutyEndTimeAfternoon = DateTime.Parse($"{checkout.CheckIn.ToString("yyyy-MM-dd")} {endTime}"); + var dutyEndTimeMorning = DateTime.Parse($"{checkout.CheckIn.ToString("yyyy-MM-dd")} {endTimeMorning}"); + + + if(currentDateTime.Date > checkout.CheckIn.Date) + { + // ถ้า check-out เป็นวันถัดไป สถานะปกติเสมอ + checkOutStatus = "NORMAL"; + } + else + { + // ถ้า check-out เป็นวันเดียวกับ check-in + // ตรวจสอบเวลาว่าสิ้นสุดก่อนบ่ายหรือไม่ + if(currentDateTime < dutyEndTimeMorning) // ถ้าออกก่อนเวลาสิ้นสุดตอนเช้า ขาดราชการ + { + checkOutStatus = "ABSENT"; + } + else if(currentDateTime >= dutyEndTimeAfternoon) // ถ้าออกหลังเวลาสิ้นสุดตอนบ่าย ปกติ + { + checkOutStatus = "NORMAL"; + } + else + { + checkOutStatus = "ABSENT"; + } + } + // checkOutStatus = DateTime.Parse(currentDate.ToString("yyyy-MM-dd HH:mm")) < + // DateTime.Parse($"{currentDate.ToString("yyyy-MM-dd")} {duty.EndTimeAfternoon}") ? + // // "ABSENT" : + // checkout.CheckIn.Date < currentDate.Date ? "NORMAL" : + // DateTime.Parse(currentDate.ToString("yyyy-MM-dd HH:mm")) >= + // DateTime.Parse($"{currentDate.ToString("yyyy-MM-dd")} {endTime}") ? + // "NORMAL" : + // "ABSENT" : + // DateTime.Parse(currentDate.ToString("yyyy-MM-dd HH:mm")) < + // DateTime.Parse($"{currentDate.ToString("yyyy-MM-dd")} {endTimeMorning}") ? + // "ABSENT" : + // "NORMAL"; } if (checkout_process != null) From 7775ea85c3e483aa88e79250de6a4e3911ca3e37 Mon Sep 17 00:00:00 2001 From: Suphonchai Phoonsawat Date: Wed, 4 Feb 2026 10:32:44 +0700 Subject: [PATCH 087/183] Refactor error handling in LeaveController to return appropriate error responses instead of throwing exceptions --- BMA.EHR.Leave/Controllers/LeaveController.cs | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/BMA.EHR.Leave/Controllers/LeaveController.cs b/BMA.EHR.Leave/Controllers/LeaveController.cs index 3f8c9536..d90321ba 100644 --- a/BMA.EHR.Leave/Controllers/LeaveController.cs +++ b/BMA.EHR.Leave/Controllers/LeaveController.cs @@ -527,12 +527,13 @@ namespace BMA.EHR.Leave.Service.Controllers if (sameTypeJob != null) { - // ตรวจสอบว่างานที่มีอยู่ถูกสร้างเมื่อไหร่ ถ้าเกิน 2 นาทีให้สร้างใหม่ได้ - var timeDiff = (currentDate - sameTypeJob.CreatedDate).TotalMinutes; - if (timeDiff < 2) - { - return Error($"มีงาน {checkType} กำลังดำเนินการอยู่ กรุณารอสักครู่", StatusCodes.Status409Conflict); - } + + return Error($"มีงาน {checkType} กำลังดำเนินการอยู่", StatusCodes.Status500InternalServerError); + // var timeDiff = (currentDate - sameTypeJob.CreatedDate).TotalMinutes; + // if (timeDiff < 2) + // { + // return Error($"มีงาน {checkType} กำลังดำเนินการอยู่ กรุณารอสักครู่", StatusCodes.Status409Conflict); + // } } } @@ -623,7 +624,8 @@ namespace BMA.EHR.Leave.Service.Controllers // Ignore delete error } } - throw new Exception($"ไม่สามารถส่งงานไปยัง Queue ได้: {ex.Message}"); + return Error($"ไม่สามารถส่งงานไปยัง Queue ได้: {ex.Message}"); + //throw new Exception($"ไม่สามารถส่งงานไปยัง Queue ได้: {ex.Message}"); } finally { From 09a720807461ccb2bc866bb00806f3186204e9b9 Mon Sep 17 00:00:00 2001 From: Suphonchai Phoonsawat Date: Wed, 4 Feb 2026 10:49:13 +0700 Subject: [PATCH 088/183] Update govAge calculation to use DateStart instead of DateAppoint in Leave repositories and controller --- .../Repositories/InsigniaPeriodsRepository.cs | 176 +++++++++--------- .../LeaveRequests/LeaveBeginingRepository.cs | 8 +- .../Controllers/LeaveRequestController.cs | 6 +- 3 files changed, 95 insertions(+), 95 deletions(-) diff --git a/BMA.EHR.Application/Repositories/InsigniaPeriodsRepository.cs b/BMA.EHR.Application/Repositories/InsigniaPeriodsRepository.cs index ddee97cb..1c0741cb 100644 --- a/BMA.EHR.Application/Repositories/InsigniaPeriodsRepository.cs +++ b/BMA.EHR.Application/Repositories/InsigniaPeriodsRepository.cs @@ -280,8 +280,8 @@ namespace BMA.EHR.Application.Repositories FullName = $"{(p.Prefix ?? "")}{p.FirstName} {p.LastName}", Position = p.Position ?? "", Rank = p.PosLevel ?? "", - ProfileDateAppoint = p.DateAppoint.Value, - GovAge = p.DateAppoint.Value.CalculateGovAgeStr(0, 0), + ProfileDateAppoint = p.DateAppoint!.Value, + GovAge = p.DateStart!.Value.CalculateGovAgeStr(0, 0), PosNo = p.PosNo, PositionLevelId = p.PosLevelId, PositionLevelName = p.PosLevel, @@ -425,8 +425,8 @@ namespace BMA.EHR.Application.Repositories FullName = $"{(p.Prefix ?? "")}{p.FirstName} {p.LastName}", Position = p.Position ?? "", Rank = p.PosLevel ?? "", - ProfileDateAppoint = p.DateAppoint.Value, - GovAge = p.DateAppoint.Value.CalculateGovAgeStr(0, 0), + ProfileDateAppoint = p.DateAppoint!.Value, + GovAge = p.DateStart!.Value.CalculateGovAgeStr(0, 0), PosNo = p.PosNo, PositionLevelId = p.PosLevelId, PositionLevelName = p.PosLevel, @@ -571,8 +571,8 @@ namespace BMA.EHR.Application.Repositories FullName = $"{(p.Prefix ?? "")}{p.FirstName} {p.LastName}", Position = p.Position ?? "", Rank = p.PosLevel ?? "", - ProfileDateAppoint = p.DateAppoint.Value, - GovAge = p.DateAppoint.Value.CalculateGovAgeStr(0, 0), + ProfileDateAppoint = p.DateAppoint!.Value, + GovAge = p.DateStart!.Value.CalculateGovAgeStr(0, 0), PosNo = p.PosNo, PositionLevelId = p.PosLevel == null ? Guid.Parse("00000000-0000-0000-0000-000000000000") @@ -794,8 +794,8 @@ namespace BMA.EHR.Application.Repositories FullName = $"{(p.Prefix ?? "")}{p.FirstName} {p.LastName}", Position = p.Position ?? "", Rank = p.PosLevel ?? "", - ProfileDateAppoint = p.DateAppoint.Value, - GovAge = p.DateAppoint.Value.CalculateGovAgeStr(0, 0), + ProfileDateAppoint = p.DateAppoint!.Value, + GovAge = p.DateStart!.Value.CalculateGovAgeStr(0, 0), PosNo = p.PosNo, PositionLevelId = p.PosLevelId, PositionLevelName = p.PosLevel, @@ -939,8 +939,8 @@ namespace BMA.EHR.Application.Repositories FullName = $"{(p.Prefix ?? "")}{p.FirstName} {p.LastName}", Position = p.Position ?? "", Rank = p.PosLevel ?? "", - ProfileDateAppoint = p.DateAppoint.Value, - GovAge = p.DateAppoint.Value.CalculateGovAgeStr(0, 0), + ProfileDateAppoint = p.DateAppoint!.Value, + GovAge = p.DateStart!.Value.CalculateGovAgeStr(0, 0), PosNo = p.PosNo, PositionLevelId = p.PosLevelId, PositionLevelName = p.PosLevel, @@ -1081,8 +1081,8 @@ namespace BMA.EHR.Application.Repositories FullName = $"{(p.Prefix ?? "")}{p.FirstName} {p.LastName}", Position = p.Position ?? "", Rank = p.PosLevel ?? "", - ProfileDateAppoint = p.DateAppoint.Value, - GovAge = p.DateAppoint.Value.CalculateGovAgeStr(0, 0), + ProfileDateAppoint = p.DateAppoint!.Value, + GovAge = p.DateStart!.Value.CalculateGovAgeStr(0, 0), PosNo = p.PosNo, PositionLevelId = p.PosLevelId, PositionLevelName = p.PosLevel, @@ -1301,8 +1301,8 @@ namespace BMA.EHR.Application.Repositories FullName = $"{(p.Prefix ?? "")}{p.FirstName} {p.LastName}", Position = p.Position ?? "", Rank = p.PosLevel ?? "", - ProfileDateAppoint = p.DateAppoint.Value, - GovAge = p.DateAppoint.Value.CalculateGovAgeStr(0, 0), + ProfileDateAppoint = p.DateAppoint!.Value, + GovAge = p.DateStart!.Value.CalculateGovAgeStr(0, 0), PosNo = p.PosNo ?? "", PositionLevelId = p.PosLevelId, PositionLevelName = p.PosLevel, @@ -1443,8 +1443,8 @@ namespace BMA.EHR.Application.Repositories FullName = $"{(p.Prefix == null ? null : p.Prefix)}{p.FirstName} {p.LastName}", Position = p.Position == null ? "" : p.Position, Rank = p.PosLevel ?? "", - GovAge = p.DateAppoint.Value.CalculateGovAgeStr(0, 0), - ProfileDateAppoint = p.DateAppoint.Value, + GovAge = p.DateStart!.Value.CalculateGovAgeStr(0, 0), + ProfileDateAppoint = p.DateAppoint!.Value, LastInsignia = p.ProfileInsignia == null || p.ProfileInsignia.Count == 0 ? "" : p.ProfileInsignia .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) @@ -1591,8 +1591,8 @@ namespace BMA.EHR.Application.Repositories FullName = $"{(p.Prefix == null ? null : p.Prefix)}{p.FirstName} {p.LastName}", Position = p.Position == null ? "" : p.Position, Rank = p.PosLevel ?? "", - GovAge = p.DateAppoint.Value.CalculateGovAgeStr(0, 0), - ProfileDateAppoint = p.DateAppoint.Value, + GovAge = p.DateStart!.Value.CalculateGovAgeStr(0, 0), + ProfileDateAppoint = p.DateAppoint!.Value, LastInsignia = p.ProfileInsignia == null || p.ProfileInsignia.Count == 0 ? "" : p.ProfileInsignia .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) @@ -1738,8 +1738,8 @@ namespace BMA.EHR.Application.Repositories FullName = $"{(p.Prefix == null ? null : p.Prefix)}{p.FirstName} {p.LastName}", Position = p.Position == null ? "" : p.Position, Rank = p.PosLevel ?? "", - GovAge = p.DateAppoint.Value.CalculateGovAgeStr(0, 0), - ProfileDateAppoint = p.DateAppoint.Value, + GovAge = p.DateStart!.Value.CalculateGovAgeStr(0, 0), + ProfileDateAppoint = p.DateAppoint!.Value, LastInsignia = p.ProfileInsignia == null || p.ProfileInsignia.Count == 0 ? "" : p.ProfileInsignia .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) @@ -1992,8 +1992,8 @@ namespace BMA.EHR.Application.Repositories FullName = $"{(p.Prefix == null ? null : p.Prefix)}{p.FirstName} {p.LastName}", Position = p.Position == null ? null : p.Position, Rank = p.PosLevel ?? "", - GovAge = p.DateAppoint.Value.CalculateGovAgeStr(0, 0), - ProfileDateAppoint = p.DateAppoint.Value, + GovAge = p.DateStart!.Value.CalculateGovAgeStr(0, 0), + ProfileDateAppoint = p.DateAppoint!.Value, LastInsignia = p.ProfileInsignia == null || p.ProfileInsignia.Count == 0 ? "" : p.ProfileInsignia .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) @@ -2139,8 +2139,8 @@ namespace BMA.EHR.Application.Repositories FullName = $"{(p.Prefix == null ? null : p.Prefix)}{p.FirstName} {p.LastName}", Position = p.Position == null ? null : p.Position, Rank = p.PosLevel ?? "", - GovAge = p.DateAppoint.Value.CalculateGovAgeStr(0, 0), - ProfileDateAppoint = p.DateAppoint.Value, + GovAge = p.DateStart!.Value.CalculateGovAgeStr(0, 0), + ProfileDateAppoint = p.DateAppoint!.Value, LastInsignia = p.ProfileInsignia == null || p.ProfileInsignia.Count == 0 ? "" : p.ProfileInsignia .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) @@ -2358,8 +2358,8 @@ namespace BMA.EHR.Application.Repositories FullName = $"{(p.Prefix == null ? null : p.Prefix)}{p.FirstName} {p.LastName}", Position = p.Position == null ? null : p.Position, Rank = p.PosLevel ?? "", - GovAge = p.DateAppoint.Value.CalculateGovAgeStr(0, 0), - ProfileDateAppoint = p.DateAppoint.Value, + GovAge = p.DateStart!.Value.CalculateGovAgeStr(0, 0), + ProfileDateAppoint = p.DateAppoint!.Value, LastInsignia = p.ProfileInsignia == null || p.ProfileInsignia.Count == 0 ? "" : p.ProfileInsignia .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) @@ -2510,8 +2510,8 @@ namespace BMA.EHR.Application.Repositories FullName = $"{(p.Prefix == null ? null : p.Prefix)}{p.FirstName} {p.LastName}", Position = p.Position == null ? null : p.Position, Rank = p.PosLevel ?? "", - GovAge = p.DateAppoint.Value.CalculateGovAgeStr(0, 0), - ProfileDateAppoint = p.DateAppoint.Value, + GovAge = p.DateStart!.Value.CalculateGovAgeStr(0, 0), + ProfileDateAppoint = p.DateAppoint!.Value, LastInsignia = p.ProfileInsignia == null || p.ProfileInsignia.Count == 0 ? "" : p.ProfileInsignia .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) @@ -2730,8 +2730,8 @@ namespace BMA.EHR.Application.Repositories FullName = $"{(p.Prefix == null ? null : p.Prefix)}{p.FirstName} {p.LastName}", Position = p.Position == null ? null : p.Position, Rank = p.PosLevel ?? "", - GovAge = p.DateAppoint.Value.CalculateGovAgeStr(0, 0), - ProfileDateAppoint = p.DateAppoint.Value, + GovAge = p.DateStart!.Value.CalculateGovAgeStr(0, 0), + ProfileDateAppoint = p.DateAppoint!.Value, LastInsignia = p.ProfileInsignia == null || p.ProfileInsignia.Count == 0 ? "" : p.ProfileInsignia .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) @@ -2883,8 +2883,8 @@ namespace BMA.EHR.Application.Repositories FullName = $"{(p.Prefix == null ? null : p.Prefix)}{p.FirstName} {p.LastName}", Position = p.Position == null ? null : p.Position, Rank = p.PosLevel ?? "", - GovAge = p.DateAppoint.Value.CalculateGovAgeStr(0, 0), - ProfileDateAppoint = p.DateAppoint.Value, + GovAge = p.DateStart!.Value.CalculateGovAgeStr(0, 0), + ProfileDateAppoint = p.DateAppoint!.Value, LastInsignia = p.ProfileInsignia == null || p.ProfileInsignia.Count == 0 ? "" : p.ProfileInsignia .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) @@ -3045,8 +3045,8 @@ namespace BMA.EHR.Application.Repositories FullName = $"{(p.Prefix == null ? null : p.Prefix)}{p.FirstName} {p.LastName}", Position = p.Position == null ? null : p.Position, Rank = p.PosLevel ?? "", - GovAge = p.DateAppoint.Value.CalculateGovAgeStr(0, 0), - ProfileDateAppoint = p.DateAppoint.Value, + GovAge = p.DateStart!.Value.CalculateGovAgeStr(0, 0), + ProfileDateAppoint = p.DateAppoint!.Value, LastInsignia = p.ProfileInsignia == null || p.ProfileInsignia.Count == 0 ? "" : p.ProfileInsignia .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) @@ -3292,8 +3292,8 @@ namespace BMA.EHR.Application.Repositories FullName = $"{(p.Prefix == null ? null : p.Prefix)}{p.FirstName} {p.LastName}", Position = p.Position == null ? null : p.Position, Rank = p.PosLevel ?? "", - GovAge = p.DateAppoint.Value.CalculateGovAgeStr(0, 0), - ProfileDateAppoint = p.DateAppoint.Value, + GovAge = p.DateStart!.Value.CalculateGovAgeStr(0, 0), + ProfileDateAppoint = p.DateAppoint!.Value, LastInsignia = p.ProfileInsignia == null || p.ProfileInsignia.Count == 0 ? "" : p.ProfileInsignia .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) @@ -3498,8 +3498,8 @@ namespace BMA.EHR.Application.Repositories FullName = $"{(p.Prefix == null ? null : p.Prefix)}{p.FirstName} {p.LastName}", Position = p.Position == null ? null : p.Position, Rank = p.PosLevel ?? "", - GovAge = p.DateAppoint.Value.CalculateGovAgeStr(0, 0), - ProfileDateAppoint = p.DateAppoint.Value, + GovAge = p.DateStart!.Value.CalculateGovAgeStr(0, 0), + ProfileDateAppoint = p.DateAppoint!.Value, LastInsignia = p.ProfileInsignia == null || p.ProfileInsignia.Count == 0 ? "" : p.ProfileInsignia .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) @@ -3657,8 +3657,8 @@ namespace BMA.EHR.Application.Repositories FullName = $"{(p.Prefix == null ? null : p.Prefix)}{p.FirstName} {p.LastName}", Position = p.Position == null ? null : p.Position, Rank = p.PosLevel ?? "", - GovAge = p.DateAppoint.Value.CalculateGovAgeStr(0, 0), - ProfileDateAppoint = p.DateAppoint.Value, + GovAge = p.DateStart!.Value.CalculateGovAgeStr(0, 0), + ProfileDateAppoint = p.DateAppoint!.Value, LastInsignia = p.ProfileInsignia == null || p.ProfileInsignia.Count == 0 ? "" : p.ProfileInsignia .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) @@ -3819,8 +3819,8 @@ namespace BMA.EHR.Application.Repositories FullName = $"{(p.Prefix == null ? null : p.Prefix)}{p.FirstName} {p.LastName}", Position = p.Position == null ? null : p.Position, Rank = p.PosLevel ?? "", - GovAge = p.DateAppoint.Value.CalculateGovAgeStr(0, 0), - ProfileDateAppoint = p.DateAppoint.Value, + GovAge = p.DateStart!.Value.CalculateGovAgeStr(0, 0), + ProfileDateAppoint = p.DateAppoint!.Value, LastInsignia = p.ProfileInsignia == null || p.ProfileInsignia.Count == 0 ? "" : p.ProfileInsignia .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) @@ -4065,8 +4065,8 @@ namespace BMA.EHR.Application.Repositories FullName = $"{(p.Prefix == null ? null : p.Prefix)}{p.FirstName} {p.LastName}", Position = p.Position == null ? null : p.Position, Rank = p.PosLevel ?? "", - GovAge = p.DateAppoint.Value.CalculateGovAgeStr(0, 0), - ProfileDateAppoint = p.DateAppoint.Value, + GovAge = p.DateStart!.Value.CalculateGovAgeStr(0, 0), + ProfileDateAppoint = p.DateAppoint!.Value, LastInsignia = p.ProfileInsignia == null || p.ProfileInsignia.Count == 0 ? "" : p.ProfileInsignia .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) @@ -4207,8 +4207,8 @@ namespace BMA.EHR.Application.Repositories FullName = $"{(p.Prefix == null ? null : p.Prefix)}{p.FirstName} {p.LastName}", Position = p.Position == null ? null : p.Position, Rank = p.PosLevel ?? "", - GovAge = p.DateAppoint.Value.CalculateGovAgeStr(0, 0), - ProfileDateAppoint = p.DateAppoint.Value, + GovAge = p.DateStart!.Value.CalculateGovAgeStr(0, 0), + ProfileDateAppoint = p.DateAppoint!.Value, LastInsignia = p.ProfileInsignia == null || p.ProfileInsignia.Count == 0 ? "" : p.ProfileInsignia .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) @@ -4442,8 +4442,8 @@ namespace BMA.EHR.Application.Repositories FullName = $"{(p.Prefix == null ? null : p.Prefix)}{p.FirstName} {p.LastName}", Position = p.Position == null ? null : p.Position, Rank = p.PosLevel ?? "", - GovAge = p.DateAppoint.Value.CalculateGovAgeStr(0, 0), - ProfileDateAppoint = p.DateAppoint.Value, + GovAge = p.DateStart!.Value.CalculateGovAgeStr(0, 0), + ProfileDateAppoint = p.DateAppoint!.Value, LastInsignia = p.ProfileInsignia == null || p.ProfileInsignia.Count == 0 ? "" : p.ProfileInsignia .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) @@ -4602,8 +4602,8 @@ namespace BMA.EHR.Application.Repositories FullName = $"{(p.Prefix == null ? null : p.Prefix)}{p.FirstName} {p.LastName}", Position = p.Position == null ? null : p.Position, Rank = p.PosLevel ?? "", - GovAge = p.DateAppoint.Value.CalculateGovAgeStr(0, 0), - ProfileDateAppoint = p.DateAppoint.Value, + GovAge = p.DateStart!.Value.CalculateGovAgeStr(0, 0), + ProfileDateAppoint = p.DateAppoint!.Value, LastInsignia = p.ProfileInsignia == null || p.ProfileInsignia.Count == 0 ? "" : p.ProfileInsignia .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) @@ -4764,8 +4764,8 @@ namespace BMA.EHR.Application.Repositories FullName = $"{(p.Prefix == null ? null : p.Prefix)}{p.FirstName} {p.LastName}", Position = p.Position == null ? null : p.Position, Rank = p.PosLevel ?? "", - GovAge = p.DateAppoint.Value.CalculateGovAgeStr(0, 0), - ProfileDateAppoint = p.DateAppoint.Value, + GovAge = p.DateStart!.Value.CalculateGovAgeStr(0, 0), + ProfileDateAppoint = p.DateAppoint!.Value, LastInsignia = p.ProfileInsignia == null || p.ProfileInsignia.Count == 0 ? "" : p.ProfileInsignia .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) @@ -5008,8 +5008,8 @@ namespace BMA.EHR.Application.Repositories FullName = $"{(p.Prefix == null ? null : p.Prefix)}{p.FirstName} {p.LastName}", Position = p.Position == null ? null : p.Position, Rank = p.PosLevel ?? "", - GovAge = p.DateAppoint.Value.CalculateGovAgeStr(0, 0), - ProfileDateAppoint = p.DateAppoint.Value, + GovAge = p.DateStart!.Value.CalculateGovAgeStr(0, 0), + ProfileDateAppoint = p.DateAppoint!.Value, LastInsignia = p.ProfileInsignia == null || p.ProfileInsignia.Count == 0 ? "" : p.ProfileInsignia .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) @@ -5176,8 +5176,8 @@ namespace BMA.EHR.Application.Repositories FullName = $"{(p.Prefix == null ? null : p.Prefix)}{p.FirstName} {p.LastName}", Position = p.Position == null ? null : p.Position, Rank = p.PosLevel ?? "", - GovAge = p.DateAppoint.Value.CalculateGovAgeStr(0, 0), - ProfileDateAppoint = p.DateAppoint.Value, + GovAge = p.DateStart!.Value.CalculateGovAgeStr(0, 0), + ProfileDateAppoint = p.DateAppoint!.Value, LastInsignia = p.ProfileInsignia == null || p.ProfileInsignia.Count == 0 ? "" : p.ProfileInsignia .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) @@ -5344,8 +5344,8 @@ namespace BMA.EHR.Application.Repositories FullName = $"{(p.Prefix == null ? null : p.Prefix)}{p.FirstName} {p.LastName}", Position = p.Position == null ? null : p.Position, Rank = p.PosLevel ?? "", - GovAge = p.DateAppoint.Value.CalculateGovAgeStr(0, 0), - ProfileDateAppoint = p.DateAppoint.Value, + GovAge = p.DateStart!.Value.CalculateGovAgeStr(0, 0), + ProfileDateAppoint = p.DateAppoint!.Value, LastInsignia = p.ProfileInsignia == null || p.ProfileInsignia.Count == 0 ? "" : p.ProfileInsignia .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) @@ -5594,8 +5594,8 @@ namespace BMA.EHR.Application.Repositories FullName = $"{(p.Prefix == null ? null : p.Prefix)}{p.FirstName} {p.LastName}", Position = p.Position == null ? null : p.Position, Rank = p.PosLevel ?? "", - GovAge = p.DateAppoint.Value.CalculateGovAgeStr(0, 0), - ProfileDateAppoint = p.DateAppoint.Value, + GovAge = p.DateStart!.Value.CalculateGovAgeStr(0, 0), + ProfileDateAppoint = p.DateAppoint!.Value, LastInsignia = p.ProfileInsignia == null || p.ProfileInsignia.Count == 0 ? "" : p.ProfileInsignia .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) @@ -5762,8 +5762,8 @@ namespace BMA.EHR.Application.Repositories FullName = $"{(p.Prefix == null ? null : p.Prefix)}{p.FirstName} {p.LastName}", Position = p.Position == null ? null : p.Position, Rank = p.PosLevel ?? "", - GovAge = p.DateAppoint.Value.CalculateGovAgeStr(0, 0), - ProfileDateAppoint = p.DateAppoint.Value, + GovAge = p.DateStart!.Value.CalculateGovAgeStr(0, 0), + ProfileDateAppoint = p.DateAppoint!.Value, LastInsignia = p.ProfileInsignia == null || p.ProfileInsignia.Count == 0 ? "" : p.ProfileInsignia .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) @@ -5930,8 +5930,8 @@ namespace BMA.EHR.Application.Repositories FullName = $"{(p.Prefix == null ? null : p.Prefix)}{p.FirstName} {p.LastName}", Position = p.Position == null ? null : p.Position, Rank = p.PosLevel ?? "", - GovAge = p.DateAppoint.Value.CalculateGovAgeStr(0, 0), - ProfileDateAppoint = p.DateAppoint.Value, + GovAge = p.DateStart!.Value.CalculateGovAgeStr(0, 0), + ProfileDateAppoint = p.DateAppoint!.Value, LastInsignia = p.ProfileInsignia == null || p.ProfileInsignia.Count == 0 ? "" : p.ProfileInsignia .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) @@ -6176,8 +6176,8 @@ namespace BMA.EHR.Application.Repositories FullName = $"{(p.Prefix == null ? null : p.Prefix)}{p.FirstName} {p.LastName}", Position = p.Position == null ? "" : p.Position, Rank = p.PosLevel ?? "", - GovAge = p.DateAppoint.Value.CalculateGovAgeStr(0, 0), - ProfileDateAppoint = p.DateAppoint.Value, + GovAge = p.DateStart!.Value.CalculateGovAgeStr(0, 0), + ProfileDateAppoint = p.DateAppoint!.Value, LastInsignia = p.ProfileInsignia == null || p.ProfileInsignia.Count == 0 ? "" : p.ProfileInsignia .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) @@ -6334,8 +6334,8 @@ namespace BMA.EHR.Application.Repositories FullName = $"{(p.Prefix == null ? null : p.Prefix)}{p.FirstName} {p.LastName}", Position = p.Position == null ? "" : p.Position, Rank = p.PosLevel ?? "", - GovAge = p.DateAppoint.Value.CalculateGovAgeStr(0, 0), - ProfileDateAppoint = p.DateAppoint.Value, + GovAge = p.DateStart!.Value.CalculateGovAgeStr(0, 0), + ProfileDateAppoint = p.DateAppoint!.Value, LastInsignia = p.ProfileInsignia == null || p.ProfileInsignia.Count == 0 ? "" : p.ProfileInsignia .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) @@ -6563,8 +6563,8 @@ namespace BMA.EHR.Application.Repositories FullName = $"{(p.Prefix == null ? null : p.Prefix)}{p.FirstName} {p.LastName}", Position = p.Position == null ? "" : p.Position, Rank = p.PosLevel ?? "", - GovAge = p.DateAppoint.Value.CalculateGovAgeStr(0, 0), - ProfileDateAppoint = p.DateAppoint.Value, + GovAge = p.DateStart!.Value.CalculateGovAgeStr(0, 0), + ProfileDateAppoint = p.DateAppoint!.Value, LastInsignia = p.ProfileInsignia == null || p.ProfileInsignia.Count == 0 ? "" : p.ProfileInsignia .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) @@ -6725,8 +6725,8 @@ namespace BMA.EHR.Application.Repositories FullName = $"{(p.Prefix == null ? null : p.Prefix)}{p.FirstName} {p.LastName}", Position = p.Position == null ? "" : p.Position, Rank = p.PosLevel ?? "", - GovAge = p.DateAppoint.Value.CalculateGovAgeStr(0, 0), - ProfileDateAppoint = p.DateAppoint.Value, + GovAge = p.DateStart!.Value.CalculateGovAgeStr(0, 0), + ProfileDateAppoint = p.DateAppoint!.Value, LastInsignia = p.ProfileInsignia == null || p.ProfileInsignia.Count == 0 ? "" : p.ProfileInsignia .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) @@ -6888,8 +6888,8 @@ namespace BMA.EHR.Application.Repositories FullName = $"{(p.Prefix == null ? null : p.Prefix)}{p.FirstName} {p.LastName}", Position = p.Position == null ? null : p.Position, Rank = p.PosLevel ?? "", - GovAge = p.DateAppoint.Value.CalculateGovAgeStr(0, 0), - ProfileDateAppoint = p.DateAppoint.Value, + GovAge = p.DateStart!.Value.CalculateGovAgeStr(0, 0), + ProfileDateAppoint = p.DateAppoint!.Value, LastInsignia = p.ProfileInsignia == null || p.ProfileInsignia.Count == 0 ? "" : p.ProfileInsignia .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) @@ -7130,8 +7130,8 @@ namespace BMA.EHR.Application.Repositories FullName = $"{(p.Prefix == null ? null : p.Prefix)}{p.FirstName} {p.LastName}", Position = p.Position == null ? null : p.Position, Rank = p.PosLevel ?? "", - GovAge = p.DateAppoint.Value.CalculateGovAgeStr(0, 0), - ProfileDateAppoint = p.DateAppoint.Value, + GovAge = p.DateStart!.Value.CalculateGovAgeStr(0, 0), + ProfileDateAppoint = p.DateAppoint!.Value, LastInsignia = p.ProfileInsignia == null || p.ProfileInsignia.Count == 0 ? "" : p.ProfileInsignia .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) @@ -7292,8 +7292,8 @@ namespace BMA.EHR.Application.Repositories FullName = $"{(p.Prefix == null ? null : p.Prefix)}{p.FirstName} {p.LastName}", Position = p.Position == null ? null : p.Position, Rank = p.PosLevel ?? "", - GovAge = p.DateAppoint.Value.CalculateGovAgeStr(0, 0), - ProfileDateAppoint = p.DateAppoint.Value, + GovAge = p.DateStart!.Value.CalculateGovAgeStr(0, 0), + ProfileDateAppoint = p.DateAppoint!.Value, LastInsignia = p.ProfileInsignia == null || p.ProfileInsignia.Count == 0 ? "" : p.ProfileInsignia .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) @@ -7454,8 +7454,8 @@ namespace BMA.EHR.Application.Repositories FullName = $"{(p.Prefix == null ? null : p.Prefix)}{p.FirstName} {p.LastName}", Position = p.Position == null ? null : p.Position, Rank = p.PosLevel ?? "", - GovAge = p.DateAppoint.Value.CalculateGovAgeStr(0, 0), - ProfileDateAppoint = p.DateAppoint.Value, + GovAge = p.DateStart!.Value.CalculateGovAgeStr(0, 0), + ProfileDateAppoint = p.DateAppoint!.Value, LastInsignia = p.ProfileInsignia == null || p.ProfileInsignia.Count == 0 ? "" : p.ProfileInsignia .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) @@ -7667,8 +7667,8 @@ namespace BMA.EHR.Application.Repositories FullName = $"{(p.Prefix == null ? null : p.Prefix)}{p.FirstName} {p.LastName}", Position = p.Position == null ? null : p.Position, Rank = p.PosLevel ?? "", - GovAge = p.DateAppoint.Value.CalculateGovAgeStr(0, 0), - ProfileDateAppoint = p.DateAppoint.Value, + GovAge = p.DateStart!.Value.CalculateGovAgeStr(0, 0), + ProfileDateAppoint = p.DateAppoint!.Value, LastInsignia = p.ProfileInsignia == null || p.ProfileInsignia.Count == 0 ? "" : p.ProfileInsignia .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) @@ -7835,8 +7835,8 @@ namespace BMA.EHR.Application.Repositories FullName = $"{(p.Prefix == null ? null : p.Prefix)}{p.FirstName} {p.LastName}", Position = p.Position == null ? null : p.Position, Rank = p.PosLevel ?? "", - GovAge = p.DateAppoint.Value.CalculateGovAgeStr(0, 0), - ProfileDateAppoint = p.DateAppoint.Value, + GovAge = p.DateStart!.Value.CalculateGovAgeStr(0, 0), + ProfileDateAppoint = p.DateAppoint!.Value, LastInsignia = p.ProfileInsignia == null || p.ProfileInsignia.Count == 0 ? "" : p.ProfileInsignia .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) @@ -8003,8 +8003,8 @@ namespace BMA.EHR.Application.Repositories FullName = $"{(p.Prefix == null ? null : p.Prefix)}{p.FirstName} {p.LastName}", Position = p.Position == null ? null : p.Position, Rank = p.PosLevel ?? "", - GovAge = p.DateAppoint.Value.CalculateGovAgeStr(0, 0), - ProfileDateAppoint = p.DateAppoint.Value, + GovAge = p.DateStart!.Value.CalculateGovAgeStr(0, 0), + ProfileDateAppoint = p.DateAppoint!.Value, LastInsignia = p.ProfileInsignia == null || p.ProfileInsignia.Count == 0 ? "" : p.ProfileInsignia .Where(x => x.InsigniaId.HasValue && x.InsigniaId.Value != GetInsigniaByName("เหรียญจักรพรรดิมาลา")?.Id) @@ -8625,8 +8625,8 @@ namespace BMA.EHR.Application.Repositories FullName = $"{(p.Prefix ?? "")}{p.FirstName} {p.LastName}", Position = p.Position ?? "", Rank = p.PosLevel ?? "", - ProfileDateAppoint = p.DateAppoint.Value, - GovAge = p.DateAppoint.Value.CalculateGovAgeStr(0, 0), + ProfileDateAppoint = p.DateAppoint!.Value, + GovAge = p.DateStart!.Value.CalculateGovAgeStr(0, 0), PosNo = p.PosNo, PositionLevelId = p.PosLevelId, PositionLevelName = p.PosLevel, diff --git a/BMA.EHR.Application/Repositories/Leaves/LeaveRequests/LeaveBeginingRepository.cs b/BMA.EHR.Application/Repositories/Leaves/LeaveRequests/LeaveBeginingRepository.cs index c2d08f70..ec280b48 100644 --- a/BMA.EHR.Application/Repositories/Leaves/LeaveRequests/LeaveBeginingRepository.cs +++ b/BMA.EHR.Application/Repositories/Leaves/LeaveRequests/LeaveBeginingRepository.cs @@ -108,7 +108,7 @@ namespace BMA.EHR.Application.Repositories.Leaves.LeaveRequests throw new Exception(GlobalMessages.DataNotFound); } - var govAge = (pf?.DateAppoint?.Date ?? DateTime.Now.Date).DiffDay(DateTime.Now.Date); + var govAge = (pf?.DateStart?.Date ?? DateTime.Now.Date).DiffDay(DateTime.Now.Date); var leaveType = await _dbContext.Set().FirstOrDefaultAsync(x => x.Id == typeId); @@ -174,7 +174,7 @@ namespace BMA.EHR.Application.Repositories.Leaves.LeaveRequests public async Task GetByYearAndTypeIdForUser(int year, Guid typeId, GetProfileByKeycloakIdDto? pf) { - var govAge = (pf?.DateAppoint?.Date ?? DateTime.Now.Date).DiffDay(DateTime.Now.Date); + var govAge = (pf?.DateStart?.Date ?? DateTime.Now.Date).DiffDay(DateTime.Now.Date); var leaveType = await _dbContext.Set().FirstOrDefaultAsync(x => x.Id == typeId); @@ -247,7 +247,7 @@ namespace BMA.EHR.Application.Repositories.Leaves.LeaveRequests return null; } - var govAge = (pf?.DateAppoint?.Date ?? DateTime.Now.Date).DiffDay(DateTime.Now.Date); + var govAge = (pf?.DateStart?.Date ?? DateTime.Now.Date).DiffDay(DateTime.Now.Date); var leaveType = await _dbContext.Set().FirstOrDefaultAsync(x => x.Id == typeId); @@ -337,7 +337,7 @@ namespace BMA.EHR.Application.Repositories.Leaves.LeaveRequests return null; } - var govAge = (pf?.DateAppoint?.Date ?? DateTime.Now.Date).DiffDay(DateTime.Now.Date); + var govAge = (pf?.DateStart?.Date ?? DateTime.Now.Date).DiffDay(DateTime.Now.Date); var leaveType = await _dbContext.Set().FirstOrDefaultAsync(x => x.Id == typeId); diff --git a/BMA.EHR.Leave/Controllers/LeaveRequestController.cs b/BMA.EHR.Leave/Controllers/LeaveRequestController.cs index a961bfa2..a90ad763 100644 --- a/BMA.EHR.Leave/Controllers/LeaveRequestController.cs +++ b/BMA.EHR.Leave/Controllers/LeaveRequestController.cs @@ -869,7 +869,7 @@ namespace BMA.EHR.Leave.Service.Controllers return Error(GlobalMessages.DataNotFound, StatusCodes.Status404NotFound); } - var govAge = (profile?.DateAppoint?.Date ?? DateTime.Now.Date).DiffDay(DateTime.Now.Date); + var govAge = (profile?.DateStart?.Date ?? DateTime.Now.Date).DiffDay(DateTime.Now.Date); var leaveType = await _leaveTypeRepository.GetByIdAsync(req.Type); if (leaveType == null) @@ -985,7 +985,7 @@ namespace BMA.EHR.Leave.Service.Controllers // var profile = await _userProfileRepository.GetProfileByKeycloakIdAsync(userId, AccessToken); var profile = await _userProfileRepository.GetProfileByKeycloakIdNewAsync(userId, AccessToken); - var govAge = (profile?.DateAppoint?.Date ?? DateTime.Now.Date).DiffDay(DateTime.Now.Date); + var govAge = (profile?.DateStart?.Date ?? DateTime.Now.Date).DiffDay(DateTime.Now.Date); var thisYear = DateTime.Now.Year; var message = string.Empty; @@ -1542,7 +1542,7 @@ namespace BMA.EHR.Leave.Service.Controllers return Error(GlobalMessages.DataNotFound, StatusCodes.Status404NotFound); } - var govAge = (profile?.DateAppoint?.Date ?? DateTime.Now.Date).DiffDay(DateTime.Now.Date); + var govAge = (profile?.DateStart?.Date ?? DateTime.Now.Date).DiffDay(DateTime.Now.Date); var userCalendar = await _userCalendarRepository.GetExist(profile.Id); var category = userCalendar == null ? "NORMAL" : userCalendar.Calendar; From 970319e8c2daada5aa4ecb5723ac1a331aa21584 Mon Sep 17 00:00:00 2001 From: harid Date: Wed, 4 Feb 2026 11:05:02 +0700 Subject: [PATCH 089/183] =?UTF-8?q?API=20=E0=B8=AD=E0=B8=B1=E0=B8=9E?= =?UTF-8?q?=E0=B9=80=E0=B8=94=E0=B8=97=E0=B8=AA=E0=B8=96=E0=B8=B2=E0=B8=99?= =?UTF-8?q?=E0=B8=B0=E0=B9=80=E0=B8=9B=E0=B9=87=E0=B8=99=E0=B8=9A=E0=B8=A3?= =?UTF-8?q?=E0=B8=A3=E0=B8=88=E0=B8=B8=20=E0=B9=80=E0=B8=89=E0=B8=9E?= =?UTF-8?q?=E0=B8=B2=E0=B8=B0=20Super=5Fadmin?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Controllers/PlacementController.cs | 35 ++++++++++++++++++- .../Requests/PersonUpdateStatusRequest.cs | 10 ++++++ 2 files changed, 44 insertions(+), 1 deletion(-) create mode 100644 BMA.EHR.Placement.Service/Requests/PersonUpdateStatusRequest.cs diff --git a/BMA.EHR.Placement.Service/Controllers/PlacementController.cs b/BMA.EHR.Placement.Service/Controllers/PlacementController.cs index a485a42d..1a9260ef 100644 --- a/BMA.EHR.Placement.Service/Controllers/PlacementController.cs +++ b/BMA.EHR.Placement.Service/Controllers/PlacementController.cs @@ -62,9 +62,9 @@ namespace BMA.EHR.Placement.Service.Controllers #region " Properties " private string? UserId => _httpContextAccessor?.HttpContext?.User?.FindFirst(ClaimTypes.NameIdentifier)?.Value; - private string? FullName => _httpContextAccessor?.HttpContext?.User?.FindFirst("name")?.Value; private string? token => _httpContextAccessor.HttpContext.Request.Headers["Authorization"]; + private bool isSuperAdmin => _httpContextAccessor?.HttpContext?.User?.IsInRole("SUPER_ADMIN") ?? false; #endregion @@ -852,6 +852,39 @@ namespace BMA.EHR.Placement.Service.Controllers return Success(); } + /// + /// API อัพเดทสถานะเป็นบรรจุ + /// + /// + /// + /// ค่าตัวแปรที่ส่งมาไม่ถูกต้อง + /// ไม่ได้ Login เข้าระบบ + /// เมื่อเกิดข้อผิดพลาดในการทำงาน + [HttpPost("pass/update-status")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesResponseType(StatusCodes.Status401Unauthorized)] + [ProducesResponseType(StatusCodes.Status500InternalServerError)] + public async Task> PersonUpdateStatus([FromBody] PersonUpdateStatusRequest req) + { + if (isSuperAdmin == false) + return Success(); + + var person = await _context.PlacementProfiles + .FirstOrDefaultAsync(x => x.Id == req.PersonalId); + if (person == null) + return Error(GlobalMessages.DataNotFound, 404); + + person.PlacementStatus = "DONE"; + person.LastUpdateFullName = FullName ?? "System Administrator"; + person.LastUpdateUserId = UserId ?? ""; + person.LastUpdatedAt = DateTime.Now; + + await _context.SaveChangesAsync(); + + return Success(); + } + [HttpGet("pass/deferment/{personalId:length(36)}")] public async Task> GetPersonDeferment(Guid personalId) { diff --git a/BMA.EHR.Placement.Service/Requests/PersonUpdateStatusRequest.cs b/BMA.EHR.Placement.Service/Requests/PersonUpdateStatusRequest.cs new file mode 100644 index 00000000..aef70fb5 --- /dev/null +++ b/BMA.EHR.Placement.Service/Requests/PersonUpdateStatusRequest.cs @@ -0,0 +1,10 @@ +using BMA.EHR.Domain.Models.MetaData; +using Microsoft.EntityFrameworkCore; + +namespace BMA.EHR.Placement.Service.Requests +{ + public class PersonUpdateStatusRequest + { + public Guid PersonalId { get; set; } + } +} From 358fd47b991c2468bff88dba285f13014e35f8e6 Mon Sep 17 00:00:00 2001 From: Suphonchai Phoonsawat Date: Thu, 5 Feb 2026 10:39:57 +0700 Subject: [PATCH 090/183] Add IsProbatin property to GetProfileByKeycloakIdDto and update leave request logic for probationary users #2266 --- .../Profiles/GetProfileByKeycloakIdDto.cs | 2 + BMA.EHR.Leave/Controllers/LeaveController.cs | 57 ++++++++++++------- .../Controllers/LeaveRequestController.cs | 8 ++- 3 files changed, 43 insertions(+), 24 deletions(-) diff --git a/BMA.EHR.Application/Responses/Profiles/GetProfileByKeycloakIdDto.cs b/BMA.EHR.Application/Responses/Profiles/GetProfileByKeycloakIdDto.cs index b2e71129..7db0e212 100644 --- a/BMA.EHR.Application/Responses/Profiles/GetProfileByKeycloakIdDto.cs +++ b/BMA.EHR.Application/Responses/Profiles/GetProfileByKeycloakIdDto.cs @@ -44,6 +44,8 @@ namespace BMA.EHR.Application.Responses.Profiles public string? ProfileType { get; set; } public bool? IsLeave { get; set; } + public bool? IsProbatin { get; set; } + public string? Root { get; set; } public string? Child1 { get; set; } public string? Child2 { get; set; } diff --git a/BMA.EHR.Leave/Controllers/LeaveController.cs b/BMA.EHR.Leave/Controllers/LeaveController.cs index d90321ba..85b782cd 100644 --- a/BMA.EHR.Leave/Controllers/LeaveController.cs +++ b/BMA.EHR.Leave/Controllers/LeaveController.cs @@ -926,9 +926,17 @@ namespace BMA.EHR.Leave.Service.Controllers var profile = await _userProfileRepository.GetProfileByKeycloakIdNewAsync(userId, data.Token); if (profile == null) + { + await _checkInJobStatusRepository.UpdateToFailedAsync(taskId, "ไม่พบข้อมูลผู้ใช้"); return Error(GlobalMessages.DataNotFound, StatusCodes.Status404NotFound); + } - if (data.CheckInFileName == "no-file") throw new Exception(GlobalMessages.NoFileToUpload); + if (data.CheckInFileName == "no-file") + { + //throw new Exception(GlobalMessages.NoFileToUpload); + await _checkInJobStatusRepository.UpdateToFailedAsync(taskId, GlobalMessages.NoFileToUpload); + return Error(GlobalMessages.NoFileToUpload, StatusCodes.Status400BadRequest); + } var currentDate = data.CurrentDate ?? DateTime.Now; var check_status = data.CheckInId == null ? "check-in-picture" : "check-out-picture"; @@ -970,6 +978,7 @@ namespace BMA.EHR.Leave.Service.Controllers if (currentCheckIn != null) { + await _checkInJobStatusRepository.UpdateToFailedAsync(taskId, "ไม่สามารถลงเวลาได้ เนื่องจากมีการลงเวลาในวันนี้แล้ว"); return Error(new Exception("ไม่สามารถลงเวลาได้ เนื่องจากมีการลงเวลาในวันนี้แล้ว!"), StatusCodes.Status400BadRequest); } @@ -1104,31 +1113,35 @@ namespace BMA.EHR.Leave.Service.Controllers else { var checkout = await _userTimeStampRepository.GetByIdAsync(data.CheckInId.Value); - - var currentCheckInProcess = await _processUserTimeStampRepository.GetTimestampByDateAsync(userId, checkout.CheckIn.Date); - - var checkout_process = await _processUserTimeStampRepository.GetByIdAsync(currentCheckInProcess.Id); - - - if (checkout != null) - { - checkout.CheckOutLat = data.Lat; - checkout.CheckOutLon = data.Lon; - checkout.IsLocationCheckOut = data.IsLocation; - checkout.CheckOutLocationName = data.LocationName; - checkout.CheckOutPOI = data.POI; - checkout.CheckOutRemark = data.Remark; - checkout.CheckOutImageUrl = fileName; - checkout.CheckOut = currentDate; - - await _userTimeStampRepository.UpdateAsync(checkout); - } - else + + if (checkout == null) { await _checkInJobStatusRepository.UpdateToFailedAsync(taskId, "ไม่พบข้อมูลการลงเวลาทำงาน"); return Error(new Exception(GlobalMessages.DataNotFound), StatusCodes.Status404NotFound); } + var currentCheckInProcess = await _processUserTimeStampRepository.GetTimestampByDateAsync(userId, checkout.CheckIn.Date); + + if (currentCheckInProcess == null) + { + await _checkInJobStatusRepository.UpdateToFailedAsync(taskId, "ไม่พบข้อมูลการประมวลผลเวลาทำงาน (CheckIn)"); + return Error(new Exception(GlobalMessages.DataNotFound), StatusCodes.Status404NotFound); + } + + var checkout_process = await _processUserTimeStampRepository.GetByIdAsync(currentCheckInProcess.Id); + + // Update checkout record + checkout.CheckOutLat = data.Lat; + checkout.CheckOutLon = data.Lon; + checkout.IsLocationCheckOut = data.IsLocation; + checkout.CheckOutLocationName = data.LocationName; + checkout.CheckOutPOI = data.POI; + checkout.CheckOutRemark = data.Remark; + checkout.CheckOutImageUrl = fileName; + checkout.CheckOut = currentDate; + + await _userTimeStampRepository.UpdateAsync(checkout); + var endTime = ""; var startTime = ""; var endTimeMorning = ""; @@ -1289,7 +1302,7 @@ namespace BMA.EHR.Leave.Service.Controllers { await _checkInJobStatusRepository.UpdateToFailedAsync(taskId, ex.Message); } - throw; + return Error(ex); } } diff --git a/BMA.EHR.Leave/Controllers/LeaveRequestController.cs b/BMA.EHR.Leave/Controllers/LeaveRequestController.cs index a90ad763..1a00d285 100644 --- a/BMA.EHR.Leave/Controllers/LeaveRequestController.cs +++ b/BMA.EHR.Leave/Controllers/LeaveRequestController.cs @@ -1084,8 +1084,12 @@ namespace BMA.EHR.Leave.Service.Controllers // fix issue : ระบบลา (ขรก.) >> ลาพักผ่อน (กรณีรับราชการไม่ถึง 6 เดือน) #838 //var leavePrevYear = (await _leaveRequestRepository.GetSumApproveLeaveAsync(fiscalYear - 1)).Where(x => x.LeaveTypeCode == "LV-005" && x.KeycloakUserId == userId).FirstOrDefault(); //var leavePrevYearRemain = 10 - (leavePrevYear == null ? 0 : leavePrevYear.SumLeaveDay); // หายอดวันลาที่เหลือของปีก่อน - - if (govAge >= 180) + if (profile.IsProbatin! == true) + { + isLeave = false; + if (!isLeave) message = "ยังอยู่ในช่วงทดลองปฏิบัติราชการ ไม่สามารถลาพักผ่อนได้"; + } + else if (govAge >= 180) { isLeave = (totalDay - (sumWorkDay + sumWeekend) + approveDay) <= (limitDay); if (!isLeave) message = "จำนวนวันลาเกินที่กำหนด"; From 639d41649c041634dbde63f0ca09a0ee5d114808 Mon Sep 17 00:00:00 2001 From: Suphonchai Phoonsawat Date: Thu, 5 Feb 2026 10:54:44 +0700 Subject: [PATCH 091/183] Add LeaveCount column to LeaveBeginnings table MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Introduced a new column 'LeaveCount' of type int to the LeaveBeginnings table. - Set default value to 0 and added a comment for clarity in Thai: "จำนวนครั้งที่ลาสะสม". --- .../Models/Leave/Requests/LeaveBeginning.cs | 3 + ...d LeaveCount to LeaveBeginning.Designer.cs | 1709 +++++++++++++++++ ...034753_Add LeaveCount to LeaveBeginning.cs | 30 + .../LeaveDb/LeaveDbContextModelSnapshot.cs | 4 + .../Controllers/LeaveBeginningController.cs | 3 + .../LeaveBeginnings/EditLeaveBeginningDto.cs | 3 + 6 files changed, 1752 insertions(+) create mode 100644 BMA.EHR.Infrastructure/Migrations/LeaveDb/20260205034753_Add LeaveCount to LeaveBeginning.Designer.cs create mode 100644 BMA.EHR.Infrastructure/Migrations/LeaveDb/20260205034753_Add LeaveCount to LeaveBeginning.cs diff --git a/BMA.EHR.Domain/Models/Leave/Requests/LeaveBeginning.cs b/BMA.EHR.Domain/Models/Leave/Requests/LeaveBeginning.cs index 9c459c69..d68f0229 100644 --- a/BMA.EHR.Domain/Models/Leave/Requests/LeaveBeginning.cs +++ b/BMA.EHR.Domain/Models/Leave/Requests/LeaveBeginning.cs @@ -30,6 +30,9 @@ namespace BMA.EHR.Domain.Models.Leave.Requests [Required, Comment("จำนวนวันลาที่ใช้ไป")] public double LeaveDaysUsed { get; set; } = 0.0; + [Comment("จำนวนครั้งที่ลาสะสม")] + public int LeaveCount { get; set; } = 0; + public Guid? RootDnaId { get; set; } public Guid? Child1DnaId { get; set; } diff --git a/BMA.EHR.Infrastructure/Migrations/LeaveDb/20260205034753_Add LeaveCount to LeaveBeginning.Designer.cs b/BMA.EHR.Infrastructure/Migrations/LeaveDb/20260205034753_Add LeaveCount to LeaveBeginning.Designer.cs new file mode 100644 index 00000000..acd7320f --- /dev/null +++ b/BMA.EHR.Infrastructure/Migrations/LeaveDb/20260205034753_Add LeaveCount to LeaveBeginning.Designer.cs @@ -0,0 +1,1709 @@ +// +using System; +using BMA.EHR.Infrastructure.Persistence; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace BMA.EHR.Infrastructure.Migrations.LeaveDb +{ + [DbContext(typeof(LeaveDbContext))] + [Migration("20260205034753_Add LeaveCount to LeaveBeginning")] + partial class AddLeaveCounttoLeaveBeginning + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "7.0.9") + .HasAnnotation("Relational:MaxIdentifierLength", 64); + + modelBuilder.Entity("BMA.EHR.Domain.Models.Documents.Document", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CreatedDate") + .HasColumnType("datetime(6)"); + + b.Property("Detail") + .IsRequired() + .HasColumnType("text"); + + b.Property("FileName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("varchar(255)"); + + b.Property("FileSize") + .HasColumnType("int"); + + b.Property("FileType") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("varchar(128)"); + + b.Property("ObjectRefId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.ToTable("Document"); + }); + + modelBuilder.Entity("BMA.EHR.Domain.Models.Leave.Commons.LeaveType", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)") + .HasColumnOrder(0) + .HasComment("PrimaryKey") + .HasAnnotation("Relational:JsonPropertyName", "id"); + + b.Property("Code") + .IsRequired() + .HasColumnType("longtext") + .HasComment("รหัสประเภทการลา"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)") + .HasColumnOrder(100) + .HasComment("สร้างข้อมูลเมื่อ"); + + b.Property("CreatedFullName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)") + .HasColumnOrder(104) + .HasComment("ชื่อ User ที่สร้างข้อมูล"); + + b.Property("CreatedUserId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("varchar(40)") + .HasColumnOrder(101) + .HasComment("User Id ที่สร้างข้อมูล"); + + b.Property("LastUpdateFullName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)") + .HasColumnOrder(105) + .HasComment("ชื่อ User ที่แก้ไขข้อมูลล่าสุด"); + + b.Property("LastUpdateUserId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("varchar(40)") + .HasColumnOrder(103) + .HasComment("User Id ที่แก้ไขข้อมูลล่าสุด"); + + b.Property("LastUpdatedAt") + .HasColumnType("datetime(6)") + .HasColumnOrder(102) + .HasComment("แก้ไขข้อมูลล่าสุดเมื่อ"); + + b.Property("Limit") + .HasColumnType("int") + .HasComment("จำนวนวันลาสูงสุดประจำปี"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext") + .HasComment("ชื่อประเภทการลา"); + + b.HasKey("Id"); + + b.ToTable("LeaveTypes"); + }); + + modelBuilder.Entity("BMA.EHR.Domain.Models.Leave.Requests.LeaveBeginning", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)") + .HasColumnOrder(0) + .HasComment("PrimaryKey") + .HasAnnotation("Relational:JsonPropertyName", "id"); + + b.Property("Child1DnaId") + .HasColumnType("char(36)"); + + b.Property("Child2DnaId") + .HasColumnType("char(36)"); + + b.Property("Child3DnaId") + .HasColumnType("char(36)"); + + b.Property("Child4DnaId") + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)") + .HasColumnOrder(100) + .HasComment("สร้างข้อมูลเมื่อ"); + + b.Property("CreatedFullName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)") + .HasColumnOrder(104) + .HasComment("ชื่อ User ที่สร้างข้อมูล"); + + b.Property("CreatedUserId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("varchar(40)") + .HasColumnOrder(101) + .HasComment("User Id ที่สร้างข้อมูล"); + + b.Property("FirstName") + .HasColumnType("longtext"); + + b.Property("LastName") + .HasColumnType("longtext"); + + b.Property("LastUpdateFullName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)") + .HasColumnOrder(105) + .HasComment("ชื่อ User ที่แก้ไขข้อมูลล่าสุด"); + + b.Property("LastUpdateUserId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("varchar(40)") + .HasColumnOrder(103) + .HasComment("User Id ที่แก้ไขข้อมูลล่าสุด"); + + b.Property("LastUpdatedAt") + .HasColumnType("datetime(6)") + .HasColumnOrder(102) + .HasComment("แก้ไขข้อมูลล่าสุดเมื่อ"); + + b.Property("LeaveCount") + .HasColumnType("int") + .HasComment("จำนวนครั้งที่ลาสะสม"); + + b.Property("LeaveDays") + .HasColumnType("double") + .HasComment("จำนวนวันลายกมา"); + + b.Property("LeaveDaysUsed") + .HasColumnType("double") + .HasComment("จำนวนวันลาที่ใช้ไป"); + + b.Property("LeaveTypeId") + .HasColumnType("char(36)") + .HasComment("รหัสประเภทการลา"); + + b.Property("LeaveYear") + .HasColumnType("int") + .HasComment("ปีงบประมาณ"); + + b.Property("Prefix") + .HasColumnType("longtext"); + + b.Property("ProfileId") + .HasColumnType("char(36)") + .HasComment("รหัส Profile ในระบบทะเบียนประวัติ"); + + b.Property("RootDnaId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("LeaveTypeId"); + + b.ToTable("LeaveBeginnings"); + }); + + modelBuilder.Entity("BMA.EHR.Domain.Models.Leave.Requests.LeaveDocument", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)") + .HasColumnOrder(0) + .HasComment("PrimaryKey") + .HasAnnotation("Relational:JsonPropertyName", "id"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)") + .HasColumnOrder(100) + .HasComment("สร้างข้อมูลเมื่อ"); + + b.Property("CreatedFullName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)") + .HasColumnOrder(104) + .HasComment("ชื่อ User ที่สร้างข้อมูล"); + + b.Property("CreatedUserId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("varchar(40)") + .HasColumnOrder(101) + .HasComment("User Id ที่สร้างข้อมูล"); + + b.Property("DocumentId") + .HasColumnType("char(36)"); + + b.Property("LastUpdateFullName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)") + .HasColumnOrder(105) + .HasComment("ชื่อ User ที่แก้ไขข้อมูลล่าสุด"); + + b.Property("LastUpdateUserId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("varchar(40)") + .HasColumnOrder(103) + .HasComment("User Id ที่แก้ไขข้อมูลล่าสุด"); + + b.Property("LastUpdatedAt") + .HasColumnType("datetime(6)") + .HasColumnOrder(102) + .HasComment("แก้ไขข้อมูลล่าสุดเมื่อ"); + + b.Property("LeaveRequestId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("DocumentId"); + + b.HasIndex("LeaveRequestId"); + + b.ToTable("LeaveDocuments"); + }); + + modelBuilder.Entity("BMA.EHR.Domain.Models.Leave.Requests.LeaveRequest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)") + .HasColumnOrder(0) + .HasComment("PrimaryKey") + .HasAnnotation("Relational:JsonPropertyName", "id"); + + b.Property("AbsentDayAt") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("AbsentDayGetIn") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("AbsentDayLocation") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("AbsentDayRegistorDate") + .HasColumnType("datetime(6)"); + + b.Property("AbsentDaySummon") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Amount") + .HasColumnType("double"); + + b.Property("ApproveStep") + .HasColumnType("longtext") + .HasComment("step การอนุมัติ st1 = จทน.อนุมัตื,st2 = ผู้บังคับบัญชา อนุมัติ "); + + b.Property("BirthDate") + .HasColumnType("datetime(6)"); + + b.Property("CancelLeaveWrote") + .HasColumnType("longtext") + .HasComment("เขียนที่ (ขอยกเลิก)"); + + b.Property("Child1") + .HasColumnType("longtext"); + + b.Property("Child1DnaId") + .HasColumnType("char(36)"); + + b.Property("Child1Id") + .HasColumnType("char(36)"); + + b.Property("Child2") + .HasColumnType("longtext"); + + b.Property("Child2DnaId") + .HasColumnType("char(36)"); + + b.Property("Child2Id") + .HasColumnType("char(36)"); + + b.Property("Child3") + .HasColumnType("longtext"); + + b.Property("Child3DnaId") + .HasColumnType("char(36)"); + + b.Property("Child3Id") + .HasColumnType("char(36)"); + + b.Property("Child4") + .HasColumnType("longtext"); + + b.Property("Child4DnaId") + .HasColumnType("char(36)"); + + b.Property("Child4Id") + .HasColumnType("char(36)"); + + b.Property("CitizenId") + .HasColumnType("longtext"); + + b.Property("CommanderPosition") + .HasColumnType("longtext"); + + b.Property("CoupleDayCountryHistory") + .HasColumnType("longtext"); + + b.Property("CoupleDayEndDateHistory") + .HasColumnType("datetime(6)"); + + b.Property("CoupleDayLevel") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("CoupleDayLevelCountry") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("CoupleDayName") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("CoupleDayPosition") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("CoupleDayStartDateHistory") + .HasColumnType("datetime(6)"); + + b.Property("CoupleDaySumTotalHistory") + .HasColumnType("longtext"); + + b.Property("CoupleDayTotalHistory") + .HasColumnType("longtext"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)") + .HasColumnOrder(100) + .HasComment("สร้างข้อมูลเมื่อ"); + + b.Property("CreatedFullName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)") + .HasColumnOrder(104) + .HasComment("ชื่อ User ที่สร้างข้อมูล"); + + b.Property("CreatedUserId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("varchar(40)") + .HasColumnOrder(101) + .HasComment("User Id ที่สร้างข้อมูล"); + + b.Property("DateAppoint") + .HasColumnType("datetime(6)"); + + b.Property("Dear") + .HasColumnType("longtext") + .HasComment("เรียนใคร"); + + b.Property("FirstName") + .HasColumnType("longtext"); + + b.Property("Gender") + .HasColumnType("longtext"); + + b.Property("HajjDayStatus") + .HasColumnType("tinyint(1)"); + + b.Property("KeycloakUserId") + .HasColumnType("char(36)"); + + b.Property("LastName") + .HasColumnType("longtext"); + + b.Property("LastUpdateFullName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)") + .HasColumnOrder(105) + .HasComment("ชื่อ User ที่แก้ไขข้อมูลล่าสุด"); + + b.Property("LastUpdateUserId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("varchar(40)") + .HasColumnOrder(103) + .HasComment("User Id ที่แก้ไขข้อมูลล่าสุด"); + + b.Property("LastUpdatedAt") + .HasColumnType("datetime(6)") + .HasColumnOrder(102) + .HasComment("แก้ไขข้อมูลล่าสุดเมื่อ"); + + b.Property("LeaveAddress") + .IsRequired() + .HasColumnType("longtext") + .HasComment("สถานที่ติดต่อขณะลา"); + + b.Property("LeaveBirthDate") + .HasColumnType("datetime(6)"); + + b.Property("LeaveCancelComment") + .HasColumnType("longtext") + .HasComment("เหตุผลในการขอยกเลิก"); + + b.Property("LeaveCancelDocumentId") + .HasColumnType("char(36)"); + + b.Property("LeaveCancelStatus") + .HasColumnType("longtext") + .HasComment("สถานะของคำขอยกเลิก"); + + b.Property("LeaveComment") + .HasColumnType("longtext") + .HasComment("ความเห็นของผู้บังคับบัญชา"); + + b.Property("LeaveDetail") + .IsRequired() + .HasColumnType("longtext") + .HasComment("รายละเอียดการลา"); + + b.Property("LeaveDirectorComment") + .HasColumnType("longtext") + .HasComment("ความเห็นของผู้อำนวยการสำนัก"); + + b.Property("LeaveDraftDocumentId") + .HasColumnType("char(36)"); + + b.Property("LeaveEndDate") + .HasColumnType("datetime(6)") + .HasComment("วัน เดือน ปีสิ้นสุดลา"); + + b.Property("LeaveGovernmentDate") + .HasColumnType("datetime(6)"); + + b.Property("LeaveLast") + .HasColumnType("datetime(6)"); + + b.Property("LeaveNumber") + .IsRequired() + .HasColumnType("longtext") + .HasComment("หมายเลขที่ติดต่อขณะลา"); + + b.Property("LeaveRange") + .HasColumnType("longtext") + .HasComment("ช่วงของการลาของวันเริ่ม เช่น ลาทั้งวัน ครึ่งวันเช้า ครึ่งวันบ่าย"); + + b.Property("LeaveRangeEnd") + .HasColumnType("longtext") + .HasComment("ช่วงของการลาของวันสิ้นสุด เช่น ลาทั้งวัน ครึ่งวันเช้า ครึ่งวันบ่าย"); + + b.Property("LeaveSalary") + .HasColumnType("int"); + + b.Property("LeaveSalaryText") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("LeaveStartDate") + .HasColumnType("datetime(6)") + .HasComment("วัน เดือน ปีเริ่มต้นลา"); + + b.Property("LeaveStatus") + .IsRequired() + .HasColumnType("longtext") + .HasComment("สถานะของคำร้อง"); + + b.Property("LeaveSubTypeName") + .HasColumnType("longtext"); + + b.Property("LeaveTotal") + .HasColumnType("double"); + + b.Property("LeaveTypeCode") + .HasColumnType("longtext") + .HasComment("code ของประเภทการลา"); + + b.Property("LeaveWrote") + .IsRequired() + .HasColumnType("longtext") + .HasComment("เขียนที่"); + + b.Property("OrdainDayBuddhistLentAddress") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("OrdainDayBuddhistLentName") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("OrdainDayLocationAddress") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("OrdainDayLocationName") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("OrdainDayLocationNumber") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("OrdainDayOrdination") + .HasColumnType("datetime(6)"); + + b.Property("OrdainDayStatus") + .HasColumnType("tinyint(1)"); + + b.Property("OrganizationName") + .HasColumnType("longtext") + .HasComment("สังกัดผู้ยื่นขอ"); + + b.Property("PositionLevelName") + .HasColumnType("longtext") + .HasComment("ระดับผู้ยื่นขอ"); + + b.Property("PositionName") + .HasColumnType("longtext") + .HasComment("ตำแหน่งผู้ยื่นขอ"); + + b.Property("Prefix") + .HasColumnType("longtext"); + + b.Property("ProfileId") + .HasColumnType("char(36)"); + + b.Property("ProfileType") + .HasColumnType("longtext"); + + b.Property("RestDayCurrentTotal") + .HasColumnType("double"); + + b.Property("RestDayOldTotal") + .HasColumnType("double"); + + b.Property("Root") + .HasColumnType("longtext"); + + b.Property("RootDnaId") + .HasColumnType("char(36)"); + + b.Property("RootId") + .HasColumnType("char(36)"); + + b.Property("StudyDayCountry") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("StudyDayDegreeLevel") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("StudyDayScholarship") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("StudyDaySubject") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("StudyDayTrainingName") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("StudyDayTrainingSubject") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("StudyDayUniversityName") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("TypeId") + .HasColumnType("char(36)"); + + b.Property("WifeDayDateBorn") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("WifeDayName") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("LeaveCancelDocumentId"); + + b.HasIndex("LeaveDraftDocumentId"); + + b.HasIndex("TypeId"); + + b.ToTable("LeaveRequests"); + }); + + modelBuilder.Entity("BMA.EHR.Domain.Models.Leave.Requests.LeaveRequestApprover", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)") + .HasColumnOrder(0) + .HasComment("PrimaryKey") + .HasAnnotation("Relational:JsonPropertyName", "id"); + + b.Property("ApproveStatus") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("ApproveType") + .HasColumnType("longtext"); + + b.Property("Comment") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)") + .HasColumnOrder(100) + .HasComment("สร้างข้อมูลเมื่อ"); + + b.Property("CreatedFullName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)") + .HasColumnOrder(104) + .HasComment("ชื่อ User ที่สร้างข้อมูล"); + + b.Property("CreatedUserId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("varchar(40)") + .HasColumnOrder(101) + .HasComment("User Id ที่สร้างข้อมูล"); + + b.Property("FirstName") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("KeycloakId") + .HasColumnType("char(36)"); + + b.Property("LastName") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("LastUpdateFullName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)") + .HasColumnOrder(105) + .HasComment("ชื่อ User ที่แก้ไขข้อมูลล่าสุด"); + + b.Property("LastUpdateUserId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("varchar(40)") + .HasColumnOrder(103) + .HasComment("User Id ที่แก้ไขข้อมูลล่าสุด"); + + b.Property("LastUpdatedAt") + .HasColumnType("datetime(6)") + .HasColumnOrder(102) + .HasComment("แก้ไขข้อมูลล่าสุดเมื่อ"); + + b.Property("LeaveRequestId") + .HasColumnType("char(36)"); + + b.Property("OrganizationName") + .IsRequired() + .HasColumnType("longtext") + .HasComment("สังกัด"); + + b.Property("PosExecutiveName") + .IsRequired() + .HasColumnType("longtext") + .HasComment("ตำแหน่งทางการบริหาร"); + + b.Property("PositionLevelName") + .IsRequired() + .HasColumnType("longtext") + .HasComment("ประเภทระดับตำแหน่ง"); + + b.Property("PositionName") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("PositionSign") + .HasColumnType("longtext") + .HasComment("ตำแหน่งใต้ลายเช็นต์"); + + b.Property("Prefix") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("ProfileId") + .HasColumnType("char(36)"); + + b.Property("Seq") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("LeaveRequestId"); + + b.ToTable("LeaveRequestApprovers"); + }); + + modelBuilder.Entity("BMA.EHR.Domain.Models.Leave.TimeAttendants.AdditionalCheckRequest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)") + .HasColumnOrder(0) + .HasComment("PrimaryKey") + .HasAnnotation("Relational:JsonPropertyName", "id"); + + b.Property("CheckDate") + .HasColumnType("datetime(6)") + .HasComment("*วันที่ลงเวลา"); + + b.Property("CheckInEdit") + .HasColumnType("tinyint(1)") + .HasComment("*ขอลงเวลาช่วงเช้า"); + + b.Property("CheckOutEdit") + .HasColumnType("tinyint(1)") + .HasComment("*ขอลงเวลาช่วงบ่าย"); + + b.Property("Child1DnaId") + .HasColumnType("char(36)"); + + b.Property("Child2DnaId") + .HasColumnType("char(36)"); + + b.Property("Child3DnaId") + .HasColumnType("char(36)"); + + b.Property("Child4DnaId") + .HasColumnType("char(36)"); + + b.Property("Comment") + .HasColumnType("longtext") + .HasComment("หมายเหตุในการการอนุมัติ/ไม่อนุมัติ"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)") + .HasColumnOrder(100) + .HasComment("สร้างข้อมูลเมื่อ"); + + b.Property("CreatedFullName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)") + .HasColumnOrder(104) + .HasComment("ชื่อ User ที่สร้างข้อมูล"); + + b.Property("CreatedUserId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("varchar(40)") + .HasColumnOrder(101) + .HasComment("User Id ที่สร้างข้อมูล"); + + b.Property("Description") + .IsRequired() + .HasColumnType("longtext") + .HasComment("*หมายเหตุขอลงเวลาพิเศษ"); + + b.Property("FirstName") + .HasColumnType("longtext"); + + b.Property("KeycloakUserId") + .HasColumnType("char(36)") + .HasComment("รหัส User ของ Keycloak ที่ร้องขอ"); + + b.Property("LastName") + .HasColumnType("longtext"); + + b.Property("LastUpdateFullName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)") + .HasColumnOrder(105) + .HasComment("ชื่อ User ที่แก้ไขข้อมูลล่าสุด"); + + b.Property("LastUpdateUserId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("varchar(40)") + .HasColumnOrder(103) + .HasComment("User Id ที่แก้ไขข้อมูลล่าสุด"); + + b.Property("LastUpdatedAt") + .HasColumnType("datetime(6)") + .HasColumnOrder(102) + .HasComment("แก้ไขข้อมูลล่าสุดเมื่อ"); + + b.Property("Latitude") + .HasColumnType("double"); + + b.Property("Longitude") + .HasColumnType("double"); + + b.Property("POI") + .HasColumnType("longtext"); + + b.Property("Prefix") + .HasColumnType("longtext"); + + b.Property("RootDnaId") + .HasColumnType("char(36)"); + + b.Property("Status") + .IsRequired() + .HasColumnType("longtext") + .HasComment("สถานะการอนุมัติ"); + + b.HasKey("Id"); + + b.ToTable("AdditionalCheckRequests"); + }); + + modelBuilder.Entity("BMA.EHR.Domain.Models.Leave.TimeAttendants.CheckInJobStatus", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)") + .HasColumnOrder(0) + .HasComment("PrimaryKey") + .HasAnnotation("Relational:JsonPropertyName", "id"); + + b.Property("AdditionalData") + .HasColumnType("longtext") + .HasComment("ข้อมูลเพิ่มเติม (JSON)"); + + b.Property("CheckInId") + .HasColumnType("char(36)") + .HasComment("CheckInId สำหรับ Check-Out"); + + b.Property("CheckType") + .HasColumnType("longtext") + .HasComment("ประเภทการลงเวลา: CHECK_IN, CHECK_OUT"); + + b.Property("CompletedDate") + .HasColumnType("datetime(6)") + .HasComment("วันเวลาที่เสร็จสิ้นการประมวลผล"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)") + .HasColumnOrder(100) + .HasComment("สร้างข้อมูลเมื่อ"); + + b.Property("CreatedDate") + .HasColumnType("datetime(6)") + .HasComment("วันเวลาที่สร้างงาน"); + + b.Property("CreatedFullName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)") + .HasColumnOrder(104) + .HasComment("ชื่อ User ที่สร้างข้อมูล"); + + b.Property("CreatedUserId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("varchar(40)") + .HasColumnOrder(101) + .HasComment("User Id ที่สร้างข้อมูล"); + + b.Property("ErrorMessage") + .HasColumnType("longtext") + .HasComment("ข้อความแสดงข้อผิดพลาด"); + + b.Property("KeycloakUserId") + .HasColumnType("char(36)") + .HasComment("รหัส User ของ Keycloak"); + + b.Property("LastUpdateFullName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)") + .HasColumnOrder(105) + .HasComment("ชื่อ User ที่แก้ไขข้อมูลล่าสุด"); + + b.Property("LastUpdateUserId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("varchar(40)") + .HasColumnOrder(103) + .HasComment("User Id ที่แก้ไขข้อมูลล่าสุด"); + + b.Property("LastUpdatedAt") + .HasColumnType("datetime(6)") + .HasColumnOrder(102) + .HasComment("แก้ไขข้อมูลล่าสุดเมื่อ"); + + b.Property("ProcessingDate") + .HasColumnType("datetime(6)") + .HasComment("วันเวลาที่เริ่มประมวลผล"); + + b.Property("Status") + .IsRequired() + .HasColumnType("longtext") + .HasComment("สถานะงาน: PENDING, PROCESSING, COMPLETED, FAILED"); + + b.Property("TaskId") + .HasColumnType("char(36)") + .HasComment("Task ID สำหรับติดตามสถานะงาน"); + + b.HasKey("Id"); + + b.ToTable("CheckInJobStatuses"); + }); + + modelBuilder.Entity("BMA.EHR.Domain.Models.Leave.TimeAttendants.DutyTime", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)") + .HasColumnOrder(0) + .HasComment("PrimaryKey") + .HasAnnotation("Relational:JsonPropertyName", "id"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)") + .HasColumnOrder(100) + .HasComment("สร้างข้อมูลเมื่อ"); + + b.Property("CreatedFullName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)") + .HasColumnOrder(104) + .HasComment("ชื่อ User ที่สร้างข้อมูล"); + + b.Property("CreatedUserId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("varchar(40)") + .HasColumnOrder(101) + .HasComment("User Id ที่สร้างข้อมูล"); + + b.Property("Description") + .IsRequired() + .HasColumnType("longtext") + .HasComment("คำอธิบาย"); + + b.Property("EndTimeAfternoon") + .IsRequired() + .HasColumnType("longtext") + .HasComment("เวลาออกงานช่วงบ่าย"); + + b.Property("EndTimeMorning") + .IsRequired() + .HasColumnType("longtext") + .HasComment("เวลาออกงานช่วงเช้า"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)") + .HasComment("สถานะการเปิดใช้งาน (เปิด/ปิด)"); + + b.Property("IsDefault") + .HasColumnType("tinyint(1)") + .HasComment("สถานะว่ารอบใดเป็นค่า Default ของข้าราชการ (สำหรับทุกคนที่ยังไม่ได้ทำการเลือกรอบ)"); + + b.Property("LastUpdateFullName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)") + .HasColumnOrder(105) + .HasComment("ชื่อ User ที่แก้ไขข้อมูลล่าสุด"); + + b.Property("LastUpdateUserId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("varchar(40)") + .HasColumnOrder(103) + .HasComment("User Id ที่แก้ไขข้อมูลล่าสุด"); + + b.Property("LastUpdatedAt") + .HasColumnType("datetime(6)") + .HasColumnOrder(102) + .HasComment("แก้ไขข้อมูลล่าสุดเมื่อ"); + + b.Property("StartTimeAfternoon") + .IsRequired() + .HasColumnType("longtext") + .HasComment("เวลาเข้างานช่วงบ่าย"); + + b.Property("StartTimeMorning") + .IsRequired() + .HasColumnType("longtext") + .HasComment("เวลาเข้างานช่วงเช้า"); + + b.HasKey("Id"); + + b.ToTable("DutyTimes"); + }); + + modelBuilder.Entity("BMA.EHR.Domain.Models.Leave.TimeAttendants.ProcessUserTimeStamp", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)") + .HasColumnOrder(0) + .HasComment("PrimaryKey") + .HasAnnotation("Relational:JsonPropertyName", "id"); + + b.Property("CheckIn") + .HasColumnType("datetime(6)") + .HasComment("วัน เวลา เข้างาน"); + + b.Property("CheckInImageUrl") + .IsRequired() + .HasColumnType("longtext") + .HasComment("รูปถ่ายสถานที่ Check-In"); + + b.Property("CheckInLat") + .HasColumnType("double") + .HasComment("พิกัดละติจูด Check-In"); + + b.Property("CheckInLocationName") + .HasColumnType("longtext") + .HasComment("กรณีเลือกนอกสถานที่ตั้ง ต้องระบุข้อมูลชื่อสถานะที่ Check-In"); + + b.Property("CheckInLon") + .HasColumnType("double") + .HasComment("พิกัดลองจิจูด Check-In"); + + b.Property("CheckInPOI") + .IsRequired() + .HasColumnType("longtext") + .HasComment("ชื่อสถานที่ ได้มาจากระบบ ArcGis ของกองสารสนเทศภูมิศาสตร์ Check-In"); + + b.Property("CheckInRemark") + .HasColumnType("longtext") + .HasComment("ข้อความหมายเหตุที่ต้องการระบุเพิ่ม(มีเผื่อไว้อาจไม่ได้ใช้) Check-In"); + + b.Property("CheckInStatus") + .IsRequired() + .HasColumnType("longtext") + .HasComment("สถานะ Check-In"); + + b.Property("CheckOut") + .HasColumnType("datetime(6)") + .HasComment("วัน เวลา ออกงาน"); + + b.Property("CheckOutImageUrl") + .IsRequired() + .HasColumnType("longtext") + .HasComment("รูปถ่ายสถานที่ Check-Out"); + + b.Property("CheckOutLat") + .HasColumnType("double") + .HasComment("พิกัดละติจูด Check-Out"); + + b.Property("CheckOutLocationName") + .HasColumnType("longtext") + .HasComment("กรณีเลือกนอกสถานที่ตั้ง ต้องระบุข้อมูลชื่อสถานะที่ Check-Out"); + + b.Property("CheckOutLon") + .HasColumnType("double") + .HasComment("พิกัดลองจิจูด Check-Out"); + + b.Property("CheckOutPOI") + .IsRequired() + .HasColumnType("longtext") + .HasComment("ชื่อสถานที่ ได้มาจากระบบ ArcGis ของกองสารสนเทศภูมิศาสตร์ Check-Out"); + + b.Property("CheckOutRemark") + .HasColumnType("longtext") + .HasComment("ข้อความหมายเหตุที่ต้องการระบุเพิ่ม(มีเผื่อไว้อาจไม่ได้ใช้) Check-Out"); + + b.Property("CheckOutStatus") + .HasColumnType("longtext") + .HasComment("สถานะ Check-Out"); + + b.Property("Child1") + .HasColumnType("longtext"); + + b.Property("Child1DnaId") + .HasColumnType("char(36)"); + + b.Property("Child1Id") + .HasColumnType("char(36)"); + + b.Property("Child2") + .HasColumnType("longtext"); + + b.Property("Child2DnaId") + .HasColumnType("char(36)"); + + b.Property("Child2Id") + .HasColumnType("char(36)"); + + b.Property("Child3") + .HasColumnType("longtext"); + + b.Property("Child3DnaId") + .HasColumnType("char(36)"); + + b.Property("Child3Id") + .HasColumnType("char(36)"); + + b.Property("Child4") + .HasColumnType("longtext"); + + b.Property("Child4DnaId") + .HasColumnType("char(36)"); + + b.Property("Child4Id") + .HasColumnType("char(36)"); + + b.Property("CitizenId") + .HasColumnType("longtext"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)") + .HasColumnOrder(100) + .HasComment("สร้างข้อมูลเมื่อ"); + + b.Property("CreatedFullName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)") + .HasColumnOrder(104) + .HasComment("ชื่อ User ที่สร้างข้อมูล"); + + b.Property("CreatedUserId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("varchar(40)") + .HasColumnOrder(101) + .HasComment("User Id ที่สร้างข้อมูล"); + + b.Property("EditReason") + .HasColumnType("longtext") + .HasComment("เหตุผลการอนุมัติ/ไม่อนุมัติขอลงเวลาพิเศษ"); + + b.Property("EditStatus") + .HasColumnType("longtext") + .HasComment("สถานะการของลงเวลาพิเศษ"); + + b.Property("FirstName") + .HasColumnType("longtext"); + + b.Property("Gender") + .HasColumnType("longtext"); + + b.Property("IsLocationCheckIn") + .HasColumnType("tinyint(1)") + .HasComment("true คือ ณ สถานที่ตั้ง, false คือ นอกสถานที่ตั้ง Check-In"); + + b.Property("IsLocationCheckOut") + .HasColumnType("tinyint(1)") + .HasComment("true คือ ณ สถานที่ตั้ง, false คือ นอกสถานที่ตั้ง Check-Out"); + + b.Property("IsProcess") + .HasColumnType("tinyint(1)") + .HasComment("นำไปประมวลผลแล้วหรือยัง"); + + b.Property("KeycloakUserId") + .HasColumnType("char(36)") + .HasComment("รหัส User ของ Keycloak"); + + b.Property("LastName") + .HasColumnType("longtext"); + + b.Property("LastUpdateFullName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)") + .HasColumnOrder(105) + .HasComment("ชื่อ User ที่แก้ไขข้อมูลล่าสุด"); + + b.Property("LastUpdateUserId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("varchar(40)") + .HasColumnOrder(103) + .HasComment("User Id ที่แก้ไขข้อมูลล่าสุด"); + + b.Property("LastUpdatedAt") + .HasColumnType("datetime(6)") + .HasColumnOrder(102) + .HasComment("แก้ไขข้อมูลล่าสุดเมื่อ"); + + b.Property("Prefix") + .HasColumnType("longtext"); + + b.Property("ProfileId") + .HasColumnType("char(36)"); + + b.Property("ProfileType") + .HasColumnType("longtext"); + + b.Property("Root") + .HasColumnType("longtext"); + + b.Property("RootDnaId") + .HasColumnType("char(36)"); + + b.Property("RootId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.ToTable("ProcessUserTimeStamps"); + }); + + modelBuilder.Entity("BMA.EHR.Domain.Models.Leave.TimeAttendants.UserCalendar", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)") + .HasColumnOrder(0) + .HasComment("PrimaryKey") + .HasAnnotation("Relational:JsonPropertyName", "id"); + + b.Property("Calendar") + .IsRequired() + .HasColumnType("longtext") + .HasComment("ปฏิทินการทำงานของ ขรก ปกติ หรือ 6 วันต่อสัปดาห์"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)") + .HasColumnOrder(100) + .HasComment("สร้างข้อมูลเมื่อ"); + + b.Property("CreatedFullName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)") + .HasColumnOrder(104) + .HasComment("ชื่อ User ที่สร้างข้อมูล"); + + b.Property("CreatedUserId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("varchar(40)") + .HasColumnOrder(101) + .HasComment("User Id ที่สร้างข้อมูล"); + + b.Property("LastUpdateFullName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)") + .HasColumnOrder(105) + .HasComment("ชื่อ User ที่แก้ไขข้อมูลล่าสุด"); + + b.Property("LastUpdateUserId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("varchar(40)") + .HasColumnOrder(103) + .HasComment("User Id ที่แก้ไขข้อมูลล่าสุด"); + + b.Property("LastUpdatedAt") + .HasColumnType("datetime(6)") + .HasColumnOrder(102) + .HasComment("แก้ไขข้อมูลล่าสุดเมื่อ"); + + b.Property("ProfileId") + .HasColumnType("char(36)") + .HasComment("รหัส Profile ในระบบทะเบียนประวัติ"); + + b.HasKey("Id"); + + b.ToTable("UserCalendars"); + }); + + modelBuilder.Entity("BMA.EHR.Domain.Models.Leave.TimeAttendants.UserDutyTime", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)") + .HasColumnOrder(0) + .HasComment("PrimaryKey") + .HasAnnotation("Relational:JsonPropertyName", "id"); + + b.Property("Child1DnaId") + .HasColumnType("char(36)"); + + b.Property("Child2DnaId") + .HasColumnType("char(36)"); + + b.Property("Child3DnaId") + .HasColumnType("char(36)"); + + b.Property("Child4DnaId") + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)") + .HasColumnOrder(100) + .HasComment("สร้างข้อมูลเมื่อ"); + + b.Property("CreatedFullName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)") + .HasColumnOrder(104) + .HasComment("ชื่อ User ที่สร้างข้อมูล"); + + b.Property("CreatedUserId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("varchar(40)") + .HasColumnOrder(101) + .HasComment("User Id ที่สร้างข้อมูล"); + + b.Property("DutyTimeId") + .HasColumnType("char(36)") + .HasComment("รหัสรอบการลงเวลา"); + + b.Property("EffectiveDate") + .HasColumnType("datetime(6)") + .HasComment("วันที่มีผล"); + + b.Property("IsProcess") + .HasColumnType("tinyint(1)") + .HasComment("ทำการประมวลผลแล้วหรือยัง"); + + b.Property("LastUpdateFullName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)") + .HasColumnOrder(105) + .HasComment("ชื่อ User ที่แก้ไขข้อมูลล่าสุด"); + + b.Property("LastUpdateUserId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("varchar(40)") + .HasColumnOrder(103) + .HasComment("User Id ที่แก้ไขข้อมูลล่าสุด"); + + b.Property("LastUpdatedAt") + .HasColumnType("datetime(6)") + .HasColumnOrder(102) + .HasComment("แก้ไขข้อมูลล่าสุดเมื่อ"); + + b.Property("ProfileId") + .HasColumnType("char(36)") + .HasComment("รหัส Profile ในระบบทะเบียนประวัติ"); + + b.Property("Remark") + .HasColumnType("longtext") + .HasComment("หมายเหตุ"); + + b.Property("RootDnaId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("DutyTimeId"); + + b.ToTable("UserDutyTimes"); + }); + + modelBuilder.Entity("BMA.EHR.Domain.Models.Leave.TimeAttendants.UserTimeStamp", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)") + .HasColumnOrder(0) + .HasComment("PrimaryKey") + .HasAnnotation("Relational:JsonPropertyName", "id"); + + b.Property("CheckIn") + .HasColumnType("datetime(6)") + .HasComment("วัน เวลา เข้างาน"); + + b.Property("CheckInImageUrl") + .IsRequired() + .HasColumnType("longtext") + .HasComment("รูปถ่ายสถานที่ Check-In"); + + b.Property("CheckInLat") + .HasColumnType("double") + .HasComment("พิกัดละติจูด Check-In"); + + b.Property("CheckInLocationName") + .HasColumnType("longtext") + .HasComment("กรณีเลือกนอกสถานที่ตั้ง ต้องระบุข้อมูลชื่อสถานะที่ Check-In"); + + b.Property("CheckInLon") + .HasColumnType("double") + .HasComment("พิกัดลองจิจูด Check-In"); + + b.Property("CheckInPOI") + .IsRequired() + .HasColumnType("longtext") + .HasComment("ชื่อสถานที่ ได้มาจากระบบ ArcGis ของกองสารสนเทศภูมิศาสตร์ Check-In"); + + b.Property("CheckInRemark") + .HasColumnType("longtext") + .HasComment("ข้อความหมายเหตุที่ต้องการระบุเพิ่ม(มีเผื่อไว้อาจไม่ได้ใช้) Check-In"); + + b.Property("CheckOut") + .HasColumnType("datetime(6)") + .HasComment("วัน เวลา ออกงาน"); + + b.Property("CheckOutImageUrl") + .IsRequired() + .HasColumnType("longtext") + .HasComment("รูปถ่ายสถานที่ Check-Out"); + + b.Property("CheckOutLat") + .HasColumnType("double") + .HasComment("พิกัดละติจูด Check-Out"); + + b.Property("CheckOutLocationName") + .HasColumnType("longtext") + .HasComment("กรณีเลือกนอกสถานที่ตั้ง ต้องระบุข้อมูลชื่อสถานะที่ Check-Out"); + + b.Property("CheckOutLon") + .HasColumnType("double") + .HasComment("พิกัดลองจิจูด Check-Out"); + + b.Property("CheckOutPOI") + .IsRequired() + .HasColumnType("longtext") + .HasComment("ชื่อสถานที่ ได้มาจากระบบ ArcGis ของกองสารสนเทศภูมิศาสตร์ Check-Out"); + + b.Property("CheckOutRemark") + .HasColumnType("longtext") + .HasComment("ข้อความหมายเหตุที่ต้องการระบุเพิ่ม(มีเผื่อไว้อาจไม่ได้ใช้) Check-Out"); + + b.Property("Child1") + .HasColumnType("longtext"); + + b.Property("Child1DnaId") + .HasColumnType("char(36)"); + + b.Property("Child1Id") + .HasColumnType("char(36)"); + + b.Property("Child2") + .HasColumnType("longtext"); + + b.Property("Child2DnaId") + .HasColumnType("char(36)"); + + b.Property("Child2Id") + .HasColumnType("char(36)"); + + b.Property("Child3") + .HasColumnType("longtext"); + + b.Property("Child3DnaId") + .HasColumnType("char(36)"); + + b.Property("Child3Id") + .HasColumnType("char(36)"); + + b.Property("Child4") + .HasColumnType("longtext"); + + b.Property("Child4DnaId") + .HasColumnType("char(36)"); + + b.Property("Child4Id") + .HasColumnType("char(36)"); + + b.Property("CitizenId") + .HasColumnType("longtext"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)") + .HasColumnOrder(100) + .HasComment("สร้างข้อมูลเมื่อ"); + + b.Property("CreatedFullName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)") + .HasColumnOrder(104) + .HasComment("ชื่อ User ที่สร้างข้อมูล"); + + b.Property("CreatedUserId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("varchar(40)") + .HasColumnOrder(101) + .HasComment("User Id ที่สร้างข้อมูล"); + + b.Property("FirstName") + .HasColumnType("longtext"); + + b.Property("Gender") + .HasColumnType("longtext"); + + b.Property("IsLocationCheckIn") + .HasColumnType("tinyint(1)") + .HasComment("true คือ ณ สถานที่ตั้ง, false คือ นอกสถานที่ตั้ง Check-In"); + + b.Property("IsLocationCheckOut") + .HasColumnType("tinyint(1)") + .HasComment("true คือ ณ สถานที่ตั้ง, false คือ นอกสถานที่ตั้ง Check-Out"); + + b.Property("IsProcess") + .HasColumnType("tinyint(1)") + .HasComment("นำไปประมวลผลแล้วหรือยัง"); + + b.Property("KeycloakUserId") + .HasColumnType("char(36)") + .HasComment("รหัส User ของ Keycloak"); + + b.Property("LastName") + .HasColumnType("longtext"); + + b.Property("LastUpdateFullName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)") + .HasColumnOrder(105) + .HasComment("ชื่อ User ที่แก้ไขข้อมูลล่าสุด"); + + b.Property("LastUpdateUserId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("varchar(40)") + .HasColumnOrder(103) + .HasComment("User Id ที่แก้ไขข้อมูลล่าสุด"); + + b.Property("LastUpdatedAt") + .HasColumnType("datetime(6)") + .HasColumnOrder(102) + .HasComment("แก้ไขข้อมูลล่าสุดเมื่อ"); + + b.Property("Prefix") + .HasColumnType("longtext"); + + b.Property("ProfileId") + .HasColumnType("char(36)"); + + b.Property("ProfileType") + .HasColumnType("longtext"); + + b.Property("Root") + .HasColumnType("longtext"); + + b.Property("RootDnaId") + .HasColumnType("char(36)"); + + b.Property("RootId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.ToTable("UserTimeStamps"); + }); + + modelBuilder.Entity("BMA.EHR.Domain.Models.Leave.Requests.LeaveBeginning", b => + { + b.HasOne("BMA.EHR.Domain.Models.Leave.Commons.LeaveType", "LeaveType") + .WithMany() + .HasForeignKey("LeaveTypeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("LeaveType"); + }); + + modelBuilder.Entity("BMA.EHR.Domain.Models.Leave.Requests.LeaveDocument", b => + { + b.HasOne("BMA.EHR.Domain.Models.Documents.Document", "Document") + .WithMany() + .HasForeignKey("DocumentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("BMA.EHR.Domain.Models.Leave.Requests.LeaveRequest", "LeaveRequest") + .WithMany("LeaveDocument") + .HasForeignKey("LeaveRequestId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Document"); + + b.Navigation("LeaveRequest"); + }); + + modelBuilder.Entity("BMA.EHR.Domain.Models.Leave.Requests.LeaveRequest", b => + { + b.HasOne("BMA.EHR.Domain.Models.Documents.Document", "LeaveCancelDocument") + .WithMany() + .HasForeignKey("LeaveCancelDocumentId"); + + b.HasOne("BMA.EHR.Domain.Models.Documents.Document", "LeaveDraftDocument") + .WithMany() + .HasForeignKey("LeaveDraftDocumentId"); + + b.HasOne("BMA.EHR.Domain.Models.Leave.Commons.LeaveType", "Type") + .WithMany() + .HasForeignKey("TypeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("LeaveCancelDocument"); + + b.Navigation("LeaveDraftDocument"); + + b.Navigation("Type"); + }); + + modelBuilder.Entity("BMA.EHR.Domain.Models.Leave.Requests.LeaveRequestApprover", b => + { + b.HasOne("BMA.EHR.Domain.Models.Leave.Requests.LeaveRequest", "LeaveRequest") + .WithMany("Approvers") + .HasForeignKey("LeaveRequestId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("LeaveRequest"); + }); + + modelBuilder.Entity("BMA.EHR.Domain.Models.Leave.TimeAttendants.UserDutyTime", b => + { + b.HasOne("BMA.EHR.Domain.Models.Leave.TimeAttendants.DutyTime", "DutyTime") + .WithMany() + .HasForeignKey("DutyTimeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("DutyTime"); + }); + + modelBuilder.Entity("BMA.EHR.Domain.Models.Leave.Requests.LeaveRequest", b => + { + b.Navigation("Approvers"); + + b.Navigation("LeaveDocument"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/BMA.EHR.Infrastructure/Migrations/LeaveDb/20260205034753_Add LeaveCount to LeaveBeginning.cs b/BMA.EHR.Infrastructure/Migrations/LeaveDb/20260205034753_Add LeaveCount to LeaveBeginning.cs new file mode 100644 index 00000000..35fecaea --- /dev/null +++ b/BMA.EHR.Infrastructure/Migrations/LeaveDb/20260205034753_Add LeaveCount to LeaveBeginning.cs @@ -0,0 +1,30 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace BMA.EHR.Infrastructure.Migrations.LeaveDb +{ + /// + public partial class AddLeaveCounttoLeaveBeginning : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "LeaveCount", + table: "LeaveBeginnings", + type: "int", + nullable: false, + defaultValue: 0, + comment: "จำนวนครั้งที่ลาสะสม"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "LeaveCount", + table: "LeaveBeginnings"); + } + } +} diff --git a/BMA.EHR.Infrastructure/Migrations/LeaveDb/LeaveDbContextModelSnapshot.cs b/BMA.EHR.Infrastructure/Migrations/LeaveDb/LeaveDbContextModelSnapshot.cs index d12bf747..b59264fc 100644 --- a/BMA.EHR.Infrastructure/Migrations/LeaveDb/LeaveDbContextModelSnapshot.cs +++ b/BMA.EHR.Infrastructure/Migrations/LeaveDb/LeaveDbContextModelSnapshot.cs @@ -184,6 +184,10 @@ namespace BMA.EHR.Infrastructure.Migrations.LeaveDb .HasColumnOrder(102) .HasComment("แก้ไขข้อมูลล่าสุดเมื่อ"); + b.Property("LeaveCount") + .HasColumnType("int") + .HasComment("จำนวนครั้งที่ลาสะสม"); + b.Property("LeaveDays") .HasColumnType("double") .HasComment("จำนวนวันลายกมา"); diff --git a/BMA.EHR.Leave/Controllers/LeaveBeginningController.cs b/BMA.EHR.Leave/Controllers/LeaveBeginningController.cs index 4c37f19d..3a3140e8 100644 --- a/BMA.EHR.Leave/Controllers/LeaveBeginningController.cs +++ b/BMA.EHR.Leave/Controllers/LeaveBeginningController.cs @@ -201,6 +201,7 @@ namespace BMA.EHR.Leave.Service.Controllers item.LeaveYear, item.LeaveDays, item.LeaveDaysUsed, + item.LeaveCount, item.CreatedAt, item.CreatedFullName, item.LastUpdatedAt, @@ -394,6 +395,7 @@ namespace BMA.EHR.Leave.Service.Controllers leaveBeginning.LeaveYear = req.LeaveYear; leaveBeginning.LeaveDays = req.LeaveDays; leaveBeginning.LeaveDaysUsed = req.LeaveDaysUsed; + leaveBeginning.LeaveCount = req.LeaveCount; leaveBeginning.ProfileId = req.ProfileId; leaveBeginning.Prefix = profile.Prefix; @@ -462,6 +464,7 @@ namespace BMA.EHR.Leave.Service.Controllers leaveBeginning.LeaveYear = req.LeaveYear; leaveBeginning.LeaveDays = req.LeaveDays; leaveBeginning.LeaveDaysUsed = req.LeaveDaysUsed; + leaveBeginning.LeaveCount = req.LeaveCount; leaveBeginning.ProfileId = req.ProfileId; leaveBeginning.Prefix = profile.Prefix; diff --git a/BMA.EHR.Leave/DTOs/LeaveBeginnings/EditLeaveBeginningDto.cs b/BMA.EHR.Leave/DTOs/LeaveBeginnings/EditLeaveBeginningDto.cs index 6ecfe1f4..a73bdfd4 100644 --- a/BMA.EHR.Leave/DTOs/LeaveBeginnings/EditLeaveBeginningDto.cs +++ b/BMA.EHR.Leave/DTOs/LeaveBeginnings/EditLeaveBeginningDto.cs @@ -19,5 +19,8 @@ namespace BMA.EHR.Leave.Service.DTOs.LeaveBeginnings [Required, Comment("จำนวนวันลาที่ใช้ไป")] public double LeaveDaysUsed { get; set; } = 0.0; + + [Required, Comment("จำนวนครั้งที่ลาสะสม")] + public int LeaveCount { get; set; } = 0; } } From d3cc0781cfad3ee47a7a9a9961de56f66dd1c064 Mon Sep 17 00:00:00 2001 From: Suphonchai Phoonsawat Date: Thu, 5 Feb 2026 11:01:49 +0700 Subject: [PATCH 092/183] Add UpdateLeaveCountAsync method to LeaveBeginningRepository and integrate it into leave request logic #2288 --- .../LeaveRequests/LeaveBeginingRepository.cs | 22 +++++++++++++++++++ .../LeaveRequests/LeaveRequestRepository.cs | 4 ++++ 2 files changed, 26 insertions(+) diff --git a/BMA.EHR.Application/Repositories/Leaves/LeaveRequests/LeaveBeginingRepository.cs b/BMA.EHR.Application/Repositories/Leaves/LeaveRequests/LeaveBeginingRepository.cs index ec280b48..2d14db9c 100644 --- a/BMA.EHR.Application/Repositories/Leaves/LeaveRequests/LeaveBeginingRepository.cs +++ b/BMA.EHR.Application/Repositories/Leaves/LeaveRequests/LeaveBeginingRepository.cs @@ -99,6 +99,28 @@ namespace BMA.EHR.Application.Repositories.Leaves.LeaveRequests await _dbContext.SaveChangesAsync(); } + public async Task UpdateLeaveCountAsync(int year, Guid typeId, Guid userId, int count) + { + // var pf = await _userProfileRepository.GetProfileByKeycloakIdAsync(userId, AccessToken); + var pf = await _userProfileRepository.GetProfileByKeycloakIdNewAsync(userId, AccessToken); + if (pf == null) + { + throw new Exception(GlobalMessages.DataNotFound); + } + + var data = await _dbContext.Set() + .Include(x => x.LeaveType) + .FirstOrDefaultAsync(x => x.LeaveYear == year && x.LeaveTypeId == typeId && x.ProfileId == pf.Id); + + if (data == null) + { + throw new Exception(GlobalMessages.DataNotFound); + } + data.LeaveCount += count; + + await _dbContext.SaveChangesAsync(); + } + public async Task GetByYearAndTypeIdForUserAsync(int year, Guid typeId, Guid userId) { // var pf = await _userProfileRepository.GetProfileByKeycloakIdAsync(userId, AccessToken); diff --git a/BMA.EHR.Application/Repositories/Leaves/LeaveRequests/LeaveRequestRepository.cs b/BMA.EHR.Application/Repositories/Leaves/LeaveRequests/LeaveRequestRepository.cs index b97441be..972a228d 100644 --- a/BMA.EHR.Application/Repositories/Leaves/LeaveRequests/LeaveRequestRepository.cs +++ b/BMA.EHR.Application/Repositories/Leaves/LeaveRequests/LeaveRequestRepository.cs @@ -733,6 +733,8 @@ namespace BMA.EHR.Application.Repositories.Leaves.LeaveRequests } await _leaveBeginningRepository.UpdateLeaveUsageAsync(thisYear, rawData.Type.Id, rawData.KeycloakUserId, -1 * rawData.LeaveTotal); + // update leave count ลดลง 1 ครั้ง + await _leaveBeginningRepository.UpdateLeaveCountAsync(thisYear, rawData.Type.Id, rawData.KeycloakUserId, -1); var _baseAPI = _configuration["API"]; var apiUrlSalary = $"{_baseAPI}/org/profile/leave/cancel/{rawData.Id}"; @@ -1241,6 +1243,8 @@ namespace BMA.EHR.Application.Repositories.Leaves.LeaveRequests // TODO : Update ไปตาราง beginning await _leaveBeginningRepository.UpdateLeaveUsageAsync(thisYear, rawData.Type.Id, rawData.KeycloakUserId, rawData.LeaveTotal); + // update leave count เพิ่มขึ้น 1 ครั้ง + await _leaveBeginningRepository.UpdateLeaveCountAsync(thisYear, rawData.Type.Id, rawData.KeycloakUserId, 1); var _baseAPI = _configuration["API"]; From 4f18a97d0b886859cf2ed2491fe902a8b9e9da39 Mon Sep 17 00:00:00 2001 From: Suphonchai Phoonsawat Date: Thu, 5 Feb 2026 11:57:19 +0700 Subject: [PATCH 093/183] Add GetOCStaffAsync method to UserProfileRepository and create GetOcStaff response models --- .../Repositories/UserProfileRepository.cs | 34 ++++++- .../Responses/Profiles/GetOcStaff.cs | 35 +++++++ BMA.EHR.Leave/Controllers/LeaveController.cs | 94 ++++++++++++++++++- 3 files changed, 158 insertions(+), 5 deletions(-) create mode 100644 BMA.EHR.Application/Responses/Profiles/GetOcStaff.cs diff --git a/BMA.EHR.Application/Repositories/UserProfileRepository.cs b/BMA.EHR.Application/Repositories/UserProfileRepository.cs index 4e0e986f..db63e056 100644 --- a/BMA.EHR.Application/Repositories/UserProfileRepository.cs +++ b/BMA.EHR.Application/Repositories/UserProfileRepository.cs @@ -186,6 +186,8 @@ namespace BMA.EHR.Application.Repositories } } + + public async Task GetProfileByKeycloakIdNewAsync(Guid keycloakId, string? accessToken,CancellationToken cancellationToken = default) { try @@ -256,6 +258,36 @@ namespace BMA.EHR.Application.Repositories } } + public async Task?> GetOCStaffAsync(Guid profileId, string? accessToken) + { + try + { + var apiPath = $"{_configuration["API"]}/org/dotnet/find-staff"; + var apiKey = _configuration["API_KEY"]; + var body = new + { + assignId = "SYS_LEAVE_LIST", + profileId = profileId + }; + + //var profiles = new List(); + + var apiResult = await PostExternalAPIAsync(apiPath, accessToken ?? "", body, apiKey); + if (apiResult != null) + { + var raw = JsonConvert.DeserializeObject(apiResult); + if (raw != null) + return raw.Result; + } + + return null; + } + catch + { + throw; + } + } + public async Task GetProfileLeaveReportByKeycloakIdAsync(Guid keycloakId, string? accessToken, string? report) { try @@ -268,7 +300,7 @@ namespace BMA.EHR.Application.Repositories report = report }; - var profiles = new List(); + //var profiles = new List(); var apiResult = await PostExternalAPIAsync(apiPath, accessToken, body, apiKey); if (apiResult != null) diff --git a/BMA.EHR.Application/Responses/Profiles/GetOcStaff.cs b/BMA.EHR.Application/Responses/Profiles/GetOcStaff.cs new file mode 100644 index 00000000..fa3ce936 --- /dev/null +++ b/BMA.EHR.Application/Responses/Profiles/GetOcStaff.cs @@ -0,0 +1,35 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace BMA.EHR.Application.Responses.Profiles +{ + public class GetOcStaff + { + public Guid ProfileId { get; set; } + public Guid Keycloak { get; set; } + public string FullName { get; set; } = null!; + public Guid? RootId { get; set; } + public Guid? OrgChild1Id { get; set; } + public Guid? OrgChild2Id { get; set; } + public Guid? OrgChild3Id { get; set; } + public Guid? OrgChild4Id { get; set; } + public Guid? RootDnaId { get; set; } + public Guid? Child1DnaId { get; set; } + public Guid? Child2DnaId { get; set; } + public Guid? Child3DnaId { get; set; } + public Guid? Child4DnaId { get; set; } + + } + + public class GetOcStaffResultDto + { + public string Message { get; set; } = string.Empty; + + public int Status { get; set; } = -1; + + public List Result { get; set; } = new(); + + } +} \ No newline at end of file diff --git a/BMA.EHR.Leave/Controllers/LeaveController.cs b/BMA.EHR.Leave/Controllers/LeaveController.cs index 85b782cd..665b209c 100644 --- a/BMA.EHR.Leave/Controllers/LeaveController.cs +++ b/BMA.EHR.Leave/Controllers/LeaveController.cs @@ -6,6 +6,7 @@ using BMA.EHR.Application.Repositories.MessageQueue; using BMA.EHR.Application.Responses.Profiles; using BMA.EHR.Domain.Common; using BMA.EHR.Domain.Models.Leave.TimeAttendants; +using BMA.EHR.Domain.Models.Notifications; using BMA.EHR.Domain.Shared; using BMA.EHR.Infrastructure.Persistence; using BMA.EHR.Leave.Service.DTOs.AdditionalCheck; @@ -46,6 +47,7 @@ namespace BMA.EHR.Leave.Service.Controllers private readonly DutyTimeRepository _dutyTimeRepository; private readonly LeaveDbContext _context; + private readonly ApplicationDBContext _appDbContext; private readonly IHttpContextAccessor _httpContextAccessor; private readonly IWebHostEnvironment _hostingEnvironment; private readonly IConfiguration _configuration; @@ -94,10 +96,12 @@ namespace BMA.EHR.Leave.Service.Controllers PermissionRepository permission, NotificationRepository notificationRepository, CheckInJobStatusRepository checkInJobStatusRepository, - HttpClient httpClient) + HttpClient httpClient, + ApplicationDBContext appDbContext) { _dutyTimeRepository = dutyTimeRepository; _context = context; + _appDbContext = appDbContext; _httpContextAccessor = httpContextAccessor; _hostingEnvironment = hostingEnvironment; _configuration = configuration; @@ -928,16 +932,31 @@ namespace BMA.EHR.Leave.Service.Controllers if (profile == null) { await _checkInJobStatusRepository.UpdateToFailedAsync(taskId, "ไม่พบข้อมูลผู้ใช้"); + //var staffList = await _userProfileRepository.GetOCStaffAsync(profile) return Error(GlobalMessages.DataNotFound, StatusCodes.Status404NotFound); } + var currentDate = data.CurrentDate ?? DateTime.Now; + if (data.CheckInFileName == "no-file") { //throw new Exception(GlobalMessages.NoFileToUpload); await _checkInJobStatusRepository.UpdateToFailedAsync(taskId, GlobalMessages.NoFileToUpload); + + // send notification to user + var noti1 = new Notification + { + Body = $"ประมวลผลการลงเวลาวันที่ {currentDate.ToString("dd-MM-yyyy")} ไม่สำเร็จ \r\nเนื่องจาก {GlobalMessages.NoFileToUpload}", + ReceiverUserId = profile.Id, + Type = "", + Payload = "", + }; + _appDbContext.Set().Add(noti1); + await _appDbContext.SaveChangesAsync(); + return Error(GlobalMessages.NoFileToUpload, StatusCodes.Status400BadRequest); } - var currentDate = data.CurrentDate ?? DateTime.Now; + var check_status = data.CheckInId == null ? "check-in-picture" : "check-out-picture"; @@ -951,6 +970,19 @@ namespace BMA.EHR.Leave.Service.Controllers catch (Exception ex) { await _checkInJobStatusRepository.UpdateToFailedAsync(taskId, $"ไม่สามารถอัปโหลดรูปภาพได้: {ex.Message}"); + + // send notification to user + var noti1 = new Notification + { + Body = $"ประมวลผลการลงเวลาวันที่ {currentDate.ToString("dd-MM-yyyy")} ไม่สำเร็จ \r\nเนื่องจากไม่สามารถอัปโหลดรูปภาพได้ {ex.Message}", + ReceiverUserId = profile.Id, + Type = "", + Payload = "", + }; + _appDbContext.Set().Add(noti1); + await _appDbContext.SaveChangesAsync(); + + return Error($"ไม่สามารถอัปโหลดรูปภาพได้: {ex.Message}", StatusCodes.Status500InternalServerError); } @@ -960,6 +992,16 @@ namespace BMA.EHR.Leave.Service.Controllers if (defaultRound == null) { await _checkInJobStatusRepository.UpdateToFailedAsync(taskId, "ไม่พบรอบการลงเวลาทำงาน Default"); + // send notification to user + var noti1 = new Notification + { + Body = $"ประมวลผลการลงเวลาวันที่ {currentDate.ToString("dd-MM-yyyy")} ไม่สำเร็จ \r\nเนื่องจากไม่พบรอบการลงเวลาทำงาน Default", + ReceiverUserId = profile.Id, + Type = "", + Payload = "", + }; + _appDbContext.Set().Add(noti1); + await _appDbContext.SaveChangesAsync(); return Error("ไม่พบรอบการลงเวลาทำงาน Default", StatusCodes.Status404NotFound); } @@ -979,6 +1021,18 @@ namespace BMA.EHR.Leave.Service.Controllers if (currentCheckIn != null) { await _checkInJobStatusRepository.UpdateToFailedAsync(taskId, "ไม่สามารถลงเวลาได้ เนื่องจากมีการลงเวลาในวันนี้แล้ว"); + + // send notification to user + var noti1 = new Notification + { + Body = $"ประมวลผลการลงเวลาวันที่ {currentDate.ToString("dd-MM-yyyy")} ไม่สำเร็จ \r\nเนื่องจากมีการลงเวลาในวันนี้แล้ว", + ReceiverUserId = profile.Id, + Type = "", + Payload = "", + }; + _appDbContext.Set().Add(noti1); + await _appDbContext.SaveChangesAsync(); + return Error(new Exception("ไม่สามารถลงเวลาได้ เนื่องจากมีการลงเวลาในวันนี้แล้ว!"), StatusCodes.Status400BadRequest); } @@ -1117,6 +1171,18 @@ namespace BMA.EHR.Leave.Service.Controllers if (checkout == null) { await _checkInJobStatusRepository.UpdateToFailedAsync(taskId, "ไม่พบข้อมูลการลงเวลาทำงาน"); + + // send notification to user + var noti1 = new Notification + { + Body = $"ประมวลผลการลงเวลาวันที่ {currentDate.ToString("dd-MM-yyyy")} ไม่สำเร็จ \r\nเนื่องจากไม่พบข้อมูลการลงเวลาทำงาน", + ReceiverUserId = profile.Id, + Type = "", + Payload = "", + }; + _appDbContext.Set().Add(noti1); + await _appDbContext.SaveChangesAsync(); + return Error(new Exception(GlobalMessages.DataNotFound), StatusCodes.Status404NotFound); } @@ -1125,6 +1191,18 @@ namespace BMA.EHR.Leave.Service.Controllers if (currentCheckInProcess == null) { await _checkInJobStatusRepository.UpdateToFailedAsync(taskId, "ไม่พบข้อมูลการประมวลผลเวลาทำงาน (CheckIn)"); + + // send notification to user + var noti1 = new Notification + { + Body = $"ประมวลผลการลงเวลาวันที่ {currentDate.ToString("dd-MM-yyyy")} ไม่สำเร็จ \r\nเนื่องจากไม่พบข้อมูลการประมวลผลเวลาทำงาน (CheckIn)", + ReceiverUserId = profile.Id, + Type = "", + Payload = "", + }; + _appDbContext.Set().Add(noti1); + await _appDbContext.SaveChangesAsync(); + return Error(new Exception(GlobalMessages.DataNotFound), StatusCodes.Status404NotFound); } @@ -1275,11 +1353,19 @@ namespace BMA.EHR.Leave.Service.Controllers else { await _checkInJobStatusRepository.UpdateToFailedAsync(taskId, "ไม่พบข้อมูลการประมวลผลเวลาทำงาน"); + // send notification to user + var noti1 = new Notification + { + Body = $"ประมวลผลการลงเวลาวันที่ {currentDate.ToString("dd-MM-yyyy")} ไม่สำเร็จ \r\nเนื่องจากไม่พบข้อมูลการประมวลผลเวลาทำงาน", + ReceiverUserId = profile.Id, + Type = "", + Payload = "", + }; + _appDbContext.Set().Add(noti1); + await _appDbContext.SaveChangesAsync(); return Error(new Exception(GlobalMessages.DataNotFound), StatusCodes.Status404NotFound); } - } - // อัปเดตสถานะเป็น COMPLETED if (taskId != Guid.Empty) { From c693364fe128bbb4f2c81e3176411fa79aff2464 Mon Sep 17 00:00:00 2001 From: Suphonchai Phoonsawat Date: Thu, 5 Feb 2026 12:03:48 +0700 Subject: [PATCH 094/183] Refactor LeaveReportController to use LeaveCount instead of CountLeaveDay for leave types --- .../Controllers/LeaveReportController.cs | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/BMA.EHR.Leave/Controllers/LeaveReportController.cs b/BMA.EHR.Leave/Controllers/LeaveReportController.cs index 2a113d94..4e5a0961 100644 --- a/BMA.EHR.Leave/Controllers/LeaveReportController.cs +++ b/BMA.EHR.Leave/Controllers/LeaveReportController.cs @@ -1389,57 +1389,57 @@ namespace BMA.EHR.Leave.Service.Controllers var sickDaySum = beginningData.FirstOrDefault(x => x.ProfileId == p.Id && x.LeaveType.Code == "LV-001"); var sickDay = leaveDays.FirstOrDefault(x => x.KeycloakUserId == keycloakUserId && x.LeaveTypeCode == "LV-001"); var sickDayCount = sickDaySum != null ? sickDaySum.LeaveDaysUsed : 0; - var sickCount = sickDay != null ? sickDay.CountLeaveDay : 0; + var sickCount = sickDaySum != null ? sickDaySum.LeaveCount : 0; var personalDaySum = beginningData.FirstOrDefault(x => x.ProfileId == p.Id && x.LeaveType.Code == "LV-002"); var personalDay = leaveDays.FirstOrDefault(x => x.KeycloakUserId == keycloakUserId && x.LeaveTypeCode == "LV-002"); var personalDayCount = personalDaySum != null ? personalDaySum.LeaveDaysUsed : 0; - var personalCount = personalDay != null ? personalDay.CountLeaveDay : 0; + var personalCount = personalDaySum != null ? personalDaySum.LeaveCount : 0; var maternityDaySum = beginningData.FirstOrDefault(x => x.ProfileId == p.Id && x.LeaveType.Code == "LV-003"); var maternityDay = leaveDays.FirstOrDefault(x => x.KeycloakUserId == keycloakUserId && x.LeaveTypeCode == "LV-003"); var maternityDayCount = maternityDaySum != null ? maternityDaySum.LeaveDaysUsed : 0; - var maternityCount = maternityDay != null ? maternityDay.CountLeaveDay : 0; + var maternityCount = maternityDaySum != null ? maternityDaySum.LeaveCount : 0; var wifeDaySum = beginningData.FirstOrDefault(x => x.ProfileId == p.Id && x.LeaveType.Code == "LV-004"); var wifeDay = leaveDays.FirstOrDefault(x => x.KeycloakUserId == keycloakUserId && x.LeaveTypeCode == "LV-004"); var wifeDayCount = wifeDaySum != null ? wifeDaySum.LeaveDaysUsed : 0; - var wifeCount = wifeDay != null ? wifeDay.CountLeaveDay : 0; + var wifeCount = wifeDaySum != null ? wifeDaySum.LeaveCount : 0; var restDaySum = beginningData.FirstOrDefault(x => x.ProfileId == p.Id && x.LeaveType.Code == "LV-005"); var restDay = leaveDays.FirstOrDefault(x => x.KeycloakUserId == keycloakUserId && x.LeaveTypeCode == "LV-005"); var restDayCount = restDaySum != null ? restDaySum.LeaveDaysUsed : 0; - var restCount = restDay != null ? restDay.CountLeaveDay : 0; + var restCount = restDaySum != null ? restDaySum.LeaveCount : 0; var ordainDaySum = beginningData.FirstOrDefault(x => x.ProfileId == p.Id && x.LeaveType.Code == "LV-006"); var ordainDay = leaveDays.FirstOrDefault(x => x.KeycloakUserId == keycloakUserId && x.LeaveTypeCode == "LV-006"); var ordainDayCount = ordainDaySum != null ? ordainDaySum.LeaveDaysUsed : 0; - var ordainCount = ordainDay != null ? ordainDay.CountLeaveDay : 0; + var ordainCount = ordainDaySum != null ? ordainDaySum.LeaveCount : 0; var absentDaySum = beginningData.FirstOrDefault(x => x.ProfileId == p.Id && x.LeaveType.Code == "LV-007"); var absentDay = leaveDays.FirstOrDefault(x => x.KeycloakUserId == keycloakUserId && x.LeaveTypeCode == "LV-007"); var absentDayCount = absentDaySum != null ? absentDaySum.LeaveDaysUsed : 0; - var absentCount = absentDay != null ? absentDay.CountLeaveDay : 0; + var absentCount = absentDaySum != null ? absentDaySum.LeaveCount : 0; var studyDaySum = beginningData.FirstOrDefault(x => x.ProfileId == p.Id && x.LeaveType.Code == "LV-008"); var studyDay = leaveDays.FirstOrDefault(x => x.KeycloakUserId == keycloakUserId && x.LeaveTypeCode == "LV-008"); var studyDayCount = studyDaySum != null ? studyDaySum.LeaveDaysUsed : 0; - var studyCount = studyDay != null ? studyDay.CountLeaveDay : 0; + var studyCount = studyDaySum != null ? studyDaySum.LeaveCount : 0; var agencyDaySum = beginningData.FirstOrDefault(x => x.ProfileId == p.Id && x.LeaveType.Code == "LV-009"); var agencyDay = leaveDays.FirstOrDefault(x => x.KeycloakUserId == keycloakUserId && x.LeaveTypeCode == "LV-009"); var agencyDayCount = agencyDaySum != null ? agencyDaySum.LeaveDaysUsed : 0; - var agencyCount = agencyDay != null ? agencyDay.CountLeaveDay : 0; + var agencyCount = agencyDaySum != null ? agencyDaySum.LeaveCount : 0; var coupleDaySum = beginningData.FirstOrDefault(x => x.ProfileId == p.Id && x.LeaveType.Code == "LV-010"); var coupleDay = leaveDays.FirstOrDefault(x => x.KeycloakUserId == keycloakUserId && x.LeaveTypeCode == "LV-010"); var coupleDayCount = coupleDaySum != null ? coupleDaySum.LeaveDaysUsed : 0; - var coupleCount = coupleDay != null ? coupleDay.CountLeaveDay : 0; + var coupleCount = coupleDaySum != null ? coupleDaySum.LeaveCount : 0; var therapyDaySum = beginningData.FirstOrDefault(x => x.ProfileId == p.Id && x.LeaveType.Code == "LV-011"); var therapyDay = leaveDays.FirstOrDefault(x => x.KeycloakUserId == keycloakUserId && x.LeaveTypeCode == "LV-011"); var therapyDayCount = therapyDaySum != null ? therapyDaySum.LeaveDaysUsed : 0; - var therapyCount = therapyDay != null ? therapyDay.CountLeaveDay : 0; + var therapyCount = therapyDaySum != null ? therapyDaySum.LeaveCount : 0; var timeStamps = await _processUserTimeStampRepository.GetTimeStampHistoryByRangeForUserAsync(p.Keycloak ?? Guid.Empty, req.StartDate, req.EndDate); From 1d8ef79373191a6b656e0df46aaac710aae899c4 Mon Sep 17 00:00:00 2001 From: harid Date: Fri, 6 Feb 2026 10:18:47 +0700 Subject: [PATCH 095/183] =?UTF-8?q?api=20=E0=B8=AD=E0=B8=B1=E0=B8=9E?= =?UTF-8?q?=E0=B9=80=E0=B8=94=E0=B8=97=E0=B8=AA=E0=B8=96=E0=B8=B2=E0=B8=99?= =?UTF-8?q?=E0=B8=B0=E0=B9=80=E0=B8=9B=E0=B9=87=E0=B8=99=E0=B8=9A=E0=B8=A3?= =?UTF-8?q?=E0=B8=A3=E0=B8=88=E0=B8=B8=20=E0=B9=80=E0=B8=9B=E0=B8=A5?= =?UTF-8?q?=E0=B8=B5=E0=B9=88=E0=B8=A2=E0=B8=99=E0=B8=AA=E0=B8=B4=E0=B8=97?= =?UTF-8?q?=E0=B8=98=E0=B8=B4=E0=B9=8C=E0=B8=88=E0=B8=B2=E0=B8=81=20super?= =?UTF-8?q?=5Fadmin=20=E0=B9=80=E0=B8=9B=E0=B9=87=E0=B8=99=20owner?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Controllers/PlacementController.cs | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/BMA.EHR.Placement.Service/Controllers/PlacementController.cs b/BMA.EHR.Placement.Service/Controllers/PlacementController.cs index 1a9260ef..51a09094 100644 --- a/BMA.EHR.Placement.Service/Controllers/PlacementController.cs +++ b/BMA.EHR.Placement.Service/Controllers/PlacementController.cs @@ -64,7 +64,7 @@ namespace BMA.EHR.Placement.Service.Controllers private string? UserId => _httpContextAccessor?.HttpContext?.User?.FindFirst(ClaimTypes.NameIdentifier)?.Value; private string? FullName => _httpContextAccessor?.HttpContext?.User?.FindFirst("name")?.Value; private string? token => _httpContextAccessor.HttpContext.Request.Headers["Authorization"]; - private bool isSuperAdmin => _httpContextAccessor?.HttpContext?.User?.IsInRole("SUPER_ADMIN") ?? false; + //private bool isSuperAdmin => _httpContextAccessor?.HttpContext?.User?.IsInRole("SUPER_ADMIN") ?? false; #endregion @@ -867,8 +867,17 @@ namespace BMA.EHR.Placement.Service.Controllers [ProducesResponseType(StatusCodes.Status500InternalServerError)] public async Task> PersonUpdateStatus([FromBody] PersonUpdateStatusRequest req) { - if (isSuperAdmin == false) - return Success(); + var getPermission = await _permission.GetPermissionAPIAsync("UPDATE", "SYS_PLACEMENT_PASS"); + var jsonData = JsonConvert.DeserializeObject(getPermission); + if (jsonData["status"]?.ToString() != "200") + { + return Error(jsonData["message"]?.ToString(), StatusCodes.Status403Forbidden); + } + string role = jsonData["result"]?.ToString(); + if (role != "OWNER") + { + return Error(jsonData["message"]?.ToString(), StatusCodes.Status403Forbidden); + } var person = await _context.PlacementProfiles .FirstOrDefaultAsync(x => x.Id == req.PersonalId); From 35310f78548a0c60045f3bb4afd3324d2ff55e9c Mon Sep 17 00:00:00 2001 From: harid Date: Tue, 10 Feb 2026 12:07:32 +0700 Subject: [PATCH 096/183] =?UTF-8?q?Fix=20=E0=B8=9B=E0=B8=A3=E0=B8=B0?= =?UTF-8?q?=E0=B8=81=E0=B8=B2=E0=B8=A8=E0=B9=80=E0=B8=81=E0=B8=A9=E0=B8=B5?= =?UTF-8?q?=E0=B8=A2=E0=B8=93=E0=B8=A5=E0=B8=B9=E0=B8=81=E0=B8=88=E0=B9=89?= =?UTF-8?q?=E0=B8=B2=E0=B8=87=20=E0=B8=9A=E0=B8=B1=E0=B8=99=E0=B8=97?= =?UTF-8?q?=E0=B8=B6=E0=B8=81=E0=B8=82=E0=B9=89=E0=B8=AD=E0=B8=A1=E0=B8=B9?= =?UTF-8?q?=E0=B8=A5=E0=B8=A7=E0=B8=B1=E0=B8=99=E0=B8=97=E0=B8=B5=E0=B9=88?= =?UTF-8?q?=E0=B8=9B=E0=B8=A3=E0=B8=B0=E0=B8=81=E0=B8=B2=E0=B8=A8=20?= =?UTF-8?q?=E0=B8=A3=E0=B8=B0=E0=B8=9A=E0=B8=9A=E0=B9=81=E0=B8=88=E0=B9=89?= =?UTF-8?q?=E0=B8=87=20Error=20#2260?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Controllers/RetirementController.cs | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/BMA.EHR.Retirement.Service/Controllers/RetirementController.cs b/BMA.EHR.Retirement.Service/Controllers/RetirementController.cs index 74f217a2..ad3105ba 100644 --- a/BMA.EHR.Retirement.Service/Controllers/RetirementController.cs +++ b/BMA.EHR.Retirement.Service/Controllers/RetirementController.cs @@ -1306,12 +1306,20 @@ namespace BMA.EHR.Retirement.Service.Controllers { return Error(jsonData["message"]?.ToString(), StatusCodes.Status403Forbidden); } + + // แยกดึงข้อมูลเพื่อลดภาระ Database var retire = await _context.RetirementPeriods - .Include(x => x.RetirementProfiles) - .Include(x => x.RetirementRawProfiles) + //.Include(x => x.RetirementProfiles) + //.Include(x => x.RetirementRawProfiles) .FirstOrDefaultAsync(x => x.Id == retireId); + if (retire == null) return Error(GlobalMessages.RetirementNotFound, 404); + + // โหลดข้อมูลลูกแยกกัน + var profiles = await _context.RetirementProfiles.Where(x => x.RetirementPeriod.Id == retireId).ToListAsync(); + var rawProfiles = await _context.RetirementRawProfiles.Where(x => x.RetirementPeriod.Id == retireId).ToListAsync(); + if (Request.Form.Files != null && Request.Form.Files.Count != 0) { var file = Request.Form.Files[0]; @@ -1366,7 +1374,7 @@ namespace BMA.EHR.Retirement.Service.Controllers } var order = 1; - foreach (var profile in retire.RetirementProfiles + foreach (var profile in profiles .OrderBy(x => string.IsNullOrEmpty(x.root) ? int.MaxValue : rootOrder.ToObject>().IndexOf(x.root)) .ThenBy(x => child1Order.ToObject>().IndexOf(x.child1 ?? "")) .ThenBy(x => child2Order.ToObject>().IndexOf(x.child2 ?? "")) @@ -1381,7 +1389,7 @@ namespace BMA.EHR.Retirement.Service.Controllers } order = 1; - foreach (var profile in retire.RetirementRawProfiles + foreach (var profile in rawProfiles .OrderBy(x => string.IsNullOrEmpty(x.root) ? int.MaxValue : rootOrder.ToObject>().IndexOf(x.root)) .ThenBy(x => child1Order.ToObject>().IndexOf(x.child1 ?? "")) .ThenBy(x => child2Order.ToObject>().IndexOf(x.child2 ?? "")) From 05ec0cccced9255571d3074273edf312e107801c Mon Sep 17 00:00:00 2001 From: harid Date: Tue, 10 Feb 2026 15:29:35 +0700 Subject: [PATCH 097/183] =?UTF-8?q?Fix=20Bug=20=E0=B8=88=E0=B8=B3=E0=B8=99?= =?UTF-8?q?=E0=B8=A7=E0=B8=99=E0=B8=84=E0=B8=99=E0=B8=A5=E0=B8=B2=E0=B9=84?= =?UTF-8?q?=E0=B8=A1=E0=B9=88=E0=B9=81=E0=B8=AA=E0=B8=94=E0=B8=87=E0=B9=83?= =?UTF-8?q?=E0=B8=99=E0=B8=A3=E0=B8=B2=E0=B8=A2=E0=B8=87=E0=B8=B2=E0=B8=99?= =?UTF-8?q?=20#2299?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Repositories/Leaves/LeaveRequests/LeaveRequestRepository.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BMA.EHR.Application/Repositories/Leaves/LeaveRequests/LeaveRequestRepository.cs b/BMA.EHR.Application/Repositories/Leaves/LeaveRequests/LeaveRequestRepository.cs index 972a228d..57a7fdc4 100644 --- a/BMA.EHR.Application/Repositories/Leaves/LeaveRequests/LeaveRequestRepository.cs +++ b/BMA.EHR.Application/Repositories/Leaves/LeaveRequests/LeaveRequestRepository.cs @@ -1694,7 +1694,7 @@ namespace BMA.EHR.Application.Repositories.Leaves.LeaveRequests if (role == "ROOT" || role == "OWNER" || role == "CHILD" || role == "BROTHER" || role == "PARENT") { data = data - .Where(x => nodeByReq == 4 ? x.Child4Id == Guid.Parse(nodeIdByReq) : nodeByReq == 3 ? x.Child3Id == Guid.Parse(nodeIdByReq) : nodeByReq == 2 ? x.Child2Id == Guid.Parse(nodeIdByReq) : nodeByReq == 1 ? x.Child1Id == Guid.Parse(nodeIdByReq) : nodeByReq == 0 ? x.RootId == Guid.Parse(nodeIdByReq) : true) + .Where(x => nodeByReq == 4 ? x.Child4DnaId == Guid.Parse(nodeIdByReq) : nodeByReq == 3 ? x.Child3DnaId == Guid.Parse(nodeIdByReq) : nodeByReq == 2 ? x.Child2DnaId == Guid.Parse(nodeIdByReq) : nodeByReq == 1 ? x.Child1DnaId == Guid.Parse(nodeIdByReq) : nodeByReq == 0 ? x.RootDnaId == Guid.Parse(nodeIdByReq) : true) .ToList(); } // รายงานการลางานจำแนกตามเพศฯ Template ให้หน่วยงานแสดงก่อนส่วนราชการ From 682c88c2dbc196622ca80dcdd47cea12449f8252 Mon Sep 17 00:00:00 2001 From: Suphonchai Phoonsawat Date: Wed, 11 Feb 2026 09:44:18 +0700 Subject: [PATCH 098/183] Add BeginningLeaveCount and BeginningLeaveDays to LeaveBeginnings table - Altered LeaveDays column to update its comment. - Added BeginningLeaveCount column to track the number of leave occurrences. - Added BeginningLeaveDays column to store the total days of leave carried over. --- .../Models/Leave/Requests/LeaveBeginning.cs | 8 +- ...d LeaveCount to LeaveBeginning.Designer.cs | 1717 +++++++++++++++++ ...gLeave and LeaveCount to LeaveBeginning.cs | 62 + .../LeaveDb/LeaveDbContextModelSnapshot.cs | 10 +- 4 files changed, 1795 insertions(+), 2 deletions(-) create mode 100644 BMA.EHR.Infrastructure/Migrations/LeaveDb/20260210091134_Add BeginningLeave and LeaveCount to LeaveBeginning.Designer.cs create mode 100644 BMA.EHR.Infrastructure/Migrations/LeaveDb/20260210091134_Add BeginningLeave and LeaveCount to LeaveBeginning.cs diff --git a/BMA.EHR.Domain/Models/Leave/Requests/LeaveBeginning.cs b/BMA.EHR.Domain/Models/Leave/Requests/LeaveBeginning.cs index d68f0229..153b7d22 100644 --- a/BMA.EHR.Domain/Models/Leave/Requests/LeaveBeginning.cs +++ b/BMA.EHR.Domain/Models/Leave/Requests/LeaveBeginning.cs @@ -24,7 +24,7 @@ namespace BMA.EHR.Domain.Models.Leave.Requests [Required, Comment("ปีงบประมาณ")] public int LeaveYear { get; set; } = 0; - [Required, Comment("จำนวนวันลายกมา")] + [Required, Comment("จำนวนวันลาทั้งหมด")] public double LeaveDays { get; set; } = 0.0; [Required, Comment("จำนวนวันลาที่ใช้ไป")] @@ -42,5 +42,11 @@ namespace BMA.EHR.Domain.Models.Leave.Requests public Guid? Child3DnaId { get; set; } public Guid? Child4DnaId { get; set; } + + [Required, Comment("จำนวนวันลายกมา")] + public double BeginningLeaveDays { get; set; } = 0.0; + + [Comment("จำนวนครั้งที่ลายกมา")] + public int BeginningLeaveCount { get; set; } = 0; } } diff --git a/BMA.EHR.Infrastructure/Migrations/LeaveDb/20260210091134_Add BeginningLeave and LeaveCount to LeaveBeginning.Designer.cs b/BMA.EHR.Infrastructure/Migrations/LeaveDb/20260210091134_Add BeginningLeave and LeaveCount to LeaveBeginning.Designer.cs new file mode 100644 index 00000000..934ba1a9 --- /dev/null +++ b/BMA.EHR.Infrastructure/Migrations/LeaveDb/20260210091134_Add BeginningLeave and LeaveCount to LeaveBeginning.Designer.cs @@ -0,0 +1,1717 @@ +// +using System; +using BMA.EHR.Infrastructure.Persistence; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace BMA.EHR.Infrastructure.Migrations.LeaveDb +{ + [DbContext(typeof(LeaveDbContext))] + [Migration("20260210091134_Add BeginningLeave and LeaveCount to LeaveBeginning")] + partial class AddBeginningLeaveandLeaveCounttoLeaveBeginning + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "7.0.9") + .HasAnnotation("Relational:MaxIdentifierLength", 64); + + modelBuilder.Entity("BMA.EHR.Domain.Models.Documents.Document", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CreatedDate") + .HasColumnType("datetime(6)"); + + b.Property("Detail") + .IsRequired() + .HasColumnType("text"); + + b.Property("FileName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("varchar(255)"); + + b.Property("FileSize") + .HasColumnType("int"); + + b.Property("FileType") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("varchar(128)"); + + b.Property("ObjectRefId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.ToTable("Document"); + }); + + modelBuilder.Entity("BMA.EHR.Domain.Models.Leave.Commons.LeaveType", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)") + .HasColumnOrder(0) + .HasComment("PrimaryKey") + .HasAnnotation("Relational:JsonPropertyName", "id"); + + b.Property("Code") + .IsRequired() + .HasColumnType("longtext") + .HasComment("รหัสประเภทการลา"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)") + .HasColumnOrder(100) + .HasComment("สร้างข้อมูลเมื่อ"); + + b.Property("CreatedFullName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)") + .HasColumnOrder(104) + .HasComment("ชื่อ User ที่สร้างข้อมูล"); + + b.Property("CreatedUserId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("varchar(40)") + .HasColumnOrder(101) + .HasComment("User Id ที่สร้างข้อมูล"); + + b.Property("LastUpdateFullName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)") + .HasColumnOrder(105) + .HasComment("ชื่อ User ที่แก้ไขข้อมูลล่าสุด"); + + b.Property("LastUpdateUserId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("varchar(40)") + .HasColumnOrder(103) + .HasComment("User Id ที่แก้ไขข้อมูลล่าสุด"); + + b.Property("LastUpdatedAt") + .HasColumnType("datetime(6)") + .HasColumnOrder(102) + .HasComment("แก้ไขข้อมูลล่าสุดเมื่อ"); + + b.Property("Limit") + .HasColumnType("int") + .HasComment("จำนวนวันลาสูงสุดประจำปี"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext") + .HasComment("ชื่อประเภทการลา"); + + b.HasKey("Id"); + + b.ToTable("LeaveTypes"); + }); + + modelBuilder.Entity("BMA.EHR.Domain.Models.Leave.Requests.LeaveBeginning", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)") + .HasColumnOrder(0) + .HasComment("PrimaryKey") + .HasAnnotation("Relational:JsonPropertyName", "id"); + + b.Property("BeginningLeaveCount") + .HasColumnType("int") + .HasComment("จำนวนครั้งที่ลายกมา"); + + b.Property("BeginningLeaveDays") + .HasColumnType("double") + .HasComment("จำนวนวันลายกมา"); + + b.Property("Child1DnaId") + .HasColumnType("char(36)"); + + b.Property("Child2DnaId") + .HasColumnType("char(36)"); + + b.Property("Child3DnaId") + .HasColumnType("char(36)"); + + b.Property("Child4DnaId") + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)") + .HasColumnOrder(100) + .HasComment("สร้างข้อมูลเมื่อ"); + + b.Property("CreatedFullName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)") + .HasColumnOrder(104) + .HasComment("ชื่อ User ที่สร้างข้อมูล"); + + b.Property("CreatedUserId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("varchar(40)") + .HasColumnOrder(101) + .HasComment("User Id ที่สร้างข้อมูล"); + + b.Property("FirstName") + .HasColumnType("longtext"); + + b.Property("LastName") + .HasColumnType("longtext"); + + b.Property("LastUpdateFullName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)") + .HasColumnOrder(105) + .HasComment("ชื่อ User ที่แก้ไขข้อมูลล่าสุด"); + + b.Property("LastUpdateUserId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("varchar(40)") + .HasColumnOrder(103) + .HasComment("User Id ที่แก้ไขข้อมูลล่าสุด"); + + b.Property("LastUpdatedAt") + .HasColumnType("datetime(6)") + .HasColumnOrder(102) + .HasComment("แก้ไขข้อมูลล่าสุดเมื่อ"); + + b.Property("LeaveCount") + .HasColumnType("int") + .HasComment("จำนวนครั้งที่ลาสะสม"); + + b.Property("LeaveDays") + .HasColumnType("double") + .HasComment("จำนวนวันลาทั้งหมด"); + + b.Property("LeaveDaysUsed") + .HasColumnType("double") + .HasComment("จำนวนวันลาที่ใช้ไป"); + + b.Property("LeaveTypeId") + .HasColumnType("char(36)") + .HasComment("รหัสประเภทการลา"); + + b.Property("LeaveYear") + .HasColumnType("int") + .HasComment("ปีงบประมาณ"); + + b.Property("Prefix") + .HasColumnType("longtext"); + + b.Property("ProfileId") + .HasColumnType("char(36)") + .HasComment("รหัส Profile ในระบบทะเบียนประวัติ"); + + b.Property("RootDnaId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("LeaveTypeId"); + + b.ToTable("LeaveBeginnings"); + }); + + modelBuilder.Entity("BMA.EHR.Domain.Models.Leave.Requests.LeaveDocument", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)") + .HasColumnOrder(0) + .HasComment("PrimaryKey") + .HasAnnotation("Relational:JsonPropertyName", "id"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)") + .HasColumnOrder(100) + .HasComment("สร้างข้อมูลเมื่อ"); + + b.Property("CreatedFullName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)") + .HasColumnOrder(104) + .HasComment("ชื่อ User ที่สร้างข้อมูล"); + + b.Property("CreatedUserId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("varchar(40)") + .HasColumnOrder(101) + .HasComment("User Id ที่สร้างข้อมูล"); + + b.Property("DocumentId") + .HasColumnType("char(36)"); + + b.Property("LastUpdateFullName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)") + .HasColumnOrder(105) + .HasComment("ชื่อ User ที่แก้ไขข้อมูลล่าสุด"); + + b.Property("LastUpdateUserId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("varchar(40)") + .HasColumnOrder(103) + .HasComment("User Id ที่แก้ไขข้อมูลล่าสุด"); + + b.Property("LastUpdatedAt") + .HasColumnType("datetime(6)") + .HasColumnOrder(102) + .HasComment("แก้ไขข้อมูลล่าสุดเมื่อ"); + + b.Property("LeaveRequestId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("DocumentId"); + + b.HasIndex("LeaveRequestId"); + + b.ToTable("LeaveDocuments"); + }); + + modelBuilder.Entity("BMA.EHR.Domain.Models.Leave.Requests.LeaveRequest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)") + .HasColumnOrder(0) + .HasComment("PrimaryKey") + .HasAnnotation("Relational:JsonPropertyName", "id"); + + b.Property("AbsentDayAt") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("AbsentDayGetIn") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("AbsentDayLocation") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("AbsentDayRegistorDate") + .HasColumnType("datetime(6)"); + + b.Property("AbsentDaySummon") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Amount") + .HasColumnType("double"); + + b.Property("ApproveStep") + .HasColumnType("longtext") + .HasComment("step การอนุมัติ st1 = จทน.อนุมัตื,st2 = ผู้บังคับบัญชา อนุมัติ "); + + b.Property("BirthDate") + .HasColumnType("datetime(6)"); + + b.Property("CancelLeaveWrote") + .HasColumnType("longtext") + .HasComment("เขียนที่ (ขอยกเลิก)"); + + b.Property("Child1") + .HasColumnType("longtext"); + + b.Property("Child1DnaId") + .HasColumnType("char(36)"); + + b.Property("Child1Id") + .HasColumnType("char(36)"); + + b.Property("Child2") + .HasColumnType("longtext"); + + b.Property("Child2DnaId") + .HasColumnType("char(36)"); + + b.Property("Child2Id") + .HasColumnType("char(36)"); + + b.Property("Child3") + .HasColumnType("longtext"); + + b.Property("Child3DnaId") + .HasColumnType("char(36)"); + + b.Property("Child3Id") + .HasColumnType("char(36)"); + + b.Property("Child4") + .HasColumnType("longtext"); + + b.Property("Child4DnaId") + .HasColumnType("char(36)"); + + b.Property("Child4Id") + .HasColumnType("char(36)"); + + b.Property("CitizenId") + .HasColumnType("longtext"); + + b.Property("CommanderPosition") + .HasColumnType("longtext"); + + b.Property("CoupleDayCountryHistory") + .HasColumnType("longtext"); + + b.Property("CoupleDayEndDateHistory") + .HasColumnType("datetime(6)"); + + b.Property("CoupleDayLevel") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("CoupleDayLevelCountry") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("CoupleDayName") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("CoupleDayPosition") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("CoupleDayStartDateHistory") + .HasColumnType("datetime(6)"); + + b.Property("CoupleDaySumTotalHistory") + .HasColumnType("longtext"); + + b.Property("CoupleDayTotalHistory") + .HasColumnType("longtext"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)") + .HasColumnOrder(100) + .HasComment("สร้างข้อมูลเมื่อ"); + + b.Property("CreatedFullName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)") + .HasColumnOrder(104) + .HasComment("ชื่อ User ที่สร้างข้อมูล"); + + b.Property("CreatedUserId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("varchar(40)") + .HasColumnOrder(101) + .HasComment("User Id ที่สร้างข้อมูล"); + + b.Property("DateAppoint") + .HasColumnType("datetime(6)"); + + b.Property("Dear") + .HasColumnType("longtext") + .HasComment("เรียนใคร"); + + b.Property("FirstName") + .HasColumnType("longtext"); + + b.Property("Gender") + .HasColumnType("longtext"); + + b.Property("HajjDayStatus") + .HasColumnType("tinyint(1)"); + + b.Property("KeycloakUserId") + .HasColumnType("char(36)"); + + b.Property("LastName") + .HasColumnType("longtext"); + + b.Property("LastUpdateFullName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)") + .HasColumnOrder(105) + .HasComment("ชื่อ User ที่แก้ไขข้อมูลล่าสุด"); + + b.Property("LastUpdateUserId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("varchar(40)") + .HasColumnOrder(103) + .HasComment("User Id ที่แก้ไขข้อมูลล่าสุด"); + + b.Property("LastUpdatedAt") + .HasColumnType("datetime(6)") + .HasColumnOrder(102) + .HasComment("แก้ไขข้อมูลล่าสุดเมื่อ"); + + b.Property("LeaveAddress") + .IsRequired() + .HasColumnType("longtext") + .HasComment("สถานที่ติดต่อขณะลา"); + + b.Property("LeaveBirthDate") + .HasColumnType("datetime(6)"); + + b.Property("LeaveCancelComment") + .HasColumnType("longtext") + .HasComment("เหตุผลในการขอยกเลิก"); + + b.Property("LeaveCancelDocumentId") + .HasColumnType("char(36)"); + + b.Property("LeaveCancelStatus") + .HasColumnType("longtext") + .HasComment("สถานะของคำขอยกเลิก"); + + b.Property("LeaveComment") + .HasColumnType("longtext") + .HasComment("ความเห็นของผู้บังคับบัญชา"); + + b.Property("LeaveDetail") + .IsRequired() + .HasColumnType("longtext") + .HasComment("รายละเอียดการลา"); + + b.Property("LeaveDirectorComment") + .HasColumnType("longtext") + .HasComment("ความเห็นของผู้อำนวยการสำนัก"); + + b.Property("LeaveDraftDocumentId") + .HasColumnType("char(36)"); + + b.Property("LeaveEndDate") + .HasColumnType("datetime(6)") + .HasComment("วัน เดือน ปีสิ้นสุดลา"); + + b.Property("LeaveGovernmentDate") + .HasColumnType("datetime(6)"); + + b.Property("LeaveLast") + .HasColumnType("datetime(6)"); + + b.Property("LeaveNumber") + .IsRequired() + .HasColumnType("longtext") + .HasComment("หมายเลขที่ติดต่อขณะลา"); + + b.Property("LeaveRange") + .HasColumnType("longtext") + .HasComment("ช่วงของการลาของวันเริ่ม เช่น ลาทั้งวัน ครึ่งวันเช้า ครึ่งวันบ่าย"); + + b.Property("LeaveRangeEnd") + .HasColumnType("longtext") + .HasComment("ช่วงของการลาของวันสิ้นสุด เช่น ลาทั้งวัน ครึ่งวันเช้า ครึ่งวันบ่าย"); + + b.Property("LeaveSalary") + .HasColumnType("int"); + + b.Property("LeaveSalaryText") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("LeaveStartDate") + .HasColumnType("datetime(6)") + .HasComment("วัน เดือน ปีเริ่มต้นลา"); + + b.Property("LeaveStatus") + .IsRequired() + .HasColumnType("longtext") + .HasComment("สถานะของคำร้อง"); + + b.Property("LeaveSubTypeName") + .HasColumnType("longtext"); + + b.Property("LeaveTotal") + .HasColumnType("double"); + + b.Property("LeaveTypeCode") + .HasColumnType("longtext") + .HasComment("code ของประเภทการลา"); + + b.Property("LeaveWrote") + .IsRequired() + .HasColumnType("longtext") + .HasComment("เขียนที่"); + + b.Property("OrdainDayBuddhistLentAddress") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("OrdainDayBuddhistLentName") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("OrdainDayLocationAddress") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("OrdainDayLocationName") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("OrdainDayLocationNumber") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("OrdainDayOrdination") + .HasColumnType("datetime(6)"); + + b.Property("OrdainDayStatus") + .HasColumnType("tinyint(1)"); + + b.Property("OrganizationName") + .HasColumnType("longtext") + .HasComment("สังกัดผู้ยื่นขอ"); + + b.Property("PositionLevelName") + .HasColumnType("longtext") + .HasComment("ระดับผู้ยื่นขอ"); + + b.Property("PositionName") + .HasColumnType("longtext") + .HasComment("ตำแหน่งผู้ยื่นขอ"); + + b.Property("Prefix") + .HasColumnType("longtext"); + + b.Property("ProfileId") + .HasColumnType("char(36)"); + + b.Property("ProfileType") + .HasColumnType("longtext"); + + b.Property("RestDayCurrentTotal") + .HasColumnType("double"); + + b.Property("RestDayOldTotal") + .HasColumnType("double"); + + b.Property("Root") + .HasColumnType("longtext"); + + b.Property("RootDnaId") + .HasColumnType("char(36)"); + + b.Property("RootId") + .HasColumnType("char(36)"); + + b.Property("StudyDayCountry") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("StudyDayDegreeLevel") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("StudyDayScholarship") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("StudyDaySubject") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("StudyDayTrainingName") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("StudyDayTrainingSubject") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("StudyDayUniversityName") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("TypeId") + .HasColumnType("char(36)"); + + b.Property("WifeDayDateBorn") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("WifeDayName") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("LeaveCancelDocumentId"); + + b.HasIndex("LeaveDraftDocumentId"); + + b.HasIndex("TypeId"); + + b.ToTable("LeaveRequests"); + }); + + modelBuilder.Entity("BMA.EHR.Domain.Models.Leave.Requests.LeaveRequestApprover", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)") + .HasColumnOrder(0) + .HasComment("PrimaryKey") + .HasAnnotation("Relational:JsonPropertyName", "id"); + + b.Property("ApproveStatus") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("ApproveType") + .HasColumnType("longtext"); + + b.Property("Comment") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)") + .HasColumnOrder(100) + .HasComment("สร้างข้อมูลเมื่อ"); + + b.Property("CreatedFullName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)") + .HasColumnOrder(104) + .HasComment("ชื่อ User ที่สร้างข้อมูล"); + + b.Property("CreatedUserId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("varchar(40)") + .HasColumnOrder(101) + .HasComment("User Id ที่สร้างข้อมูล"); + + b.Property("FirstName") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("KeycloakId") + .HasColumnType("char(36)"); + + b.Property("LastName") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("LastUpdateFullName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)") + .HasColumnOrder(105) + .HasComment("ชื่อ User ที่แก้ไขข้อมูลล่าสุด"); + + b.Property("LastUpdateUserId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("varchar(40)") + .HasColumnOrder(103) + .HasComment("User Id ที่แก้ไขข้อมูลล่าสุด"); + + b.Property("LastUpdatedAt") + .HasColumnType("datetime(6)") + .HasColumnOrder(102) + .HasComment("แก้ไขข้อมูลล่าสุดเมื่อ"); + + b.Property("LeaveRequestId") + .HasColumnType("char(36)"); + + b.Property("OrganizationName") + .IsRequired() + .HasColumnType("longtext") + .HasComment("สังกัด"); + + b.Property("PosExecutiveName") + .IsRequired() + .HasColumnType("longtext") + .HasComment("ตำแหน่งทางการบริหาร"); + + b.Property("PositionLevelName") + .IsRequired() + .HasColumnType("longtext") + .HasComment("ประเภทระดับตำแหน่ง"); + + b.Property("PositionName") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("PositionSign") + .HasColumnType("longtext") + .HasComment("ตำแหน่งใต้ลายเช็นต์"); + + b.Property("Prefix") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("ProfileId") + .HasColumnType("char(36)"); + + b.Property("Seq") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("LeaveRequestId"); + + b.ToTable("LeaveRequestApprovers"); + }); + + modelBuilder.Entity("BMA.EHR.Domain.Models.Leave.TimeAttendants.AdditionalCheckRequest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)") + .HasColumnOrder(0) + .HasComment("PrimaryKey") + .HasAnnotation("Relational:JsonPropertyName", "id"); + + b.Property("CheckDate") + .HasColumnType("datetime(6)") + .HasComment("*วันที่ลงเวลา"); + + b.Property("CheckInEdit") + .HasColumnType("tinyint(1)") + .HasComment("*ขอลงเวลาช่วงเช้า"); + + b.Property("CheckOutEdit") + .HasColumnType("tinyint(1)") + .HasComment("*ขอลงเวลาช่วงบ่าย"); + + b.Property("Child1DnaId") + .HasColumnType("char(36)"); + + b.Property("Child2DnaId") + .HasColumnType("char(36)"); + + b.Property("Child3DnaId") + .HasColumnType("char(36)"); + + b.Property("Child4DnaId") + .HasColumnType("char(36)"); + + b.Property("Comment") + .HasColumnType("longtext") + .HasComment("หมายเหตุในการการอนุมัติ/ไม่อนุมัติ"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)") + .HasColumnOrder(100) + .HasComment("สร้างข้อมูลเมื่อ"); + + b.Property("CreatedFullName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)") + .HasColumnOrder(104) + .HasComment("ชื่อ User ที่สร้างข้อมูล"); + + b.Property("CreatedUserId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("varchar(40)") + .HasColumnOrder(101) + .HasComment("User Id ที่สร้างข้อมูล"); + + b.Property("Description") + .IsRequired() + .HasColumnType("longtext") + .HasComment("*หมายเหตุขอลงเวลาพิเศษ"); + + b.Property("FirstName") + .HasColumnType("longtext"); + + b.Property("KeycloakUserId") + .HasColumnType("char(36)") + .HasComment("รหัส User ของ Keycloak ที่ร้องขอ"); + + b.Property("LastName") + .HasColumnType("longtext"); + + b.Property("LastUpdateFullName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)") + .HasColumnOrder(105) + .HasComment("ชื่อ User ที่แก้ไขข้อมูลล่าสุด"); + + b.Property("LastUpdateUserId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("varchar(40)") + .HasColumnOrder(103) + .HasComment("User Id ที่แก้ไขข้อมูลล่าสุด"); + + b.Property("LastUpdatedAt") + .HasColumnType("datetime(6)") + .HasColumnOrder(102) + .HasComment("แก้ไขข้อมูลล่าสุดเมื่อ"); + + b.Property("Latitude") + .HasColumnType("double"); + + b.Property("Longitude") + .HasColumnType("double"); + + b.Property("POI") + .HasColumnType("longtext"); + + b.Property("Prefix") + .HasColumnType("longtext"); + + b.Property("RootDnaId") + .HasColumnType("char(36)"); + + b.Property("Status") + .IsRequired() + .HasColumnType("longtext") + .HasComment("สถานะการอนุมัติ"); + + b.HasKey("Id"); + + b.ToTable("AdditionalCheckRequests"); + }); + + modelBuilder.Entity("BMA.EHR.Domain.Models.Leave.TimeAttendants.CheckInJobStatus", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)") + .HasColumnOrder(0) + .HasComment("PrimaryKey") + .HasAnnotation("Relational:JsonPropertyName", "id"); + + b.Property("AdditionalData") + .HasColumnType("longtext") + .HasComment("ข้อมูลเพิ่มเติม (JSON)"); + + b.Property("CheckInId") + .HasColumnType("char(36)") + .HasComment("CheckInId สำหรับ Check-Out"); + + b.Property("CheckType") + .HasColumnType("longtext") + .HasComment("ประเภทการลงเวลา: CHECK_IN, CHECK_OUT"); + + b.Property("CompletedDate") + .HasColumnType("datetime(6)") + .HasComment("วันเวลาที่เสร็จสิ้นการประมวลผล"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)") + .HasColumnOrder(100) + .HasComment("สร้างข้อมูลเมื่อ"); + + b.Property("CreatedDate") + .HasColumnType("datetime(6)") + .HasComment("วันเวลาที่สร้างงาน"); + + b.Property("CreatedFullName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)") + .HasColumnOrder(104) + .HasComment("ชื่อ User ที่สร้างข้อมูล"); + + b.Property("CreatedUserId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("varchar(40)") + .HasColumnOrder(101) + .HasComment("User Id ที่สร้างข้อมูล"); + + b.Property("ErrorMessage") + .HasColumnType("longtext") + .HasComment("ข้อความแสดงข้อผิดพลาด"); + + b.Property("KeycloakUserId") + .HasColumnType("char(36)") + .HasComment("รหัส User ของ Keycloak"); + + b.Property("LastUpdateFullName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)") + .HasColumnOrder(105) + .HasComment("ชื่อ User ที่แก้ไขข้อมูลล่าสุด"); + + b.Property("LastUpdateUserId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("varchar(40)") + .HasColumnOrder(103) + .HasComment("User Id ที่แก้ไขข้อมูลล่าสุด"); + + b.Property("LastUpdatedAt") + .HasColumnType("datetime(6)") + .HasColumnOrder(102) + .HasComment("แก้ไขข้อมูลล่าสุดเมื่อ"); + + b.Property("ProcessingDate") + .HasColumnType("datetime(6)") + .HasComment("วันเวลาที่เริ่มประมวลผล"); + + b.Property("Status") + .IsRequired() + .HasColumnType("longtext") + .HasComment("สถานะงาน: PENDING, PROCESSING, COMPLETED, FAILED"); + + b.Property("TaskId") + .HasColumnType("char(36)") + .HasComment("Task ID สำหรับติดตามสถานะงาน"); + + b.HasKey("Id"); + + b.ToTable("CheckInJobStatuses"); + }); + + modelBuilder.Entity("BMA.EHR.Domain.Models.Leave.TimeAttendants.DutyTime", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)") + .HasColumnOrder(0) + .HasComment("PrimaryKey") + .HasAnnotation("Relational:JsonPropertyName", "id"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)") + .HasColumnOrder(100) + .HasComment("สร้างข้อมูลเมื่อ"); + + b.Property("CreatedFullName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)") + .HasColumnOrder(104) + .HasComment("ชื่อ User ที่สร้างข้อมูล"); + + b.Property("CreatedUserId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("varchar(40)") + .HasColumnOrder(101) + .HasComment("User Id ที่สร้างข้อมูล"); + + b.Property("Description") + .IsRequired() + .HasColumnType("longtext") + .HasComment("คำอธิบาย"); + + b.Property("EndTimeAfternoon") + .IsRequired() + .HasColumnType("longtext") + .HasComment("เวลาออกงานช่วงบ่าย"); + + b.Property("EndTimeMorning") + .IsRequired() + .HasColumnType("longtext") + .HasComment("เวลาออกงานช่วงเช้า"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)") + .HasComment("สถานะการเปิดใช้งาน (เปิด/ปิด)"); + + b.Property("IsDefault") + .HasColumnType("tinyint(1)") + .HasComment("สถานะว่ารอบใดเป็นค่า Default ของข้าราชการ (สำหรับทุกคนที่ยังไม่ได้ทำการเลือกรอบ)"); + + b.Property("LastUpdateFullName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)") + .HasColumnOrder(105) + .HasComment("ชื่อ User ที่แก้ไขข้อมูลล่าสุด"); + + b.Property("LastUpdateUserId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("varchar(40)") + .HasColumnOrder(103) + .HasComment("User Id ที่แก้ไขข้อมูลล่าสุด"); + + b.Property("LastUpdatedAt") + .HasColumnType("datetime(6)") + .HasColumnOrder(102) + .HasComment("แก้ไขข้อมูลล่าสุดเมื่อ"); + + b.Property("StartTimeAfternoon") + .IsRequired() + .HasColumnType("longtext") + .HasComment("เวลาเข้างานช่วงบ่าย"); + + b.Property("StartTimeMorning") + .IsRequired() + .HasColumnType("longtext") + .HasComment("เวลาเข้างานช่วงเช้า"); + + b.HasKey("Id"); + + b.ToTable("DutyTimes"); + }); + + modelBuilder.Entity("BMA.EHR.Domain.Models.Leave.TimeAttendants.ProcessUserTimeStamp", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)") + .HasColumnOrder(0) + .HasComment("PrimaryKey") + .HasAnnotation("Relational:JsonPropertyName", "id"); + + b.Property("CheckIn") + .HasColumnType("datetime(6)") + .HasComment("วัน เวลา เข้างาน"); + + b.Property("CheckInImageUrl") + .IsRequired() + .HasColumnType("longtext") + .HasComment("รูปถ่ายสถานที่ Check-In"); + + b.Property("CheckInLat") + .HasColumnType("double") + .HasComment("พิกัดละติจูด Check-In"); + + b.Property("CheckInLocationName") + .HasColumnType("longtext") + .HasComment("กรณีเลือกนอกสถานที่ตั้ง ต้องระบุข้อมูลชื่อสถานะที่ Check-In"); + + b.Property("CheckInLon") + .HasColumnType("double") + .HasComment("พิกัดลองจิจูด Check-In"); + + b.Property("CheckInPOI") + .IsRequired() + .HasColumnType("longtext") + .HasComment("ชื่อสถานที่ ได้มาจากระบบ ArcGis ของกองสารสนเทศภูมิศาสตร์ Check-In"); + + b.Property("CheckInRemark") + .HasColumnType("longtext") + .HasComment("ข้อความหมายเหตุที่ต้องการระบุเพิ่ม(มีเผื่อไว้อาจไม่ได้ใช้) Check-In"); + + b.Property("CheckInStatus") + .IsRequired() + .HasColumnType("longtext") + .HasComment("สถานะ Check-In"); + + b.Property("CheckOut") + .HasColumnType("datetime(6)") + .HasComment("วัน เวลา ออกงาน"); + + b.Property("CheckOutImageUrl") + .IsRequired() + .HasColumnType("longtext") + .HasComment("รูปถ่ายสถานที่ Check-Out"); + + b.Property("CheckOutLat") + .HasColumnType("double") + .HasComment("พิกัดละติจูด Check-Out"); + + b.Property("CheckOutLocationName") + .HasColumnType("longtext") + .HasComment("กรณีเลือกนอกสถานที่ตั้ง ต้องระบุข้อมูลชื่อสถานะที่ Check-Out"); + + b.Property("CheckOutLon") + .HasColumnType("double") + .HasComment("พิกัดลองจิจูด Check-Out"); + + b.Property("CheckOutPOI") + .IsRequired() + .HasColumnType("longtext") + .HasComment("ชื่อสถานที่ ได้มาจากระบบ ArcGis ของกองสารสนเทศภูมิศาสตร์ Check-Out"); + + b.Property("CheckOutRemark") + .HasColumnType("longtext") + .HasComment("ข้อความหมายเหตุที่ต้องการระบุเพิ่ม(มีเผื่อไว้อาจไม่ได้ใช้) Check-Out"); + + b.Property("CheckOutStatus") + .HasColumnType("longtext") + .HasComment("สถานะ Check-Out"); + + b.Property("Child1") + .HasColumnType("longtext"); + + b.Property("Child1DnaId") + .HasColumnType("char(36)"); + + b.Property("Child1Id") + .HasColumnType("char(36)"); + + b.Property("Child2") + .HasColumnType("longtext"); + + b.Property("Child2DnaId") + .HasColumnType("char(36)"); + + b.Property("Child2Id") + .HasColumnType("char(36)"); + + b.Property("Child3") + .HasColumnType("longtext"); + + b.Property("Child3DnaId") + .HasColumnType("char(36)"); + + b.Property("Child3Id") + .HasColumnType("char(36)"); + + b.Property("Child4") + .HasColumnType("longtext"); + + b.Property("Child4DnaId") + .HasColumnType("char(36)"); + + b.Property("Child4Id") + .HasColumnType("char(36)"); + + b.Property("CitizenId") + .HasColumnType("longtext"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)") + .HasColumnOrder(100) + .HasComment("สร้างข้อมูลเมื่อ"); + + b.Property("CreatedFullName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)") + .HasColumnOrder(104) + .HasComment("ชื่อ User ที่สร้างข้อมูล"); + + b.Property("CreatedUserId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("varchar(40)") + .HasColumnOrder(101) + .HasComment("User Id ที่สร้างข้อมูล"); + + b.Property("EditReason") + .HasColumnType("longtext") + .HasComment("เหตุผลการอนุมัติ/ไม่อนุมัติขอลงเวลาพิเศษ"); + + b.Property("EditStatus") + .HasColumnType("longtext") + .HasComment("สถานะการของลงเวลาพิเศษ"); + + b.Property("FirstName") + .HasColumnType("longtext"); + + b.Property("Gender") + .HasColumnType("longtext"); + + b.Property("IsLocationCheckIn") + .HasColumnType("tinyint(1)") + .HasComment("true คือ ณ สถานที่ตั้ง, false คือ นอกสถานที่ตั้ง Check-In"); + + b.Property("IsLocationCheckOut") + .HasColumnType("tinyint(1)") + .HasComment("true คือ ณ สถานที่ตั้ง, false คือ นอกสถานที่ตั้ง Check-Out"); + + b.Property("IsProcess") + .HasColumnType("tinyint(1)") + .HasComment("นำไปประมวลผลแล้วหรือยัง"); + + b.Property("KeycloakUserId") + .HasColumnType("char(36)") + .HasComment("รหัส User ของ Keycloak"); + + b.Property("LastName") + .HasColumnType("longtext"); + + b.Property("LastUpdateFullName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)") + .HasColumnOrder(105) + .HasComment("ชื่อ User ที่แก้ไขข้อมูลล่าสุด"); + + b.Property("LastUpdateUserId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("varchar(40)") + .HasColumnOrder(103) + .HasComment("User Id ที่แก้ไขข้อมูลล่าสุด"); + + b.Property("LastUpdatedAt") + .HasColumnType("datetime(6)") + .HasColumnOrder(102) + .HasComment("แก้ไขข้อมูลล่าสุดเมื่อ"); + + b.Property("Prefix") + .HasColumnType("longtext"); + + b.Property("ProfileId") + .HasColumnType("char(36)"); + + b.Property("ProfileType") + .HasColumnType("longtext"); + + b.Property("Root") + .HasColumnType("longtext"); + + b.Property("RootDnaId") + .HasColumnType("char(36)"); + + b.Property("RootId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.ToTable("ProcessUserTimeStamps"); + }); + + modelBuilder.Entity("BMA.EHR.Domain.Models.Leave.TimeAttendants.UserCalendar", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)") + .HasColumnOrder(0) + .HasComment("PrimaryKey") + .HasAnnotation("Relational:JsonPropertyName", "id"); + + b.Property("Calendar") + .IsRequired() + .HasColumnType("longtext") + .HasComment("ปฏิทินการทำงานของ ขรก ปกติ หรือ 6 วันต่อสัปดาห์"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)") + .HasColumnOrder(100) + .HasComment("สร้างข้อมูลเมื่อ"); + + b.Property("CreatedFullName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)") + .HasColumnOrder(104) + .HasComment("ชื่อ User ที่สร้างข้อมูล"); + + b.Property("CreatedUserId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("varchar(40)") + .HasColumnOrder(101) + .HasComment("User Id ที่สร้างข้อมูล"); + + b.Property("LastUpdateFullName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)") + .HasColumnOrder(105) + .HasComment("ชื่อ User ที่แก้ไขข้อมูลล่าสุด"); + + b.Property("LastUpdateUserId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("varchar(40)") + .HasColumnOrder(103) + .HasComment("User Id ที่แก้ไขข้อมูลล่าสุด"); + + b.Property("LastUpdatedAt") + .HasColumnType("datetime(6)") + .HasColumnOrder(102) + .HasComment("แก้ไขข้อมูลล่าสุดเมื่อ"); + + b.Property("ProfileId") + .HasColumnType("char(36)") + .HasComment("รหัส Profile ในระบบทะเบียนประวัติ"); + + b.HasKey("Id"); + + b.ToTable("UserCalendars"); + }); + + modelBuilder.Entity("BMA.EHR.Domain.Models.Leave.TimeAttendants.UserDutyTime", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)") + .HasColumnOrder(0) + .HasComment("PrimaryKey") + .HasAnnotation("Relational:JsonPropertyName", "id"); + + b.Property("Child1DnaId") + .HasColumnType("char(36)"); + + b.Property("Child2DnaId") + .HasColumnType("char(36)"); + + b.Property("Child3DnaId") + .HasColumnType("char(36)"); + + b.Property("Child4DnaId") + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)") + .HasColumnOrder(100) + .HasComment("สร้างข้อมูลเมื่อ"); + + b.Property("CreatedFullName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)") + .HasColumnOrder(104) + .HasComment("ชื่อ User ที่สร้างข้อมูล"); + + b.Property("CreatedUserId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("varchar(40)") + .HasColumnOrder(101) + .HasComment("User Id ที่สร้างข้อมูล"); + + b.Property("DutyTimeId") + .HasColumnType("char(36)") + .HasComment("รหัสรอบการลงเวลา"); + + b.Property("EffectiveDate") + .HasColumnType("datetime(6)") + .HasComment("วันที่มีผล"); + + b.Property("IsProcess") + .HasColumnType("tinyint(1)") + .HasComment("ทำการประมวลผลแล้วหรือยัง"); + + b.Property("LastUpdateFullName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)") + .HasColumnOrder(105) + .HasComment("ชื่อ User ที่แก้ไขข้อมูลล่าสุด"); + + b.Property("LastUpdateUserId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("varchar(40)") + .HasColumnOrder(103) + .HasComment("User Id ที่แก้ไขข้อมูลล่าสุด"); + + b.Property("LastUpdatedAt") + .HasColumnType("datetime(6)") + .HasColumnOrder(102) + .HasComment("แก้ไขข้อมูลล่าสุดเมื่อ"); + + b.Property("ProfileId") + .HasColumnType("char(36)") + .HasComment("รหัส Profile ในระบบทะเบียนประวัติ"); + + b.Property("Remark") + .HasColumnType("longtext") + .HasComment("หมายเหตุ"); + + b.Property("RootDnaId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("DutyTimeId"); + + b.ToTable("UserDutyTimes"); + }); + + modelBuilder.Entity("BMA.EHR.Domain.Models.Leave.TimeAttendants.UserTimeStamp", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)") + .HasColumnOrder(0) + .HasComment("PrimaryKey") + .HasAnnotation("Relational:JsonPropertyName", "id"); + + b.Property("CheckIn") + .HasColumnType("datetime(6)") + .HasComment("วัน เวลา เข้างาน"); + + b.Property("CheckInImageUrl") + .IsRequired() + .HasColumnType("longtext") + .HasComment("รูปถ่ายสถานที่ Check-In"); + + b.Property("CheckInLat") + .HasColumnType("double") + .HasComment("พิกัดละติจูด Check-In"); + + b.Property("CheckInLocationName") + .HasColumnType("longtext") + .HasComment("กรณีเลือกนอกสถานที่ตั้ง ต้องระบุข้อมูลชื่อสถานะที่ Check-In"); + + b.Property("CheckInLon") + .HasColumnType("double") + .HasComment("พิกัดลองจิจูด Check-In"); + + b.Property("CheckInPOI") + .IsRequired() + .HasColumnType("longtext") + .HasComment("ชื่อสถานที่ ได้มาจากระบบ ArcGis ของกองสารสนเทศภูมิศาสตร์ Check-In"); + + b.Property("CheckInRemark") + .HasColumnType("longtext") + .HasComment("ข้อความหมายเหตุที่ต้องการระบุเพิ่ม(มีเผื่อไว้อาจไม่ได้ใช้) Check-In"); + + b.Property("CheckOut") + .HasColumnType("datetime(6)") + .HasComment("วัน เวลา ออกงาน"); + + b.Property("CheckOutImageUrl") + .IsRequired() + .HasColumnType("longtext") + .HasComment("รูปถ่ายสถานที่ Check-Out"); + + b.Property("CheckOutLat") + .HasColumnType("double") + .HasComment("พิกัดละติจูด Check-Out"); + + b.Property("CheckOutLocationName") + .HasColumnType("longtext") + .HasComment("กรณีเลือกนอกสถานที่ตั้ง ต้องระบุข้อมูลชื่อสถานะที่ Check-Out"); + + b.Property("CheckOutLon") + .HasColumnType("double") + .HasComment("พิกัดลองจิจูด Check-Out"); + + b.Property("CheckOutPOI") + .IsRequired() + .HasColumnType("longtext") + .HasComment("ชื่อสถานที่ ได้มาจากระบบ ArcGis ของกองสารสนเทศภูมิศาสตร์ Check-Out"); + + b.Property("CheckOutRemark") + .HasColumnType("longtext") + .HasComment("ข้อความหมายเหตุที่ต้องการระบุเพิ่ม(มีเผื่อไว้อาจไม่ได้ใช้) Check-Out"); + + b.Property("Child1") + .HasColumnType("longtext"); + + b.Property("Child1DnaId") + .HasColumnType("char(36)"); + + b.Property("Child1Id") + .HasColumnType("char(36)"); + + b.Property("Child2") + .HasColumnType("longtext"); + + b.Property("Child2DnaId") + .HasColumnType("char(36)"); + + b.Property("Child2Id") + .HasColumnType("char(36)"); + + b.Property("Child3") + .HasColumnType("longtext"); + + b.Property("Child3DnaId") + .HasColumnType("char(36)"); + + b.Property("Child3Id") + .HasColumnType("char(36)"); + + b.Property("Child4") + .HasColumnType("longtext"); + + b.Property("Child4DnaId") + .HasColumnType("char(36)"); + + b.Property("Child4Id") + .HasColumnType("char(36)"); + + b.Property("CitizenId") + .HasColumnType("longtext"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)") + .HasColumnOrder(100) + .HasComment("สร้างข้อมูลเมื่อ"); + + b.Property("CreatedFullName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)") + .HasColumnOrder(104) + .HasComment("ชื่อ User ที่สร้างข้อมูล"); + + b.Property("CreatedUserId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("varchar(40)") + .HasColumnOrder(101) + .HasComment("User Id ที่สร้างข้อมูล"); + + b.Property("FirstName") + .HasColumnType("longtext"); + + b.Property("Gender") + .HasColumnType("longtext"); + + b.Property("IsLocationCheckIn") + .HasColumnType("tinyint(1)") + .HasComment("true คือ ณ สถานที่ตั้ง, false คือ นอกสถานที่ตั้ง Check-In"); + + b.Property("IsLocationCheckOut") + .HasColumnType("tinyint(1)") + .HasComment("true คือ ณ สถานที่ตั้ง, false คือ นอกสถานที่ตั้ง Check-Out"); + + b.Property("IsProcess") + .HasColumnType("tinyint(1)") + .HasComment("นำไปประมวลผลแล้วหรือยัง"); + + b.Property("KeycloakUserId") + .HasColumnType("char(36)") + .HasComment("รหัส User ของ Keycloak"); + + b.Property("LastName") + .HasColumnType("longtext"); + + b.Property("LastUpdateFullName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)") + .HasColumnOrder(105) + .HasComment("ชื่อ User ที่แก้ไขข้อมูลล่าสุด"); + + b.Property("LastUpdateUserId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("varchar(40)") + .HasColumnOrder(103) + .HasComment("User Id ที่แก้ไขข้อมูลล่าสุด"); + + b.Property("LastUpdatedAt") + .HasColumnType("datetime(6)") + .HasColumnOrder(102) + .HasComment("แก้ไขข้อมูลล่าสุดเมื่อ"); + + b.Property("Prefix") + .HasColumnType("longtext"); + + b.Property("ProfileId") + .HasColumnType("char(36)"); + + b.Property("ProfileType") + .HasColumnType("longtext"); + + b.Property("Root") + .HasColumnType("longtext"); + + b.Property("RootDnaId") + .HasColumnType("char(36)"); + + b.Property("RootId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.ToTable("UserTimeStamps"); + }); + + modelBuilder.Entity("BMA.EHR.Domain.Models.Leave.Requests.LeaveBeginning", b => + { + b.HasOne("BMA.EHR.Domain.Models.Leave.Commons.LeaveType", "LeaveType") + .WithMany() + .HasForeignKey("LeaveTypeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("LeaveType"); + }); + + modelBuilder.Entity("BMA.EHR.Domain.Models.Leave.Requests.LeaveDocument", b => + { + b.HasOne("BMA.EHR.Domain.Models.Documents.Document", "Document") + .WithMany() + .HasForeignKey("DocumentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("BMA.EHR.Domain.Models.Leave.Requests.LeaveRequest", "LeaveRequest") + .WithMany("LeaveDocument") + .HasForeignKey("LeaveRequestId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Document"); + + b.Navigation("LeaveRequest"); + }); + + modelBuilder.Entity("BMA.EHR.Domain.Models.Leave.Requests.LeaveRequest", b => + { + b.HasOne("BMA.EHR.Domain.Models.Documents.Document", "LeaveCancelDocument") + .WithMany() + .HasForeignKey("LeaveCancelDocumentId"); + + b.HasOne("BMA.EHR.Domain.Models.Documents.Document", "LeaveDraftDocument") + .WithMany() + .HasForeignKey("LeaveDraftDocumentId"); + + b.HasOne("BMA.EHR.Domain.Models.Leave.Commons.LeaveType", "Type") + .WithMany() + .HasForeignKey("TypeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("LeaveCancelDocument"); + + b.Navigation("LeaveDraftDocument"); + + b.Navigation("Type"); + }); + + modelBuilder.Entity("BMA.EHR.Domain.Models.Leave.Requests.LeaveRequestApprover", b => + { + b.HasOne("BMA.EHR.Domain.Models.Leave.Requests.LeaveRequest", "LeaveRequest") + .WithMany("Approvers") + .HasForeignKey("LeaveRequestId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("LeaveRequest"); + }); + + modelBuilder.Entity("BMA.EHR.Domain.Models.Leave.TimeAttendants.UserDutyTime", b => + { + b.HasOne("BMA.EHR.Domain.Models.Leave.TimeAttendants.DutyTime", "DutyTime") + .WithMany() + .HasForeignKey("DutyTimeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("DutyTime"); + }); + + modelBuilder.Entity("BMA.EHR.Domain.Models.Leave.Requests.LeaveRequest", b => + { + b.Navigation("Approvers"); + + b.Navigation("LeaveDocument"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/BMA.EHR.Infrastructure/Migrations/LeaveDb/20260210091134_Add BeginningLeave and LeaveCount to LeaveBeginning.cs b/BMA.EHR.Infrastructure/Migrations/LeaveDb/20260210091134_Add BeginningLeave and LeaveCount to LeaveBeginning.cs new file mode 100644 index 00000000..a54ad519 --- /dev/null +++ b/BMA.EHR.Infrastructure/Migrations/LeaveDb/20260210091134_Add BeginningLeave and LeaveCount to LeaveBeginning.cs @@ -0,0 +1,62 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace BMA.EHR.Infrastructure.Migrations.LeaveDb +{ + /// + public partial class AddBeginningLeaveandLeaveCounttoLeaveBeginning : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AlterColumn( + name: "LeaveDays", + table: "LeaveBeginnings", + type: "double", + nullable: false, + comment: "จำนวนวันลาทั้งหมด", + oldClrType: typeof(double), + oldType: "double", + oldComment: "จำนวนวันลายกมา"); + + migrationBuilder.AddColumn( + name: "BeginningLeaveCount", + table: "LeaveBeginnings", + type: "int", + nullable: false, + defaultValue: 0, + comment: "จำนวนครั้งที่ลายกมา"); + + migrationBuilder.AddColumn( + name: "BeginningLeaveDays", + table: "LeaveBeginnings", + type: "double", + nullable: false, + defaultValue: 0.0, + comment: "จำนวนวันลายกมา"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "BeginningLeaveCount", + table: "LeaveBeginnings"); + + migrationBuilder.DropColumn( + name: "BeginningLeaveDays", + table: "LeaveBeginnings"); + + migrationBuilder.AlterColumn( + name: "LeaveDays", + table: "LeaveBeginnings", + type: "double", + nullable: false, + comment: "จำนวนวันลายกมา", + oldClrType: typeof(double), + oldType: "double", + oldComment: "จำนวนวันลาทั้งหมด"); + } + } +} diff --git a/BMA.EHR.Infrastructure/Migrations/LeaveDb/LeaveDbContextModelSnapshot.cs b/BMA.EHR.Infrastructure/Migrations/LeaveDb/LeaveDbContextModelSnapshot.cs index b59264fc..510601d8 100644 --- a/BMA.EHR.Infrastructure/Migrations/LeaveDb/LeaveDbContextModelSnapshot.cs +++ b/BMA.EHR.Infrastructure/Migrations/LeaveDb/LeaveDbContextModelSnapshot.cs @@ -128,6 +128,14 @@ namespace BMA.EHR.Infrastructure.Migrations.LeaveDb .HasComment("PrimaryKey") .HasAnnotation("Relational:JsonPropertyName", "id"); + b.Property("BeginningLeaveCount") + .HasColumnType("int") + .HasComment("จำนวนครั้งที่ลายกมา"); + + b.Property("BeginningLeaveDays") + .HasColumnType("double") + .HasComment("จำนวนวันลายกมา"); + b.Property("Child1DnaId") .HasColumnType("char(36)"); @@ -190,7 +198,7 @@ namespace BMA.EHR.Infrastructure.Migrations.LeaveDb b.Property("LeaveDays") .HasColumnType("double") - .HasComment("จำนวนวันลายกมา"); + .HasComment("จำนวนวันลาทั้งหมด"); b.Property("LeaveDaysUsed") .HasColumnType("double") From 2410574d42c42d8eaf2356f25fbc1605b6fb03d7 Mon Sep 17 00:00:00 2001 From: Suphonchai Phoonsawat Date: Wed, 11 Feb 2026 10:47:01 +0700 Subject: [PATCH 099/183] Fix typo in IsProbatin property name and update related condition check in LeaveRequestController #2306 --- .../Profiles/GetProfileByKeycloakIdDto.cs | 2 +- .../Controllers/LeaveRequestController.cs | 15 ++++++++------- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/BMA.EHR.Application/Responses/Profiles/GetProfileByKeycloakIdDto.cs b/BMA.EHR.Application/Responses/Profiles/GetProfileByKeycloakIdDto.cs index 7db0e212..8e319904 100644 --- a/BMA.EHR.Application/Responses/Profiles/GetProfileByKeycloakIdDto.cs +++ b/BMA.EHR.Application/Responses/Profiles/GetProfileByKeycloakIdDto.cs @@ -44,7 +44,7 @@ namespace BMA.EHR.Application.Responses.Profiles public string? ProfileType { get; set; } public bool? IsLeave { get; set; } - public bool? IsProbatin { get; set; } + public bool? IsProbation { get; set; } public string? Root { get; set; } public string? Child1 { get; set; } diff --git a/BMA.EHR.Leave/Controllers/LeaveRequestController.cs b/BMA.EHR.Leave/Controllers/LeaveRequestController.cs index 1a00d285..391ec6d4 100644 --- a/BMA.EHR.Leave/Controllers/LeaveRequestController.cs +++ b/BMA.EHR.Leave/Controllers/LeaveRequestController.cs @@ -1084,12 +1084,13 @@ namespace BMA.EHR.Leave.Service.Controllers // fix issue : ระบบลา (ขรก.) >> ลาพักผ่อน (กรณีรับราชการไม่ถึง 6 เดือน) #838 //var leavePrevYear = (await _leaveRequestRepository.GetSumApproveLeaveAsync(fiscalYear - 1)).Where(x => x.LeaveTypeCode == "LV-005" && x.KeycloakUserId == userId).FirstOrDefault(); //var leavePrevYearRemain = 10 - (leavePrevYear == null ? 0 : leavePrevYear.SumLeaveDay); // หายอดวันลาที่เหลือของปีก่อน - if (profile.IsProbatin! == true) + if (profile.IsProbation! == true) { isLeave = false; if (!isLeave) message = "ยังอยู่ในช่วงทดลองปฏิบัติราชการ ไม่สามารถลาพักผ่อนได้"; } - else if (govAge >= 180) + //else if (govAge >= 180) + else { isLeave = (totalDay - (sumWorkDay + sumWeekend) + approveDay) <= (limitDay); if (!isLeave) message = "จำนวนวันลาเกินที่กำหนด"; @@ -1112,11 +1113,11 @@ namespace BMA.EHR.Leave.Service.Controllers // if (!isLeave) message = "จำนวนวันลาเกินที่กำหนด"; // } - else - { - isLeave = false; - if (!isLeave) message = "อายุราชการน้อยกว่า 6 เดือนหรือ 180 วัน"; - } + // else + // { + // isLeave = false; + // if (!isLeave) message = "อายุราชการน้อยกว่า 6 เดือนหรือ 180 วัน"; + // } break; case "LV-006": From a2ac05ed61cc1028ba882cb1efd82e614bd866ef Mon Sep 17 00:00:00 2001 From: Suphonchai Phoonsawat Date: Wed, 11 Feb 2026 11:11:19 +0700 Subject: [PATCH 100/183] Add BeginningLeaveDays and BeginningLeaveCount to LeaveBeginning DTOs and update controller logic #2304 #2305 --- .../Controllers/LeaveBeginningController.cs | 84 +++++++++++++++++++ .../CreateLeaveBeginningDto.cs | 11 ++- .../LeaveBeginnings/EditLeaveBeginningDto.cs | 6 ++ 3 files changed, 100 insertions(+), 1 deletion(-) diff --git a/BMA.EHR.Leave/Controllers/LeaveBeginningController.cs b/BMA.EHR.Leave/Controllers/LeaveBeginningController.cs index 3a3140e8..c99aeaea 100644 --- a/BMA.EHR.Leave/Controllers/LeaveBeginningController.cs +++ b/BMA.EHR.Leave/Controllers/LeaveBeginningController.cs @@ -202,6 +202,8 @@ namespace BMA.EHR.Leave.Service.Controllers item.LeaveDays, item.LeaveDaysUsed, item.LeaveCount, + item.BeginningLeaveDays, + item.BeginningLeaveCount, item.CreatedAt, item.CreatedFullName, item.LastUpdatedAt, @@ -396,6 +398,8 @@ namespace BMA.EHR.Leave.Service.Controllers leaveBeginning.LeaveDays = req.LeaveDays; leaveBeginning.LeaveDaysUsed = req.LeaveDaysUsed; leaveBeginning.LeaveCount = req.LeaveCount; + leaveBeginning.BeginningLeaveDays = req.BeginningLeaveDays; + leaveBeginning.BeginningLeaveCount = req.BeginningLeaveCount; leaveBeginning.ProfileId = req.ProfileId; leaveBeginning.Prefix = profile.Prefix; @@ -465,6 +469,8 @@ namespace BMA.EHR.Leave.Service.Controllers leaveBeginning.LeaveDays = req.LeaveDays; leaveBeginning.LeaveDaysUsed = req.LeaveDaysUsed; leaveBeginning.LeaveCount = req.LeaveCount; + leaveBeginning.BeginningLeaveDays = req.BeginningLeaveDays; + leaveBeginning.BeginningLeaveCount = req.BeginningLeaveCount; leaveBeginning.ProfileId = req.ProfileId; leaveBeginning.Prefix = profile.Prefix; @@ -492,6 +498,84 @@ namespace BMA.EHR.Leave.Service.Controllers } } + [HttpPut("schedule")] + [AllowAnonymous] + public async Task> ScheduleLeaveBeginning([FromBody] EditLeaveBeginningDto req) + { + try + { + var profile = await _userProfileRepository.GetProfileByProfileIdAsync(req.ProfileId, AccessToken); + if(profile == null) + { + return Error("ไม่พบข้อมูลข้าราชการหรือลูกจ้าง", StatusCodes.Status404NotFound); + } + // check duplicate + var oldData = await _context.LeaveBeginnings.FirstOrDefaultAsync(x => x.ProfileId == req.ProfileId + && x.LeaveTypeId == req.LeaveTypeId + && x.LeaveYear == req.LeaveYear); + + if (oldData is not null) + { + //return Error("ไม่สามารถบันทึกข้อมูล เนื่องจากมีข้อมูลในระบบแล้ว"); + oldData.LeaveTypeId = req.LeaveTypeId; + oldData.LeaveYear = req.LeaveYear; + oldData.LeaveDays = req.LeaveDays; + oldData.LeaveDaysUsed = req.LeaveDaysUsed; + oldData.LeaveCount = req.LeaveCount; + oldData.BeginningLeaveDays = req.BeginningLeaveDays; + oldData.BeginningLeaveCount = req.BeginningLeaveCount; + + oldData.ProfileId = req.ProfileId; + oldData.Prefix = profile.Prefix; + oldData.FirstName = profile.FirstName; + oldData.LastName = profile.LastName; + oldData.RootDnaId = profile.RootDnaId; + oldData.Child1DnaId = profile.Child1DnaId; + oldData.Child2DnaId = profile.Child2DnaId; + oldData.Child3DnaId = profile.Child3DnaId; + oldData.Child4DnaId = profile.Child4DnaId; + + oldData.LastUpdateUserId = ""; + oldData.LastUpdateFullName = FullName ?? ""; + oldData.LastUpdatedAt = DateTime.Now; + + await _leaveBeginningRepository.UpdateAsync(oldData); + } + else + { + var leaveBeginning = new LeaveBeginning(); + leaveBeginning.LeaveTypeId = req.LeaveTypeId; + leaveBeginning.LeaveYear = req.LeaveYear; + leaveBeginning.LeaveDays = req.LeaveDays; + leaveBeginning.LeaveDaysUsed = req.LeaveDaysUsed; + leaveBeginning.LeaveCount = req.LeaveCount; + leaveBeginning.BeginningLeaveDays = req.BeginningLeaveDays; + leaveBeginning.BeginningLeaveCount = req.BeginningLeaveCount; + + leaveBeginning.ProfileId = req.ProfileId; + leaveBeginning.Prefix = profile.Prefix; + leaveBeginning.FirstName = profile.FirstName; + leaveBeginning.LastName = profile.LastName; + + leaveBeginning.RootDnaId = profile.RootDnaId; + leaveBeginning.Child1DnaId = profile.Child1DnaId; + leaveBeginning.Child2DnaId = profile.Child2DnaId; + leaveBeginning.Child3DnaId = profile.Child3DnaId; + leaveBeginning.Child4DnaId = profile.Child4DnaId; + + leaveBeginning.CreatedUserId = ""; + leaveBeginning.CreatedFullName = FullName ?? ""; + leaveBeginning.CreatedAt = DateTime.Now; + + await _leaveBeginningRepository.AddAsync(leaveBeginning); + } + return Success(); + } + catch (Exception ex) + { + return Error(ex); + } + } #endregion } diff --git a/BMA.EHR.Leave/DTOs/LeaveBeginnings/CreateLeaveBeginningDto.cs b/BMA.EHR.Leave/DTOs/LeaveBeginnings/CreateLeaveBeginningDto.cs index 79d33052..423385ed 100644 --- a/BMA.EHR.Leave/DTOs/LeaveBeginnings/CreateLeaveBeginningDto.cs +++ b/BMA.EHR.Leave/DTOs/LeaveBeginnings/CreateLeaveBeginningDto.cs @@ -14,10 +14,19 @@ namespace BMA.EHR.Leave.Service.DTOs.LeaveBeginnings [Required, Comment("ปีงบประมาณ")] public int LeaveYear { get; set; } = 0; - [Required, Comment("จำนวนวันลายกมา")] + [Required, Comment("จำนวนวันลาที่ได้รับ")] public double LeaveDays { get; set; } = 0.0; [Required, Comment("จำนวนวันลาที่ใช้ไป")] public double LeaveDaysUsed { get; set; } = 0.0; + + [Required, Comment("จำนวนครั้งที่ลาสะสม")] + public int LeaveCount { get; set; } = 0; + + [Required, Comment("จำนวนวันลายกมา")] + public double BeginningLeaveDays { get; set; } = 0.0; + + [Comment("จำนวนครั้งที่ลายกมา")] + public int BeginningLeaveCount { get; set; } = 0; } } diff --git a/BMA.EHR.Leave/DTOs/LeaveBeginnings/EditLeaveBeginningDto.cs b/BMA.EHR.Leave/DTOs/LeaveBeginnings/EditLeaveBeginningDto.cs index a73bdfd4..d4a2661f 100644 --- a/BMA.EHR.Leave/DTOs/LeaveBeginnings/EditLeaveBeginningDto.cs +++ b/BMA.EHR.Leave/DTOs/LeaveBeginnings/EditLeaveBeginningDto.cs @@ -22,5 +22,11 @@ namespace BMA.EHR.Leave.Service.DTOs.LeaveBeginnings [Required, Comment("จำนวนครั้งที่ลาสะสม")] public int LeaveCount { get; set; } = 0; + + [Required, Comment("จำนวนวันลายกมา")] + public double BeginningLeaveDays { get; set; } = 0.0; + + [Comment("จำนวนครั้งที่ลายกมา")] + public int BeginningLeaveCount { get; set; } = 0; } } From e5e7c7788096d8d2c3deed2f2cbc5c2a72b4a77f Mon Sep 17 00:00:00 2001 From: Suphonchai Phoonsawat Date: Wed, 11 Feb 2026 11:47:49 +0700 Subject: [PATCH 101/183] Add GetProfileByProfileIdNoAuthAsync method and update related controller logic --- .../Repositories/UserProfileRepository.cs | 25 +++++++++++++++++++ .../Controllers/LeaveBeginningController.cs | 4 +-- .../Controllers/LeaveReportController.cs | 16 ++++++++++++ .../Controllers/LeaveRequestController.cs | 11 ++++---- 4 files changed, 49 insertions(+), 7 deletions(-) diff --git a/BMA.EHR.Application/Repositories/UserProfileRepository.cs b/BMA.EHR.Application/Repositories/UserProfileRepository.cs index db63e056..23a33870 100644 --- a/BMA.EHR.Application/Repositories/UserProfileRepository.cs +++ b/BMA.EHR.Application/Repositories/UserProfileRepository.cs @@ -341,6 +341,31 @@ namespace BMA.EHR.Application.Repositories } } + + public async Task GetProfileByProfileIdNoAuthAsync(Guid profileId, string? accessToken) + { + try + { + var apiPath = $"{_configuration["API"]}/api/v1/org/unauthorize/profile/{profileId}"; + var apiKey = _configuration["API_KEY"]; + + var apiResult = await GetExternalAPIAsync(apiPath, accessToken ?? "", apiKey); + if (apiResult != null) + { + var raw = JsonConvert.DeserializeObject(apiResult); + if (raw != null) + return raw.Result; + } + + return null; + } + catch + { + throw; + } + } + + public async Task UpdateDutyTimeAsync(Guid profileId, Guid roundId, DateTime effectiveDate, string? accessToken) { try diff --git a/BMA.EHR.Leave/Controllers/LeaveBeginningController.cs b/BMA.EHR.Leave/Controllers/LeaveBeginningController.cs index c99aeaea..245176da 100644 --- a/BMA.EHR.Leave/Controllers/LeaveBeginningController.cs +++ b/BMA.EHR.Leave/Controllers/LeaveBeginningController.cs @@ -500,11 +500,11 @@ namespace BMA.EHR.Leave.Service.Controllers [HttpPut("schedule")] [AllowAnonymous] - public async Task> ScheduleLeaveBeginning([FromBody] EditLeaveBeginningDto req) + public async Task> ScheduleUpdateLeaveBeginningAsync([FromBody] EditLeaveBeginningDto req) { try { - var profile = await _userProfileRepository.GetProfileByProfileIdAsync(req.ProfileId, AccessToken); + var profile = await _userProfileRepository.GetProfileByProfileIdNoAuthAsync(req.ProfileId, AccessToken); if(profile == null) { return Error("ไม่พบข้อมูลข้าราชการหรือลูกจ้าง", StatusCodes.Status404NotFound); diff --git a/BMA.EHR.Leave/Controllers/LeaveReportController.cs b/BMA.EHR.Leave/Controllers/LeaveReportController.cs index 4e5a0961..51f8e6f0 100644 --- a/BMA.EHR.Leave/Controllers/LeaveReportController.cs +++ b/BMA.EHR.Leave/Controllers/LeaveReportController.cs @@ -149,6 +149,7 @@ namespace BMA.EHR.Leave.Service.Controllers } var fullName = $"{profile!.Prefix}{profile!.FirstName} {profile!.LastName}"; + var lastLeaveRequest = await _leaveRequestRepository.GetLastLeaveRequestByTypeForUserAsync(data.KeycloakUserId, @@ -156,7 +157,18 @@ namespace BMA.EHR.Leave.Service.Controllers var startFiscalYear = new DateTime(data.LeaveStartDate.Year - 1, 10, 1); var endFiscalYear = data.LeaveStartDate.Date.AddDays(-1); // นับจากวันที่ยื่นลา + + var thisYear = data.LeaveStartDate.Year; + var toDay = data.LeaveStartDate.Date; + if (toDay >= new DateTime(toDay.Year, 10, 1) && toDay <= new DateTime(toDay.Year, 12, 31)) + thisYear = thisYear + 1; + var leaveData = await _leaveBeginningRepository.GetByYearAndTypeIdForUser2Async(thisYear, data.Type.Id, data.KeycloakUserId); var sumLeave = await _leaveRequestRepository.GetSumApproveLeaveTotalByTypeAndRangeForUser(data.KeycloakUserId, data.Type.Id, startFiscalYear, endFiscalYear); + if (leaveData != null) + { + sumLeave += leaveData.BeginningLeaveDays; + } + var Oc = profile.isCommission == false ? profile.Oc.ToThaiNumber() : profile.Oc.Replace("สำนักงานคณะกรรมการข้าราชการกรุงเทพมหานคร", "สำนักงาน ก.ก.").ToThaiNumber(); @@ -334,6 +346,10 @@ namespace BMA.EHR.Leave.Service.Controllers var sumLeave = await _leaveRequestRepository.GetSumApproveLeaveTotalByTypeAndRangeForUser(data.KeycloakUserId, data.Type.Id, startFiscalYear, endFiscalYear); + if (leaveData != null) + { + sumLeave += leaveData.BeginningLeaveDays; + } //var sumLeave = leaveData == null ? 0.0 : leaveData.LeaveDaysUsed; var leaveLimit = leaveData == null ? 0.0 : leaveData.LeaveDays; diff --git a/BMA.EHR.Leave/Controllers/LeaveRequestController.cs b/BMA.EHR.Leave/Controllers/LeaveRequestController.cs index 391ec6d4..b2982860 100644 --- a/BMA.EHR.Leave/Controllers/LeaveRequestController.cs +++ b/BMA.EHR.Leave/Controllers/LeaveRequestController.cs @@ -1579,7 +1579,8 @@ namespace BMA.EHR.Leave.Service.Controllers var leaveData = await _leaveBeginningRepository.GetByYearAndTypeIdForUserAsync(thisYear, rawData.Type.Id, rawData.KeycloakUserId); - var restDayOld = govAge < 180 ? 0 : leaveData == null ? 0 : leaveData.LeaveDays - 10; + var restDayOld = govAge < 180 ? 0 : leaveData == null ? 0 : (leaveData.LeaveDays + leaveData.BeginningLeaveDays - 10); + if (restDayOld < 0) restDayOld = 0; var restDayCurrent = govAge < 180 ? 0 : 10; @@ -2509,6 +2510,8 @@ namespace BMA.EHR.Leave.Service.Controllers var endFiscalYear = rawData.LeaveStartDate.Date.AddDays(-1); // นับจากวันที่ยื่นลา var leaveSummary = await _leaveRequestRepository.GetSumApproveLeaveTotalByTypeAndRangeForUser(rawData.KeycloakUserId, rawData.Type.Id, startFiscalYear, endFiscalYear); //var leaveSummary = leaveData == null ? 0.0 : leaveData.LeaveDaysUsed; + if (leaveData != null) + leaveSummary += leaveData.LeaveDaysUsed; var extendLeave = 0.0; var leaveLimit = (double)rawData.Type.Limit; @@ -2516,7 +2519,7 @@ namespace BMA.EHR.Leave.Service.Controllers if (rawData.Type.Code == "LV-005") { leaveLimit = leaveData == null ? 0.0 : leaveData.LeaveDays; - extendLeave = leaveLimit - 10; + extendLeave = leaveLimit <= 0 ? 0 : leaveLimit - 10; } var result = new GetLeaveRequestForAdminByIdDto @@ -2615,8 +2618,6 @@ namespace BMA.EHR.Leave.Service.Controllers //OrganizationName = rawData.OrganizationName ?? "", // fix SIT ระบบบันทึกการลา>>รายการลา (ข้อมูลผู้สังกัดและเรียนไม่แสดง) #971 - - ApproveStep = rawData.ApproveStep ?? "-", LeaveLimit = rawData.Type.Limit + extendLeave, @@ -2742,7 +2743,7 @@ namespace BMA.EHR.Leave.Service.Controllers if (leaveType.Code == "LV-005") { leaveLimit = leaveData?.LeaveDays ?? 0.0; - extendLeave = leaveLimit - 10; + extendLeave = leaveLimit <= 0 ? 0 : leaveLimit - 10; } var data = new From c81220a049224bd91b3445c395c9be72b0d586e6 Mon Sep 17 00:00:00 2001 From: Suphonchai Phoonsawat Date: Wed, 11 Feb 2026 11:56:04 +0700 Subject: [PATCH 102/183] Remove unnecessary whitespace and comment out unused code in LeaveReportController --- BMA.EHR.Leave/Controllers/LeaveReportController.cs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/BMA.EHR.Leave/Controllers/LeaveReportController.cs b/BMA.EHR.Leave/Controllers/LeaveReportController.cs index 51f8e6f0..8c3de6c0 100644 --- a/BMA.EHR.Leave/Controllers/LeaveReportController.cs +++ b/BMA.EHR.Leave/Controllers/LeaveReportController.cs @@ -168,7 +168,7 @@ namespace BMA.EHR.Leave.Service.Controllers { sumLeave += leaveData.BeginningLeaveDays; } - + var Oc = profile.isCommission == false ? profile.Oc.ToThaiNumber() : profile.Oc.Replace("สำนักงานคณะกรรมการข้าราชการกรุงเทพมหานคร", "สำนักงาน ก.ก.").ToThaiNumber(); @@ -2885,6 +2885,14 @@ namespace BMA.EHR.Leave.Service.Controllers var leaveDays = await _leaveRequestRepository.GetSumApproveLeaveByRootAndRange(req.StartDate, req.EndDate, type, jsonData["result"]?.ToString(), nodeId, profileAdmin?.Node, req.nodeId, req.node); var enddate = req.EndDate.Date == req.StartDate.Date ? "" : $" - {req.EndDate.Date.ToThaiShortDate().ToThaiNumber()}"; + + // var thisYear = req.StartDate.Year; + // var toDay = req.StartDate.Date; + // if (toDay >= new DateTime(toDay.Year, 10, 1) && toDay <= new DateTime(toDay.Year, 12, 31)) + // thisYear = thisYear + 1; + + // var leaveData = await _leaveBeginningRepository.GetByYearAndTypeIdForUser2Async(thisYear, type, data.KeycloakUserId); + var result = new { template = "leave2", From 14fd9d5262492c8fd655b30d2412ee42afe930fd Mon Sep 17 00:00:00 2001 From: Suphonchai Phoonsawat Date: Wed, 11 Feb 2026 12:14:44 +0700 Subject: [PATCH 103/183] Refactor leave limit logic to use IsProbation property instead of govAge check --- BMA.EHR.Leave/Controllers/LeaveRequestController.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/BMA.EHR.Leave/Controllers/LeaveRequestController.cs b/BMA.EHR.Leave/Controllers/LeaveRequestController.cs index b2982860..120cc8ef 100644 --- a/BMA.EHR.Leave/Controllers/LeaveRequestController.cs +++ b/BMA.EHR.Leave/Controllers/LeaveRequestController.cs @@ -897,12 +897,12 @@ namespace BMA.EHR.Leave.Service.Controllers if (leaveType.Code.Trim().ToUpper() == "LV-005") { - if (govAge < 180) + if (profile.IsProbation! == true) leaveLimit = 0; else { leaveLimit = leaveData == null ? - govAge < 180 ? 0 : 10 + 10 : leaveData.LeaveDays; } } @@ -911,7 +911,7 @@ namespace BMA.EHR.Leave.Service.Controllers var restOldDay = leaveData == null ? 0 : leaveData.LeaveDays - 10; var restCurrentDay = 10.0; - if (govAge < 180) + if (profile.IsProbation! == true) { restOldDay = 0; restCurrentDay = 0; From a8271c8d79d465d3864066cac72decb90024a20f Mon Sep 17 00:00:00 2001 From: Suphonchai Phoonsawat Date: Thu, 12 Feb 2026 20:09:05 +0700 Subject: [PATCH 104/183] Update leave summary calculation to use BeginningLeaveDays instead of LeaveDaysUsed #2305 --- BMA.EHR.Leave/Controllers/LeaveRequestController.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BMA.EHR.Leave/Controllers/LeaveRequestController.cs b/BMA.EHR.Leave/Controllers/LeaveRequestController.cs index 120cc8ef..6580dd53 100644 --- a/BMA.EHR.Leave/Controllers/LeaveRequestController.cs +++ b/BMA.EHR.Leave/Controllers/LeaveRequestController.cs @@ -2511,7 +2511,7 @@ namespace BMA.EHR.Leave.Service.Controllers var leaveSummary = await _leaveRequestRepository.GetSumApproveLeaveTotalByTypeAndRangeForUser(rawData.KeycloakUserId, rawData.Type.Id, startFiscalYear, endFiscalYear); //var leaveSummary = leaveData == null ? 0.0 : leaveData.LeaveDaysUsed; if (leaveData != null) - leaveSummary += leaveData.LeaveDaysUsed; + leaveSummary += leaveData.BeginningLeaveDays; var extendLeave = 0.0; var leaveLimit = (double)rawData.Type.Limit; From 1b7bdd82e66bed2d4826bc68443357fd4b91efe4 Mon Sep 17 00:00:00 2001 From: Adisak Date: Fri, 13 Feb 2026 13:04:29 +0700 Subject: [PATCH 105/183] #2313 --- BMA.EHR.Placement.Service/Controllers/PlacementController.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BMA.EHR.Placement.Service/Controllers/PlacementController.cs b/BMA.EHR.Placement.Service/Controllers/PlacementController.cs index 51a09094..d3c28712 100644 --- a/BMA.EHR.Placement.Service/Controllers/PlacementController.cs +++ b/BMA.EHR.Placement.Service/Controllers/PlacementController.cs @@ -1791,7 +1791,7 @@ namespace BMA.EHR.Placement.Service.Controllers isLeave = false, dateRetire = (DateTime?)null, dateAppoint = r.commandDateAffect, - dateStart = r.commandDateAffect, + dateStart = p.ReportingDate, govAgeAbsent = 0, govAgePlus = 0, birthDate = (p.DateOfBirth == null || p.DateOfBirth == DateTime.MinValue) ? (DateTime?)null : p.DateOfBirth, From 7d3ec6c74e8d71a4b4858068982b51bcbb74320d Mon Sep 17 00:00:00 2001 From: Suphonchai Phoonsawat Date: Wed, 18 Feb 2026 16:34:35 +0700 Subject: [PATCH 106/183] Refactor ScheduleUpdateLeaveBeginningAsync to use ScheduleEditLeaveBeginningDto and remove unused profile checks --- .../Controllers/LeaveBeginningController.cs | 40 +++++++++---------- .../LeaveBeginnings/EditLeaveBeginningDto.cs | 16 ++++++++ 2 files changed, 36 insertions(+), 20 deletions(-) diff --git a/BMA.EHR.Leave/Controllers/LeaveBeginningController.cs b/BMA.EHR.Leave/Controllers/LeaveBeginningController.cs index 245176da..28f88302 100644 --- a/BMA.EHR.Leave/Controllers/LeaveBeginningController.cs +++ b/BMA.EHR.Leave/Controllers/LeaveBeginningController.cs @@ -500,15 +500,15 @@ namespace BMA.EHR.Leave.Service.Controllers [HttpPut("schedule")] [AllowAnonymous] - public async Task> ScheduleUpdateLeaveBeginningAsync([FromBody] EditLeaveBeginningDto req) + public async Task> ScheduleUpdateLeaveBeginningAsync([FromBody] ScheduleEditLeaveBeginningDto req) { try { - var profile = await _userProfileRepository.GetProfileByProfileIdNoAuthAsync(req.ProfileId, AccessToken); - if(profile == null) - { - return Error("ไม่พบข้อมูลข้าราชการหรือลูกจ้าง", StatusCodes.Status404NotFound); - } + // var profile = await _userProfileRepository.GetProfileByProfileIdNoAuthAsync(req.ProfileId, AccessToken); + // if(profile == null) + // { + // return Error("ไม่พบข้อมูลข้าราชการหรือลูกจ้าง", StatusCodes.Status404NotFound); + // } // check duplicate var oldData = await _context.LeaveBeginnings.FirstOrDefaultAsync(x => x.ProfileId == req.ProfileId && x.LeaveTypeId == req.LeaveTypeId @@ -520,23 +520,23 @@ namespace BMA.EHR.Leave.Service.Controllers oldData.LeaveTypeId = req.LeaveTypeId; oldData.LeaveYear = req.LeaveYear; oldData.LeaveDays = req.LeaveDays; - oldData.LeaveDaysUsed = req.LeaveDaysUsed; - oldData.LeaveCount = req.LeaveCount; - oldData.BeginningLeaveDays = req.BeginningLeaveDays; - oldData.BeginningLeaveCount = req.BeginningLeaveCount; + // oldData.LeaveDaysUsed = req.LeaveDaysUsed; + // oldData.LeaveCount = req.LeaveCount; + // oldData.BeginningLeaveDays = req.BeginningLeaveDays; + // oldData.BeginningLeaveCount = req.BeginningLeaveCount; - oldData.ProfileId = req.ProfileId; - oldData.Prefix = profile.Prefix; - oldData.FirstName = profile.FirstName; - oldData.LastName = profile.LastName; - oldData.RootDnaId = profile.RootDnaId; - oldData.Child1DnaId = profile.Child1DnaId; - oldData.Child2DnaId = profile.Child2DnaId; - oldData.Child3DnaId = profile.Child3DnaId; - oldData.Child4DnaId = profile.Child4DnaId; + // oldData.ProfileId = req.ProfileId; + // oldData.Prefix = profile.Prefix; + // oldData.FirstName = profile.FirstName; + // oldData.LastName = profile.LastName; + // oldData.RootDnaId = profile.RootDnaId; + // oldData.Child1DnaId = profile.Child1DnaId; + // oldData.Child2DnaId = profile.Child2DnaId; + // oldData.Child3DnaId = profile.Child3DnaId; + // oldData.Child4DnaId = profile.Child4DnaId; oldData.LastUpdateUserId = ""; - oldData.LastUpdateFullName = FullName ?? ""; + oldData.LastUpdateFullName = "System"; oldData.LastUpdatedAt = DateTime.Now; await _leaveBeginningRepository.UpdateAsync(oldData); diff --git a/BMA.EHR.Leave/DTOs/LeaveBeginnings/EditLeaveBeginningDto.cs b/BMA.EHR.Leave/DTOs/LeaveBeginnings/EditLeaveBeginningDto.cs index d4a2661f..fe0c433f 100644 --- a/BMA.EHR.Leave/DTOs/LeaveBeginnings/EditLeaveBeginningDto.cs +++ b/BMA.EHR.Leave/DTOs/LeaveBeginnings/EditLeaveBeginningDto.cs @@ -29,4 +29,20 @@ namespace BMA.EHR.Leave.Service.DTOs.LeaveBeginnings [Comment("จำนวนครั้งที่ลายกมา")] public int BeginningLeaveCount { get; set; } = 0; } + + + public class ScheduleEditLeaveBeginningDto + { + [Required] + public Guid ProfileId { get; set; } = Guid.Empty; + + [Required] + public Guid LeaveTypeId { get; set; } = Guid.Empty; + + [Required, Comment("ปีงบประมาณ")] + public int LeaveYear { get; set; } = 0; + + [Required, Comment("จำนวนวันลายกมา")] + public double LeaveDays { get; set; } = 0.0; + } } From de91fd0fa215d6e883c68a54d2268702832c68c2 Mon Sep 17 00:00:00 2001 From: Suphonchai Phoonsawat Date: Wed, 18 Feb 2026 16:47:14 +0700 Subject: [PATCH 107/183] Refactor LeaveBeginningController to restore profile checks and reset leave days to zero --- .../Controllers/LeaveBeginningController.cs | 38 +++++++++---------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/BMA.EHR.Leave/Controllers/LeaveBeginningController.cs b/BMA.EHR.Leave/Controllers/LeaveBeginningController.cs index 28f88302..559a784d 100644 --- a/BMA.EHR.Leave/Controllers/LeaveBeginningController.cs +++ b/BMA.EHR.Leave/Controllers/LeaveBeginningController.cs @@ -504,11 +504,11 @@ namespace BMA.EHR.Leave.Service.Controllers { try { - // var profile = await _userProfileRepository.GetProfileByProfileIdNoAuthAsync(req.ProfileId, AccessToken); - // if(profile == null) - // { - // return Error("ไม่พบข้อมูลข้าราชการหรือลูกจ้าง", StatusCodes.Status404NotFound); - // } + var profile = await _userProfileRepository.GetProfileByProfileIdNoAuthAsync(req.ProfileId, AccessToken); + if(profile == null) + { + return Error("ไม่พบข้อมูลข้าราชการหรือลูกจ้าง", StatusCodes.Status404NotFound); + } // check duplicate var oldData = await _context.LeaveBeginnings.FirstOrDefaultAsync(x => x.ProfileId == req.ProfileId && x.LeaveTypeId == req.LeaveTypeId @@ -525,15 +525,15 @@ namespace BMA.EHR.Leave.Service.Controllers // oldData.BeginningLeaveDays = req.BeginningLeaveDays; // oldData.BeginningLeaveCount = req.BeginningLeaveCount; - // oldData.ProfileId = req.ProfileId; - // oldData.Prefix = profile.Prefix; - // oldData.FirstName = profile.FirstName; - // oldData.LastName = profile.LastName; - // oldData.RootDnaId = profile.RootDnaId; - // oldData.Child1DnaId = profile.Child1DnaId; - // oldData.Child2DnaId = profile.Child2DnaId; - // oldData.Child3DnaId = profile.Child3DnaId; - // oldData.Child4DnaId = profile.Child4DnaId; + oldData.ProfileId = req.ProfileId; + oldData.Prefix = profile.Prefix; + oldData.FirstName = profile.FirstName; + oldData.LastName = profile.LastName; + oldData.RootDnaId = profile.RootDnaId; + oldData.Child1DnaId = profile.Child1DnaId; + oldData.Child2DnaId = profile.Child2DnaId; + oldData.Child3DnaId = profile.Child3DnaId; + oldData.Child4DnaId = profile.Child4DnaId; oldData.LastUpdateUserId = ""; oldData.LastUpdateFullName = "System"; @@ -547,10 +547,10 @@ namespace BMA.EHR.Leave.Service.Controllers leaveBeginning.LeaveTypeId = req.LeaveTypeId; leaveBeginning.LeaveYear = req.LeaveYear; leaveBeginning.LeaveDays = req.LeaveDays; - leaveBeginning.LeaveDaysUsed = req.LeaveDaysUsed; - leaveBeginning.LeaveCount = req.LeaveCount; - leaveBeginning.BeginningLeaveDays = req.BeginningLeaveDays; - leaveBeginning.BeginningLeaveCount = req.BeginningLeaveCount; + leaveBeginning.LeaveDaysUsed = 0; + leaveBeginning.LeaveCount = 0; + leaveBeginning.BeginningLeaveDays = 0; + leaveBeginning.BeginningLeaveCount = 0; leaveBeginning.ProfileId = req.ProfileId; leaveBeginning.Prefix = profile.Prefix; @@ -564,7 +564,7 @@ namespace BMA.EHR.Leave.Service.Controllers leaveBeginning.Child4DnaId = profile.Child4DnaId; leaveBeginning.CreatedUserId = ""; - leaveBeginning.CreatedFullName = FullName ?? ""; + leaveBeginning.CreatedFullName = "System"; leaveBeginning.CreatedAt = DateTime.Now; await _leaveBeginningRepository.AddAsync(leaveBeginning); From d70ed254c0f6a3816edfeae47c028d28fbb8ac8d Mon Sep 17 00:00:00 2001 From: Suphonchai Phoonsawat Date: Wed, 18 Feb 2026 16:56:48 +0700 Subject: [PATCH 108/183] Enhance LeaveRequestController to restore profile checks and implement officer notification logic #2164 --- .../Controllers/LeaveRequestController.cs | 31 +++++++++++++++---- 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/BMA.EHR.Leave/Controllers/LeaveRequestController.cs b/BMA.EHR.Leave/Controllers/LeaveRequestController.cs index 6580dd53..a8a5e9b4 100644 --- a/BMA.EHR.Leave/Controllers/LeaveRequestController.cs +++ b/BMA.EHR.Leave/Controllers/LeaveRequestController.cs @@ -2323,12 +2323,31 @@ namespace BMA.EHR.Leave.Service.Controllers await _leaveRequestRepository.SendToOfficerAsync(id); // Remove Workflow Integration - // var userId = UserId == null ? Guid.Empty : Guid.Parse(UserId); - // var profile = await _userProfileRepository.GetProfileByKeycloakIdAsync(userId, AccessToken); - // if (profile == null) - // { - // return Error(GlobalMessages.DataNotFound, StatusCodes.Status404NotFound); - // } + var userId = UserId == null ? Guid.Empty : Guid.Parse(UserId); + var profile = await _userProfileRepository.GetProfileByKeycloakIdAsync(userId, AccessToken); + if (profile == null) + { + return Error(GlobalMessages.DataNotFound, StatusCodes.Status404NotFound); + } + + // Get Officer List + var officers = await _userProfileRepository.GetOCStaffAsync(profile.Id, AccessToken); + if(officers != null && officers.Count > 0) + { + foreach (var officer in officers) + { + // Send Notification + var noti = new Notification + { + Body = $"มีคำร้องขอลาจาก {profile.Prefix}{profile.FirstName} {profile.LastName} รอรับการอนุมัติจากคุณ", + ReceiverUserId = officer.ProfileId, + Type = "", + Payload = $"{URL}/leave/detail/{id}", + }; + _appDbContext.Set().Add(noti); + } + await _appDbContext.SaveChangesAsync(); + } // var baseAPIOrg = _configuration["API"]; // var apiUrlOrg = $"{baseAPIOrg}/org/workflow/add-workflow"; // if (profile.ProfileType == "OFFICER") From ddaa339e9fd3d3ea5d7e6222177a11f7243d7aad Mon Sep 17 00:00:00 2001 From: Suphonchai Phoonsawat Date: Wed, 18 Feb 2026 20:24:30 +0700 Subject: [PATCH 109/183] Refactor LeaveRequestController and LeaveReportController to use GetSumApproveLeaveTotalByTypeAndRangeForUser2 method and update fiscal year end date calculation #2305 --- .../LeaveRequests/LeaveRequestRepository.cs | 20 +++++++++++++++++++ .../Controllers/LeaveReportController.cs | 8 ++++---- .../Controllers/LeaveRequestController.cs | 4 ++-- 3 files changed, 26 insertions(+), 6 deletions(-) diff --git a/BMA.EHR.Application/Repositories/Leaves/LeaveRequests/LeaveRequestRepository.cs b/BMA.EHR.Application/Repositories/Leaves/LeaveRequests/LeaveRequestRepository.cs index 57a7fdc4..54107021 100644 --- a/BMA.EHR.Application/Repositories/Leaves/LeaveRequests/LeaveRequestRepository.cs +++ b/BMA.EHR.Application/Repositories/Leaves/LeaveRequests/LeaveRequestRepository.cs @@ -558,6 +558,7 @@ namespace BMA.EHR.Application.Repositories.Leaves.LeaveRequests var data = await _dbContext.Set().AsQueryable().AsNoTracking() .Include(x => x.Type) .Where(x => x.LeaveStartDate.Date < beforeDate.Date) + //.Where(x => x.CreatedAt < beforeDate) .Where(x => x.KeycloakUserId == keycloakUserId) .Where(x => x.Type.Id == leaveTypeId) .Where(x => x.LeaveStatus == "APPROVE" || x.LeaveStatus == "DELETING") @@ -1835,6 +1836,7 @@ namespace BMA.EHR.Application.Repositories.Leaves.LeaveRequests .Include(x => x.Type) .Where(x => x.KeycloakUserId == keycloakUserId) .Where(x => x.Type.Id == leaveTypeId) + //.Where(x => x.CreatedAt >= startDate && x.CreatedAt <= endDate) .Where(x => x.LeaveStartDate.Date >= startDate.Date && x.LeaveStartDate.Date <= endDate.Date) .Where(x => x.LeaveStatus == "APPROVE" || x.LeaveStatus == "DELETING") .ToListAsync(); @@ -1845,6 +1847,23 @@ namespace BMA.EHR.Application.Repositories.Leaves.LeaveRequests return 0; } + public async Task GetSumApproveLeaveTotalByTypeAndRangeForUser2(Guid keycloakUserId, Guid leaveTypeId, DateTime startDate, DateTime endDate) + { + var data = await _dbContext.Set().AsQueryable().AsNoTracking() + .Include(x => x.Type) + .Where(x => x.KeycloakUserId == keycloakUserId) + .Where(x => x.Type.Id == leaveTypeId) + .Where(x => x.CreatedAt.Date >= startDate && x.CreatedAt < endDate) + //.Where(x => x.LeaveStartDate.Date >= startDate.Date && x.LeaveStartDate.Date <= endDate.Date) + .Where(x => x.LeaveStatus == "APPROVE" || x.LeaveStatus == "DELETING") + .ToListAsync(); + + if (data.Count > 0) + return data.Sum(x => x.LeaveTotal); + else + return 0; + } + public async Task GetCountApproveLeaveByTypeAndRangeForUser(Guid keycloakUserId, Guid leaveTypeId, DateTime startDate, DateTime endDate) { var data = await _dbContext.Set().AsQueryable().AsNoTracking() @@ -1852,6 +1871,7 @@ namespace BMA.EHR.Application.Repositories.Leaves.LeaveRequests .Where(x => x.KeycloakUserId == keycloakUserId) .Where(x => x.Type.Id == leaveTypeId) .Where(x => x.LeaveStartDate.Date >= startDate.Date && x.LeaveStartDate.Date <= endDate.Date) + //.Where(x => x.CreatedAt >= startDate && x.CreatedAt <= endDate) .Where(x => x.LeaveStatus == "APPROVE" || x.LeaveStatus == "DELETING") .ToListAsync(); diff --git a/BMA.EHR.Leave/Controllers/LeaveReportController.cs b/BMA.EHR.Leave/Controllers/LeaveReportController.cs index 8c3de6c0..96ff61ee 100644 --- a/BMA.EHR.Leave/Controllers/LeaveReportController.cs +++ b/BMA.EHR.Leave/Controllers/LeaveReportController.cs @@ -156,14 +156,14 @@ namespace BMA.EHR.Leave.Service.Controllers data.Type.Id, data.LeaveStartDate.Date); var startFiscalYear = new DateTime(data.LeaveStartDate.Year - 1, 10, 1); - var endFiscalYear = data.LeaveStartDate.Date.AddDays(-1); // นับจากวันที่ยื่นลา + var endFiscalYear = data.CreatedAt; var thisYear = data.LeaveStartDate.Year; var toDay = data.LeaveStartDate.Date; if (toDay >= new DateTime(toDay.Year, 10, 1) && toDay <= new DateTime(toDay.Year, 12, 31)) thisYear = thisYear + 1; var leaveData = await _leaveBeginningRepository.GetByYearAndTypeIdForUser2Async(thisYear, data.Type.Id, data.KeycloakUserId); - var sumLeave = await _leaveRequestRepository.GetSumApproveLeaveTotalByTypeAndRangeForUser(data.KeycloakUserId, data.Type.Id, startFiscalYear, endFiscalYear); + var sumLeave = await _leaveRequestRepository.GetSumApproveLeaveTotalByTypeAndRangeForUser2(data.KeycloakUserId, data.Type.Id, startFiscalYear, endFiscalYear); if (leaveData != null) { sumLeave += leaveData.BeginningLeaveDays; @@ -331,7 +331,7 @@ namespace BMA.EHR.Leave.Service.Controllers var fullName = $"{profile!.Prefix}{profile!.FirstName} {profile!.LastName}"; var startFiscalYear = new DateTime(data.LeaveStartDate.Year - 1, 10, 1); - var endFiscalYear = data.LeaveStartDate.Date.AddDays(-1); // นับจากวันที่ยื่นลา + var endFiscalYear = data.CreatedAt; var thisYear = data.LeaveStartDate.Year; var toDay = data.LeaveStartDate.Date; @@ -345,7 +345,7 @@ namespace BMA.EHR.Leave.Service.Controllers //var sumLeave = await _leaveRequestRepository.GetSumApproveLeaveTotalByTypeAndRangeForUser(data.KeycloakUserId, data.Type.Id, startFiscalYear, endFiscalYear); - var sumLeave = await _leaveRequestRepository.GetSumApproveLeaveTotalByTypeAndRangeForUser(data.KeycloakUserId, data.Type.Id, startFiscalYear, endFiscalYear); + var sumLeave = await _leaveRequestRepository.GetSumApproveLeaveTotalByTypeAndRangeForUser2(data.KeycloakUserId, data.Type.Id, startFiscalYear, endFiscalYear); if (leaveData != null) { sumLeave += leaveData.BeginningLeaveDays; diff --git a/BMA.EHR.Leave/Controllers/LeaveRequestController.cs b/BMA.EHR.Leave/Controllers/LeaveRequestController.cs index a8a5e9b4..926842e4 100644 --- a/BMA.EHR.Leave/Controllers/LeaveRequestController.cs +++ b/BMA.EHR.Leave/Controllers/LeaveRequestController.cs @@ -2526,8 +2526,8 @@ namespace BMA.EHR.Leave.Service.Controllers var leaveData = await _leaveBeginningRepository.GetByYearAndTypeIdForUser2Async(thisYear, rawData.Type.Id, rawData.KeycloakUserId); var startFiscalYear = new DateTime(rawData.LeaveStartDate.Year - 1, 10, 1); - var endFiscalYear = rawData.LeaveStartDate.Date.AddDays(-1); // นับจากวันที่ยื่นลา - var leaveSummary = await _leaveRequestRepository.GetSumApproveLeaveTotalByTypeAndRangeForUser(rawData.KeycloakUserId, rawData.Type.Id, startFiscalYear, endFiscalYear); + var endFiscalYear = rawData.CreatedAt; + var leaveSummary = await _leaveRequestRepository.GetSumApproveLeaveTotalByTypeAndRangeForUser2(rawData.KeycloakUserId, rawData.Type.Id, startFiscalYear, endFiscalYear); //var leaveSummary = leaveData == null ? 0.0 : leaveData.LeaveDaysUsed; if (leaveData != null) leaveSummary += leaveData.BeginningLeaveDays; From c42aaa38f649c4e5ac279e31a00efa6b444936ab Mon Sep 17 00:00:00 2001 From: Suphonchai Phoonsawat Date: Thu, 19 Feb 2026 10:11:39 +0700 Subject: [PATCH 110/183] Fix API path in UserProfileRepository to remove redundant versioning --- BMA.EHR.Application/Repositories/UserProfileRepository.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BMA.EHR.Application/Repositories/UserProfileRepository.cs b/BMA.EHR.Application/Repositories/UserProfileRepository.cs index 23a33870..6ac6b9ed 100644 --- a/BMA.EHR.Application/Repositories/UserProfileRepository.cs +++ b/BMA.EHR.Application/Repositories/UserProfileRepository.cs @@ -346,7 +346,7 @@ namespace BMA.EHR.Application.Repositories { try { - var apiPath = $"{_configuration["API"]}/api/v1/org/unauthorize/profile/{profileId}"; + var apiPath = $"{_configuration["API"]}/org/unauthorize/profile/{profileId}"; var apiKey = _configuration["API_KEY"]; var apiResult = await GetExternalAPIAsync(apiPath, accessToken ?? "", apiKey); From b8df2d402420ee31950ce80a2190378db336c91d Mon Sep 17 00:00:00 2001 From: Suphonchai Phoonsawat Date: Thu, 19 Feb 2026 14:06:59 +0700 Subject: [PATCH 111/183] Add NodaTime package and update LeaveRequestController to use LocalDate for date calculations #2324 --- BMA.EHR.Leave/BMA.EHR.Leave.csproj | 1 + .../Controllers/LeaveRequestController.cs | 41 +++++++++++-------- 2 files changed, 25 insertions(+), 17 deletions(-) diff --git a/BMA.EHR.Leave/BMA.EHR.Leave.csproj b/BMA.EHR.Leave/BMA.EHR.Leave.csproj index 83132dd1..28f42590 100644 --- a/BMA.EHR.Leave/BMA.EHR.Leave.csproj +++ b/BMA.EHR.Leave/BMA.EHR.Leave.csproj @@ -45,6 +45,7 @@ + diff --git a/BMA.EHR.Leave/Controllers/LeaveRequestController.cs b/BMA.EHR.Leave/Controllers/LeaveRequestController.cs index 926842e4..6d16e8de 100644 --- a/BMA.EHR.Leave/Controllers/LeaveRequestController.cs +++ b/BMA.EHR.Leave/Controllers/LeaveRequestController.cs @@ -17,6 +17,7 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using Newtonsoft.Json; using Newtonsoft.Json.Linq; +using NodaTime; using Org.BouncyCastle.Asn1.Pkcs; using Sentry; using Swashbuckle.AspNetCore.Annotations; @@ -986,6 +987,13 @@ namespace BMA.EHR.Leave.Service.Controllers // var profile = await _userProfileRepository.GetProfileByKeycloakIdAsync(userId, AccessToken); var profile = await _userProfileRepository.GetProfileByKeycloakIdNewAsync(userId, AccessToken); var govAge = (profile?.DateStart?.Date ?? DateTime.Now.Date).DiffDay(DateTime.Now.Date); + var date1Raw = profile?.DateStart?.Date ?? DateTime.Now.Date; + var date1 = new LocalDate(date1Raw.Year, date1Raw.Month, date1Raw.Day); + var date2 = new LocalDate(DateTime.Now.Year, DateTime.Now.Month, DateTime.Now.Day); + Period period = Period.Between(date1, date2); + var govAgeMonth = period.Months; + var govAgeYear = period.Years; + var thisYear = DateTime.Now.Year; var message = string.Empty; @@ -1055,12 +1063,11 @@ namespace BMA.EHR.Leave.Service.Controllers case "LV-002": // fix issue : ระบบลา (ขรก.) >> ลากิจส่วนตัว (กรณียื่นขอลาเกิน 45 วัน/ปี) #829 // fix issue : ระบบลา (ขรก.) >> ลากิจส่วนตัว (กรณีผู้เข้ารับราชการไม่เกิน 1 ปี ยื่นขอลาเกิน 15 วัน/ปี) #831 - if (govAge <= 365) + if (govAgeYear <= 1) { isLeave = (totalDay - (sumWorkDay + sumWeekend) + approveDay) <= 15; if (!isLeave) message = "จำนวนวันลาเกินที่กำหนด"; } - else { isLeave = (totalDay - (sumWorkDay + sumWeekend) + approveDay) <= 45; @@ -1084,13 +1091,14 @@ namespace BMA.EHR.Leave.Service.Controllers // fix issue : ระบบลา (ขรก.) >> ลาพักผ่อน (กรณีรับราชการไม่ถึง 6 เดือน) #838 //var leavePrevYear = (await _leaveRequestRepository.GetSumApproveLeaveAsync(fiscalYear - 1)).Where(x => x.LeaveTypeCode == "LV-005" && x.KeycloakUserId == userId).FirstOrDefault(); //var leavePrevYearRemain = 10 - (leavePrevYear == null ? 0 : leavePrevYear.SumLeaveDay); // หายอดวันลาที่เหลือของปีก่อน - if (profile.IsProbation! == true) - { - isLeave = false; - if (!isLeave) message = "ยังอยู่ในช่วงทดลองปฏิบัติราชการ ไม่สามารถลาพักผ่อนได้"; - } - //else if (govAge >= 180) - else + // if (profile.IsProbation! == true) + // { + // isLeave = false; + // if (!isLeave) message = "ยังอยู่ในช่วงทดลองปฏิบัติราชการ ไม่สามารถลาพักผ่อนได้"; + // } + // else + if (govAgeMonth >= 6) + //else { isLeave = (totalDay - (sumWorkDay + sumWeekend) + approveDay) <= (limitDay); if (!isLeave) message = "จำนวนวันลาเกินที่กำหนด"; @@ -1112,17 +1120,16 @@ namespace BMA.EHR.Leave.Service.Controllers // isLeave = (totalDay - (sumWorkDay + sumWeekend) + sumApproveLeave) <= (10 + leavePrevYearRemain); // if (!isLeave) message = "จำนวนวันลาเกินที่กำหนด"; // } - - // else - // { - // isLeave = false; - // if (!isLeave) message = "อายุราชการน้อยกว่า 6 เดือนหรือ 180 วัน"; - // } + else + { + isLeave = false; + if (!isLeave) message = "อายุราชการน้อยกว่า 6 เดือนหรือ 180 วัน"; + } break; case "LV-006": // fix issue : ระบบลา(ขรก.) >> ลาอุปสมบทหรือการลาประกอบพิธีฮัจย์ฯ(กรณีรับราชการน้อยกว่า 1 ปี) #840 - if (govAge < 365) + if (govAgeYear < 1) { isLeave = false; if (!isLeave) message = "อายุราชการน้อยกว่า 1 ปีหรือ 365 วัน"; @@ -1148,7 +1155,7 @@ namespace BMA.EHR.Leave.Service.Controllers break; case "LV-008": case "LV-009": - isLeave = govAge >= 365; + isLeave = govAgeYear >= 1; if (!isLeave) message = "อายุราชการน้อยกว่า 1 ปีหรือ 365 วัน"; break; case "LV-010": From d74830841903432a21c59ba8e873817bfda3198f Mon Sep 17 00:00:00 2001 From: Suphonchai Phoonsawat Date: Thu, 19 Feb 2026 15:07:04 +0700 Subject: [PATCH 112/183] Enhance LeaveController to implement check-out logic and status validation based on last check-in record --- BMA.EHR.Leave/Controllers/LeaveController.cs | 128 +++++++++++++++++++ 1 file changed, 128 insertions(+) diff --git a/BMA.EHR.Leave/Controllers/LeaveController.cs b/BMA.EHR.Leave/Controllers/LeaveController.cs index 665b209c..08dcb575 100644 --- a/BMA.EHR.Leave/Controllers/LeaveController.cs +++ b/BMA.EHR.Leave/Controllers/LeaveController.cs @@ -957,15 +957,24 @@ 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])) { 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) { @@ -1015,6 +1024,124 @@ namespace BMA.EHR.Leave.Service.Controllers // create check in object if (data.CheckInId == null) { + if (lastCheckIn != null && lastCheckIn.CheckOut == null) + { + var checkout = await _userTimeStampRepository.GetByIdAsync(lastCheckIn!.Id); + if(checkout != null) + { + var currentCheckInProcess = await _processUserTimeStampRepository.GetTimestampByDateAsync(userId, checkout.CheckIn.Date); + var checkout_process = await _processUserTimeStampRepository.GetByIdAsync(currentCheckInProcess!.Id); + var endTime1 = ""; + var startTime1 = ""; + var endTimeMorning1 = ""; + if (!data.IsLocation && data.LocationName == "ไปประชุม / อบรม / สัมมนา") + { + startTime1 = "13:00"; + endTime1 = "14:30"; + endTimeMorning1 = "12:00"; + } + else + { + endTime1 = duty.EndTimeAfternoon; + startTime1 = duty.StartTimeAfternoon; + endTimeMorning1 = duty.EndTimeMorning; + } + + string checkOutStatus = "NORMAL"; + var leaveReq1 = await _leaveRequestRepository.GetLeavePeriodAsync(userId, currentDate.Date); + if (leaveReq1 != null) + { + var leaveRange = leaveReq1.LeaveRangeEnd == null ? "" : leaveReq1.LeaveRangeEnd.ToUpper(); + if (leaveRange == "AFTERNOON" || leaveRange == "ALL") + { + if(DateTime.Parse(currentDate.ToString("yyyy-MM-dd HH:mm")) < + DateTime.Parse($"{currentDate.ToString("yyyy-MM-dd")} {endTimeMorning1}")) + checkOutStatus = "ABSENT"; + else + checkOutStatus = "NORMAL"; + } + else + { + // fix issue : SIT ระบบบันทึกเวลาปฏิบัติงาน>>ลงเวลาเข้า-ออกงาน (กรณีลงเวลาออกอีกวัน) #921 + var currentDateTime = DateTime.Parse(currentDate.ToString("yyyy-MM-dd HH:mm")); + var dutyEndTimeAfternoon = DateTime.Parse($"{checkout.CheckIn.ToString("yyyy-MM-dd")} {endTime1}"); + var dutyEndTimeMorning = DateTime.Parse($"{checkout.CheckIn.ToString("yyyy-MM-dd")} {endTimeMorning1}"); + + + if(currentDateTime.Date > checkout.CheckIn.Date) + { + // ถ้า check-out เป็นวันถัดไป สถานะปกติเสมอ + checkOutStatus = "NORMAL"; + } + else + { + // ถ้า check-out เป็นวันเดียวกับ check-in + // ตรวจสอบเวลาว่าสิ้นสุดก่อนบ่ายหรือไม่ + if(currentDateTime < dutyEndTimeMorning) // ถ้าออกก่อนเวลาสิ้นสุดตอนเช้า ขาดราชการ + { + checkOutStatus = "ABSENT"; + } + else if(currentDateTime >= dutyEndTimeAfternoon) // ถ้าออกหลังเวลาสิ้นสุดตอนบ่าย ปกติ + { + checkOutStatus = "NORMAL"; + } + else + { + checkOutStatus = "ABSENT"; + } + } + } + } + else + { + // fix issue : SIT ระบบบันทึกเวลาปฏิบัติงาน>>ลงเวลาเข้า-ออกงาน (กรณีลงเวลาออกอีกวัน) #921 + var currentDateTime = DateTime.Parse(currentDate.ToString("yyyy-MM-dd HH:mm")); + var dutyEndTimeAfternoon = DateTime.Parse($"{checkout.CheckIn.ToString("yyyy-MM-dd")} {endTime1}"); + var dutyEndTimeMorning = DateTime.Parse($"{checkout.CheckIn.ToString("yyyy-MM-dd")} {endTimeMorning1}"); + + + if(currentDateTime.Date > checkout.CheckIn.Date) + { + // ถ้า check-out เป็นวันถัดไป สถานะปกติเสมอ + checkOutStatus = "NORMAL"; + } + else + { + // ถ้า check-out เป็นวันเดียวกับ check-in + // ตรวจสอบเวลาว่าสิ้นสุดก่อนบ่ายหรือไม่ + if(currentDateTime < dutyEndTimeMorning) // ถ้าออกก่อนเวลาสิ้นสุดตอนเช้า ขาดราชการ + { + checkOutStatus = "ABSENT"; + } + else if(currentDateTime >= dutyEndTimeAfternoon) // ถ้าออกหลังเวลาสิ้นสุดตอนบ่าย ปกติ + { + checkOutStatus = "NORMAL"; + } + else + { + checkOutStatus = "ABSENT"; + } + } + + } + + if (checkout_process != null) + { + checkout_process.CheckOutLat = data.Lat; + checkout_process.CheckOutLon = data.Lon; + checkout_process.IsLocationCheckOut = data.IsLocation; + checkout_process.CheckOutLocationName = data.LocationName; + checkout_process.CheckOutPOI = data.POI; + checkout_process.CheckOutRemark = data.Remark; + checkout_process.CheckOutImageUrl = fileNameCheckOut; + checkout_process.CheckOut = currentDate; + checkout_process.CheckOutStatus = checkOutStatus; + + await _processUserTimeStampRepository.UpdateAsync(checkout_process); + } + } + } + // validate duplicate check in var currentCheckIn = await _userTimeStampRepository.GetTimestampByDateAsync(userId, currentDate); @@ -1167,6 +1294,7 @@ namespace BMA.EHR.Leave.Service.Controllers else { var checkout = await _userTimeStampRepository.GetByIdAsync(data.CheckInId.Value); + //var currentCheckIn = await _userTimeStampRepository.GetTimestampByDateAsync(userId, currentDate); if (checkout == null) { From 65feb994ee5852e8f77c4655686d5827020973a3 Mon Sep 17 00:00:00 2001 From: Suphonchai Phoonsawat Date: Thu, 19 Feb 2026 15:10:44 +0700 Subject: [PATCH 113/183] Add GetLastLeaveRequestByTypeForUserAsync2 method and update LeaveReportController to use new method for fetching last leave request #2305 --- .../LeaveRequests/LeaveRequestRepository.cs | 16 ++++++++++++++++ .../Controllers/LeaveReportController.cs | 4 ++-- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/BMA.EHR.Application/Repositories/Leaves/LeaveRequests/LeaveRequestRepository.cs b/BMA.EHR.Application/Repositories/Leaves/LeaveRequests/LeaveRequestRepository.cs index 54107021..130dba3b 100644 --- a/BMA.EHR.Application/Repositories/Leaves/LeaveRequests/LeaveRequestRepository.cs +++ b/BMA.EHR.Application/Repositories/Leaves/LeaveRequests/LeaveRequestRepository.cs @@ -569,6 +569,22 @@ namespace BMA.EHR.Application.Repositories.Leaves.LeaveRequests return data; } + public async Task GetLastLeaveRequestByTypeForUserAsync2(Guid keycloakUserId, Guid leaveTypeId, DateTime beforeDate) + { + var data = await _dbContext.Set().AsQueryable().AsNoTracking() + .Include(x => x.Type) + //.Where(x => x.LeaveStartDate.Date < beforeDate.Date) + .Where(x => x.CreatedAt < beforeDate) + .Where(x => x.KeycloakUserId == keycloakUserId) + .Where(x => x.Type.Id == leaveTypeId) + .Where(x => x.LeaveStatus == "APPROVE" || x.LeaveStatus == "DELETING") + //.Where(x => x.LeaveStatus != "REJECT" && x.LeaveStatus != "DELETE") + .OrderByDescending(x => x.LeaveStartDate.Date) + .FirstOrDefaultAsync(); + + return data; + } + public async Task> GetCancelLeaveRequestForAdminAsync(int year, Guid type, string status, string role, string? nodeId, int? node) { var rawData = _dbContext.Set().AsNoTracking() diff --git a/BMA.EHR.Leave/Controllers/LeaveReportController.cs b/BMA.EHR.Leave/Controllers/LeaveReportController.cs index 96ff61ee..2b555e73 100644 --- a/BMA.EHR.Leave/Controllers/LeaveReportController.cs +++ b/BMA.EHR.Leave/Controllers/LeaveReportController.cs @@ -152,8 +152,8 @@ namespace BMA.EHR.Leave.Service.Controllers var lastLeaveRequest = - await _leaveRequestRepository.GetLastLeaveRequestByTypeForUserAsync(data.KeycloakUserId, - data.Type.Id, data.LeaveStartDate.Date); + await _leaveRequestRepository.GetLastLeaveRequestByTypeForUserAsync2(data.KeycloakUserId, + data.Type.Id, data.CreatedAt); var startFiscalYear = new DateTime(data.LeaveStartDate.Year - 1, 10, 1); var endFiscalYear = data.CreatedAt; From 869defcc7ea18bd65e8ff58d15a4a303d0f06528 Mon Sep 17 00:00:00 2001 From: Suphonchai Phoonsawat Date: Thu, 19 Feb 2026 15:19:41 +0700 Subject: [PATCH 114/183] Update LeaveRequestRepository to order leave requests by creation date instead of start date #2305 --- .../Repositories/Leaves/LeaveRequests/LeaveRequestRepository.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BMA.EHR.Application/Repositories/Leaves/LeaveRequests/LeaveRequestRepository.cs b/BMA.EHR.Application/Repositories/Leaves/LeaveRequests/LeaveRequestRepository.cs index 130dba3b..10e7c927 100644 --- a/BMA.EHR.Application/Repositories/Leaves/LeaveRequests/LeaveRequestRepository.cs +++ b/BMA.EHR.Application/Repositories/Leaves/LeaveRequests/LeaveRequestRepository.cs @@ -579,7 +579,7 @@ namespace BMA.EHR.Application.Repositories.Leaves.LeaveRequests .Where(x => x.Type.Id == leaveTypeId) .Where(x => x.LeaveStatus == "APPROVE" || x.LeaveStatus == "DELETING") //.Where(x => x.LeaveStatus != "REJECT" && x.LeaveStatus != "DELETE") - .OrderByDescending(x => x.LeaveStartDate.Date) + .OrderByDescending(x => x.CreatedAt) .FirstOrDefaultAsync(); return data; From 256da24caf188d695a5498e96a08a42133b06fd3 Mon Sep 17 00:00:00 2001 From: harid Date: Thu, 19 Feb 2026 17:01:44 +0700 Subject: [PATCH 115/183] =?UTF-8?q?=E0=B9=81=E0=B8=81=E0=B9=89=E0=B9=84?= =?UTF-8?q?=E0=B8=82=E0=B8=AA=E0=B8=B4=E0=B8=97=E0=B8=98=E0=B8=B4=E0=B9=8C?= =?UTF-8?q?=20PARENT=20=E0=B9=83=E0=B8=AB=E0=B9=89=E0=B9=80=E0=B8=AB?= =?UTF-8?q?=E0=B9=87=E0=B8=99=E0=B8=82=E0=B9=89=E0=B8=AD=E0=B8=A1=E0=B8=B9?= =?UTF-8?q?=E0=B8=A5=E0=B8=97=E0=B8=B1=E0=B9=89=E0=B8=87=E0=B8=AB=E0=B8=A1?= =?UTF-8?q?=E0=B8=94=E0=B8=97=E0=B8=B8=E0=B8=81=E0=B8=AB=E0=B8=99=E0=B9=88?= =?UTF-8?q?=E0=B8=A7=E0=B8=A2=E0=B8=87=E0=B8=B2=E0=B8=99=20#54?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../LeaveRequests/LeaveRequestRepository.cs | 38 +++++++++---------- .../AdditionalCheckRequestRepository.cs | 10 ++--- .../ProcessUserTimeStampRepository.cs | 20 +++++----- .../TimeAttendants/UserTimeStampRepository.cs | 12 +++--- .../DisciplineComplaint_AppealController.cs | 12 +++--- .../DisciplineSuspendController.cs | 6 +-- .../Controllers/InsigniaManageController.cs | 24 ++++++------ .../Controllers/LeaveBeginningController.cs | 12 +++--- BMA.EHR.Leave/Controllers/LeaveController.cs | 10 ++--- .../Controllers/LeaveReportController.cs | 10 ++--- .../Controllers/LeaveRequestController.cs | 6 +-- .../PlacementAppointmentController.cs | 12 +++--- .../PlacementAppointmentEmployeeController.cs | 12 +++--- .../Controllers/PlacementOfficerController.cs | 12 +++--- .../Controllers/PlacementReceiveController.cs | 12 +++--- .../PlacementRepatriationController.cs | 12 +++--- .../PlacementTransferController.cs | 12 +++--- .../RetirementDeceasedController.cs | 12 +++--- .../Controllers/RetirementOtherController.cs | 12 +++--- .../Controllers/RetirementOutController.cs | 12 +++--- .../Controllers/RetirementResignController.cs | 36 +++++++++--------- .../RetirementResignEmployeeController.cs | 36 +++++++++--------- 22 files changed, 170 insertions(+), 170 deletions(-) diff --git a/BMA.EHR.Application/Repositories/Leaves/LeaveRequests/LeaveRequestRepository.cs b/BMA.EHR.Application/Repositories/Leaves/LeaveRequests/LeaveRequestRepository.cs index 10e7c927..2caecc9f 100644 --- a/BMA.EHR.Application/Repositories/Leaves/LeaveRequests/LeaveRequestRepository.cs +++ b/BMA.EHR.Application/Repositories/Leaves/LeaveRequests/LeaveRequestRepository.cs @@ -307,11 +307,11 @@ namespace BMA.EHR.Application.Repositories.Leaves.LeaveRequests rawData = rawData .Where(x => x.RootDnaId == Guid.Parse(nodeId!)); } - else if (role == "PARENT") - { - rawData = rawData - .Where(x => x.RootDnaId == Guid.Parse(nodeId!) && x.Child1DnaId != null); - } + // else if (role == "PARENT") + // { + // rawData = rawData + // .Where(x => x.RootDnaId == Guid.Parse(nodeId!) && x.Child1DnaId != null); + // } else if (role == "NORMAL") { rawData = rawData @@ -421,11 +421,11 @@ namespace BMA.EHR.Application.Repositories.Leaves.LeaveRequests rawData = rawData .Where(x => x.RootDnaId == Guid.Parse(nodeId!)); } - else if (role == "PARENT") - { - rawData = rawData - .Where(x => x.RootDnaId == Guid.Parse(nodeId!) && x.Child1DnaId != null); - } + // else if (role == "PARENT") + // { + // rawData = rawData + // .Where(x => x.RootDnaId == Guid.Parse(nodeId!) && x.Child1DnaId != null); + // } else if (role == "NORMAL") { rawData = rawData @@ -627,11 +627,11 @@ namespace BMA.EHR.Application.Repositories.Leaves.LeaveRequests rawData = rawData .Where(x => x.RootDnaId == Guid.Parse(nodeId!)); } - else if (role == "PARENT") - { - rawData = rawData - .Where(x => x.RootDnaId == Guid.Parse(nodeId!) && x.Child1DnaId != null); - } + // else if (role == "PARENT") + // { + // rawData = rawData + // .Where(x => x.RootDnaId == Guid.Parse(nodeId!) && x.Child1DnaId != null); + // } else if (role == "NORMAL") { rawData = rawData @@ -1692,10 +1692,10 @@ namespace BMA.EHR.Application.Repositories.Leaves.LeaveRequests { data = data.Where(x => x.RootDnaId == Guid.Parse(nodeId)).ToList(); } - else if (role == "PARENT") - { - data = data.Where(x => x.RootDnaId == Guid.Parse(nodeId) && x.Child1DnaId != null).ToList(); - } + // else if (role == "PARENT") + // { + // data = data.Where(x => x.RootDnaId == Guid.Parse(nodeId) && x.Child1DnaId != null).ToList(); + // } else if (role == "NORMAL") { data = data.Where(x => diff --git a/BMA.EHR.Application/Repositories/Leaves/TimeAttendants/AdditionalCheckRequestRepository.cs b/BMA.EHR.Application/Repositories/Leaves/TimeAttendants/AdditionalCheckRequestRepository.cs index 3dca61e1..0544e261 100644 --- a/BMA.EHR.Application/Repositories/Leaves/TimeAttendants/AdditionalCheckRequestRepository.cs +++ b/BMA.EHR.Application/Repositories/Leaves/TimeAttendants/AdditionalCheckRequestRepository.cs @@ -185,11 +185,11 @@ namespace BMA.EHR.Application.Repositories.Leaves.TimeAttendants data = data .Where(x => x.RootDnaId == Guid.Parse(nodeId!)).ToList(); } - else if (role == "PARENT") - { - data = data - .Where(x => x.RootDnaId == Guid.Parse(nodeId!) && x.Child1DnaId != null && x.Child1DnaId != Guid.Empty).ToList(); - } + // else if (role == "PARENT") + // { + // data = data + // .Where(x => x.RootDnaId == Guid.Parse(nodeId!) && x.Child1DnaId != null && x.Child1DnaId != Guid.Empty).ToList(); + // } else if (role == "NORMAL") { data = data.Where(x => diff --git a/BMA.EHR.Application/Repositories/Leaves/TimeAttendants/ProcessUserTimeStampRepository.cs b/BMA.EHR.Application/Repositories/Leaves/TimeAttendants/ProcessUserTimeStampRepository.cs index 64d32a19..c1df8798 100644 --- a/BMA.EHR.Application/Repositories/Leaves/TimeAttendants/ProcessUserTimeStampRepository.cs +++ b/BMA.EHR.Application/Repositories/Leaves/TimeAttendants/ProcessUserTimeStampRepository.cs @@ -172,10 +172,10 @@ namespace BMA.EHR.Application.Repositories.Leaves.TimeAttendants { data = data.Where(x => x.RootDnaId == Guid.Parse(nodeId)).ToList(); } - else if (role == "PARENT") - { - data = data.Where(x => x.RootDnaId == Guid.Parse(nodeId) && x.Child1DnaId != null).ToList(); - } + // else if (role == "PARENT") + // { + // data = data.Where(x => x.RootDnaId == Guid.Parse(nodeId) && x.Child1DnaId != null).ToList(); + // } else if (role == "NORMAL") { data = data.Where(x => @@ -288,12 +288,12 @@ namespace BMA.EHR.Application.Repositories.Leaves.TimeAttendants .Where(x => x.RootDnaId == Guid.Parse(nodeId!)) .ToList(); } - else if (role == "PARENT") - { - data = data - .Where(x => x.RootDnaId == Guid.Parse(nodeId!) && x.Child1DnaId != null) - .ToList(); - } + // else if (role == "PARENT") + // { + // data = data + // .Where(x => x.RootDnaId == Guid.Parse(nodeId!) && x.Child1DnaId != null) + // .ToList(); + // } else if (role == "NORMAL") { data = data.Where(x => diff --git a/BMA.EHR.Application/Repositories/Leaves/TimeAttendants/UserTimeStampRepository.cs b/BMA.EHR.Application/Repositories/Leaves/TimeAttendants/UserTimeStampRepository.cs index 2598dace..6dffcb6c 100644 --- a/BMA.EHR.Application/Repositories/Leaves/TimeAttendants/UserTimeStampRepository.cs +++ b/BMA.EHR.Application/Repositories/Leaves/TimeAttendants/UserTimeStampRepository.cs @@ -140,12 +140,12 @@ namespace BMA.EHR.Application.Repositories.Leaves.TimeAttendants .Where(x => x.RootDnaId == Guid.Parse(nodeId!)) .ToList(); } - else if (role == "PARENT") - { - data = data - .Where(x => x.RootDnaId == Guid.Parse(nodeId!) && x.Child1DnaId != null) - .ToList(); - } + // else if (role == "PARENT") + // { + // data = data + // .Where(x => x.RootDnaId == Guid.Parse(nodeId!) && x.Child1DnaId != null) + // .ToList(); + // } else if (role == "NORMAL") { data = data.Where(x => diff --git a/BMA.EHR.Discipline.Service/Controllers/DisciplineComplaint_AppealController.cs b/BMA.EHR.Discipline.Service/Controllers/DisciplineComplaint_AppealController.cs index 21204d6b..619756c2 100644 --- a/BMA.EHR.Discipline.Service/Controllers/DisciplineComplaint_AppealController.cs +++ b/BMA.EHR.Discipline.Service/Controllers/DisciplineComplaint_AppealController.cs @@ -786,7 +786,7 @@ namespace BMA.EHR.DisciplineComplaint_Appeal.Service.Controllers ? profileAdmin?.RootDnaId : ""; } - else if (role == "ROOT" || role == "PARENT") + else if (role == "ROOT" /*|| role == "PARENT"*/) { nodeId = profileAdmin?.RootDnaId; } @@ -826,11 +826,11 @@ namespace BMA.EHR.DisciplineComplaint_Appeal.Service.Controllers data_search = data_search .Where(x => x.rootDnaId == nodeId).ToList(); } - else if (role == "PARENT") - { - data_search = data_search - .Where(x => x.rootDnaId == nodeId && x.child1DnaId != null).ToList(); - } + // else if (role == "PARENT") + // { + // data_search = data_search + // .Where(x => x.rootDnaId == nodeId && x.child1DnaId != null).ToList(); + // } else if (role == "NORMAL") { data_search = data_search.Where(x => diff --git a/BMA.EHR.Discipline.Service/Controllers/DisciplineSuspendController.cs b/BMA.EHR.Discipline.Service/Controllers/DisciplineSuspendController.cs index 732c518b..39b5f06a 100644 --- a/BMA.EHR.Discipline.Service/Controllers/DisciplineSuspendController.cs +++ b/BMA.EHR.Discipline.Service/Controllers/DisciplineSuspendController.cs @@ -102,7 +102,7 @@ namespace BMA.EHR.DisciplineSuspend.Service.Controllers ? profileAdmin?.RootDnaId : ""; } - else if (role == "ROOT" || role == "PARENT") + else if (role == "ROOT" /*|| role == "PARENT"*/) { nodeId = profileAdmin?.RootDnaId; } @@ -143,8 +143,8 @@ namespace BMA.EHR.DisciplineSuspend.Service.Controllers ? true : role == "ROOT" ? x.rootDnaId == nodeId - : role == "PARENT" - ? x.rootDnaId == nodeId && x.child1DnaId != null + // : role == "PARENT" + // ? x.rootDnaId == nodeId && x.child1DnaId != null : role == "CHILD" ? ( profileAdmin.Node == 4 ? x.child4DnaId == nodeId : diff --git a/BMA.EHR.Insignia/Controllers/InsigniaManageController.cs b/BMA.EHR.Insignia/Controllers/InsigniaManageController.cs index 80a8ef31..7d3c9ae4 100644 --- a/BMA.EHR.Insignia/Controllers/InsigniaManageController.cs +++ b/BMA.EHR.Insignia/Controllers/InsigniaManageController.cs @@ -639,7 +639,7 @@ namespace BMA.EHR.Insignia.Service.Controllers ? profileAdmin?.RootDnaId : ""; } - else if (role == "ROOT" || role == "PARENT") + else if (role == "ROOT" /*|| role == "PARENT"*/) { nodeId = profileAdmin?.RootDnaId; } @@ -724,11 +724,11 @@ namespace BMA.EHR.Insignia.Service.Controllers rawData = rawData .Where(x => x.RootDnaId == Guid.Parse(nodeId!)).ToList(); } - else if (role == "PARENT") - { - rawData = rawData - .Where(x => x.RootDnaId == Guid.Parse(nodeId!) && x.Child1DnaId != null).ToList(); - } + // else if (role == "PARENT") + // { + // rawData = rawData + // .Where(x => x.RootDnaId == Guid.Parse(nodeId!) && x.Child1DnaId != null).ToList(); + // } else if (role == "NORMAL") { rawData = rawData.Where(x => @@ -943,7 +943,7 @@ namespace BMA.EHR.Insignia.Service.Controllers ? profileAdmin?.RootDnaId : ""; } - else if (role == "ROOT" || role == "PARENT") + else if (role == "ROOT" /*|| role == "PARENT"*/) { nodeId = profileAdmin?.RootDnaId; } @@ -1026,11 +1026,11 @@ namespace BMA.EHR.Insignia.Service.Controllers rawData = rawData .Where(x => x.RootDnaId == Guid.Parse(nodeId!)).ToList(); } - else if (role == "PARENT") - { - rawData = rawData - .Where(x => x.RootDnaId == Guid.Parse(nodeId!) && x.Child1DnaId != null).ToList(); - } + // else if (role == "PARENT") + // { + // rawData = rawData + // .Where(x => x.RootDnaId == Guid.Parse(nodeId!) && x.Child1DnaId != null).ToList(); + // } else if (role == "NORMAL") { rawData = rawData.Where(x => diff --git a/BMA.EHR.Leave/Controllers/LeaveBeginningController.cs b/BMA.EHR.Leave/Controllers/LeaveBeginningController.cs index 559a784d..f3b661cf 100644 --- a/BMA.EHR.Leave/Controllers/LeaveBeginningController.cs +++ b/BMA.EHR.Leave/Controllers/LeaveBeginningController.cs @@ -141,7 +141,7 @@ namespace BMA.EHR.Leave.Service.Controllers ? profileAdmin?.RootDnaId : ""; } - else if (role == "ROOT" || role == "PARENT") + else if (role == "ROOT" /*|| role == "PARENT"*/) { nodeId = profileAdmin?.RootDnaId; } @@ -166,11 +166,11 @@ namespace BMA.EHR.Leave.Service.Controllers resData = resData .Where(x => x.RootDnaId == Guid.Parse(nodeId!)).ToList(); } - else if (role == "PARENT") - { - resData = resData - .Where(x => x.RootDnaId == Guid.Parse(nodeId!) && x.Child1DnaId != null).ToList(); - } + // else if (role == "PARENT") + // { + // resData = resData + // .Where(x => x.RootDnaId == Guid.Parse(nodeId!) && x.Child1DnaId != null).ToList(); + // } else if (role == "NORMAL") { resData = resData diff --git a/BMA.EHR.Leave/Controllers/LeaveController.cs b/BMA.EHR.Leave/Controllers/LeaveController.cs index 08dcb575..267abee9 100644 --- a/BMA.EHR.Leave/Controllers/LeaveController.cs +++ b/BMA.EHR.Leave/Controllers/LeaveController.cs @@ -1842,7 +1842,7 @@ namespace BMA.EHR.Leave.Service.Controllers ? profileAdmin?.RootDnaId : ""; } - else if (role == "ROOT" || role == "PARENT") + else if (role == "ROOT" /*|| role == "PARENT"*/) { nodeId = profileAdmin?.RootDnaId; } @@ -2151,7 +2151,7 @@ namespace BMA.EHR.Leave.Service.Controllers ? profileAdmin?.RootDnaId : ""; } - else if (role == "ROOT" || role == "PARENT") + else if (role == "ROOT" /*|| role == "PARENT"*/) { nodeId = profileAdmin?.RootDnaId; } @@ -2397,7 +2397,7 @@ namespace BMA.EHR.Leave.Service.Controllers ? profileAdmin?.RootDnaId : ""; } - else if (role == "ROOT" || role == "PARENT") + else if (role == "ROOT" /*|| role == "PARENT"*/) { nodeId = profileAdmin?.RootDnaId; } @@ -2652,7 +2652,7 @@ namespace BMA.EHR.Leave.Service.Controllers ? profileAdmin?.RootDnaId : ""; } - else if (role == "ROOT" || role == "PARENT") + else if (role == "ROOT" /*|| role == "PARENT"*/) { nodeId = profileAdmin?.RootDnaId; } @@ -3077,7 +3077,7 @@ namespace BMA.EHR.Leave.Service.Controllers ? profileAdmin?.RootDnaId : ""; } - else if (role == "ROOT" || role == "PARENT") + else if (role == "ROOT" /*|| role == "PARENT"*/) { nodeId = profileAdmin?.RootDnaId; } diff --git a/BMA.EHR.Leave/Controllers/LeaveReportController.cs b/BMA.EHR.Leave/Controllers/LeaveReportController.cs index 2b555e73..e96fc0a0 100644 --- a/BMA.EHR.Leave/Controllers/LeaveReportController.cs +++ b/BMA.EHR.Leave/Controllers/LeaveReportController.cs @@ -1340,7 +1340,7 @@ namespace BMA.EHR.Leave.Service.Controllers ? profileAdmin?.RootDnaId : ""; } - else if (role == "ROOT" || role == "PARENT") + else if (role == "ROOT" /*|| role == "PARENT"*/) { nodeId = profileAdmin?.RootDnaId; } @@ -1970,7 +1970,7 @@ namespace BMA.EHR.Leave.Service.Controllers ? profileAdmin?.RootDnaId : ""; } - else if (role == "ROOT" || role == "PARENT") + else if (role == "ROOT" /*|| role == "PARENT"*/) { nodeId = profileAdmin?.RootDnaId; } @@ -2312,7 +2312,7 @@ namespace BMA.EHR.Leave.Service.Controllers ? profileAdmin?.RootDnaId : ""; } - else if (role == "ROOT" || role == "PARENT") + else if (role == "ROOT" /*|| role == "PARENT"*/) { nodeId = profileAdmin?.RootDnaId; } @@ -2757,7 +2757,7 @@ namespace BMA.EHR.Leave.Service.Controllers ? profileAdmin?.RootDnaId : ""; } - else if (role == "ROOT" || role == "PARENT") + else if (role == "ROOT" /*|| role == "PARENT"*/) { nodeId = profileAdmin?.RootDnaId; } @@ -2878,7 +2878,7 @@ namespace BMA.EHR.Leave.Service.Controllers ? profileAdmin?.RootDnaId : ""; } - else if (role == "ROOT" || role == "PARENT") + else if (role == "ROOT" /*|| role == "PARENT"*/) { nodeId = profileAdmin?.RootDnaId; } diff --git a/BMA.EHR.Leave/Controllers/LeaveRequestController.cs b/BMA.EHR.Leave/Controllers/LeaveRequestController.cs index 6d16e8de..9af0df96 100644 --- a/BMA.EHR.Leave/Controllers/LeaveRequestController.cs +++ b/BMA.EHR.Leave/Controllers/LeaveRequestController.cs @@ -1359,7 +1359,7 @@ namespace BMA.EHR.Leave.Service.Controllers ? profileAdmin?.RootDnaId : ""; } - else if (role == "ROOT" || role == "PARENT") + else if (role == "ROOT" /*|| role == "PARENT"*/) { nodeId = profileAdmin?.RootDnaId; } @@ -1757,7 +1757,7 @@ namespace BMA.EHR.Leave.Service.Controllers ? profileAdmin?.RootDnaId : ""; } - else if (role == "ROOT" || role == "PARENT") + else if (role == "ROOT" /*|| role == "PARENT"*/) { nodeId = profileAdmin?.RootDnaId; } @@ -1955,7 +1955,7 @@ namespace BMA.EHR.Leave.Service.Controllers ? profileAdmin?.RootDnaId : ""; } - else if (role == "ROOT" || role == "PARENT") + else if (role == "ROOT" /*|| role == "PARENT"*/) { nodeId = profileAdmin?.RootDnaId; } diff --git a/BMA.EHR.Placement.Service/Controllers/PlacementAppointmentController.cs b/BMA.EHR.Placement.Service/Controllers/PlacementAppointmentController.cs index a98abb74..ff010a2a 100644 --- a/BMA.EHR.Placement.Service/Controllers/PlacementAppointmentController.cs +++ b/BMA.EHR.Placement.Service/Controllers/PlacementAppointmentController.cs @@ -112,7 +112,7 @@ namespace BMA.EHR.Placement.Service.Controllers ? profileAdmin?.RootDnaId : ""; } - else if (role == "ROOT" || role == "PARENT") + else if (role == "ROOT" /*|| role == "PARENT"*/) { nodeId = profileAdmin?.RootDnaId; } @@ -239,11 +239,11 @@ namespace BMA.EHR.Placement.Service.Controllers placementAppointments = placementAppointments .Where(x => x.rootDnaId == nodeId).ToList(); } - else if (role == "PARENT") - { - placementAppointments = placementAppointments - .Where(x => x.rootDnaId == nodeId && x.child1DnaId != null).ToList(); - } + // else if (role == "PARENT") + // { + // placementAppointments = placementAppointments + // .Where(x => x.rootDnaId == nodeId && x.child1DnaId != null).ToList(); + // } else if (role == "NORMAL") { placementAppointments = placementAppointments.Where(x => diff --git a/BMA.EHR.Placement.Service/Controllers/PlacementAppointmentEmployeeController.cs b/BMA.EHR.Placement.Service/Controllers/PlacementAppointmentEmployeeController.cs index ae8e40ac..971cd526 100644 --- a/BMA.EHR.Placement.Service/Controllers/PlacementAppointmentEmployeeController.cs +++ b/BMA.EHR.Placement.Service/Controllers/PlacementAppointmentEmployeeController.cs @@ -110,7 +110,7 @@ namespace BMA.EHR.Placement.Service.Controllers ? profileAdmin?.RootDnaId : ""; } - else if (role == "ROOT" || role == "PARENT") + else if (role == "ROOT" /*|| role == "PARENT"*/) { nodeId = profileAdmin?.RootDnaId; } @@ -232,11 +232,11 @@ namespace BMA.EHR.Placement.Service.Controllers placementAppointments = placementAppointments .Where(x => x.rootDnaId == nodeId).ToList(); } - else if (role == "PARENT") - { - placementAppointments = placementAppointments - .Where(x => x.rootDnaId == nodeId && x.child1DnaId != null).ToList(); - } + // else if (role == "PARENT") + // { + // placementAppointments = placementAppointments + // .Where(x => x.rootDnaId == nodeId && x.child1DnaId != null).ToList(); + // } else if (role == "NORMAL") { placementAppointments = placementAppointments.Where(x => diff --git a/BMA.EHR.Placement.Service/Controllers/PlacementOfficerController.cs b/BMA.EHR.Placement.Service/Controllers/PlacementOfficerController.cs index 19cc4e2d..c2033280 100644 --- a/BMA.EHR.Placement.Service/Controllers/PlacementOfficerController.cs +++ b/BMA.EHR.Placement.Service/Controllers/PlacementOfficerController.cs @@ -111,7 +111,7 @@ namespace BMA.EHR.Placement.Service.Controllers ? profileAdmin?.RootDnaId : ""; } - else if (role == "ROOT" || role == "PARENT") + else if (role == "ROOT" /*|| role == "PARENT"*/) { nodeId = profileAdmin?.RootDnaId; } @@ -193,11 +193,11 @@ namespace BMA.EHR.Placement.Service.Controllers placementOfficers = placementOfficers .Where(x => x.rootDnaOldId == nodeId).ToList(); } - else if (role == "PARENT") - { - placementOfficers = placementOfficers - .Where(x => x.rootDnaOldId == nodeId && x.child1DnaOldId != null).ToList(); - } + // else if (role == "PARENT") + // { + // placementOfficers = placementOfficers + // .Where(x => x.rootDnaOldId == nodeId && x.child1DnaOldId != null).ToList(); + // } else if (role == "NORMAL") { placementOfficers = placementOfficers.Where(x => diff --git a/BMA.EHR.Placement.Service/Controllers/PlacementReceiveController.cs b/BMA.EHR.Placement.Service/Controllers/PlacementReceiveController.cs index db8a28a7..ab8a508e 100644 --- a/BMA.EHR.Placement.Service/Controllers/PlacementReceiveController.cs +++ b/BMA.EHR.Placement.Service/Controllers/PlacementReceiveController.cs @@ -112,7 +112,7 @@ namespace BMA.EHR.Placement.Service.Controllers ? profileAdmin?.RootDnaId : ""; } - else if (role == "ROOT" || role == "PARENT") + else if (role == "ROOT" /*|| role == "PARENT"*/) { nodeId = profileAdmin?.RootDnaId; } @@ -231,11 +231,11 @@ namespace BMA.EHR.Placement.Service.Controllers placementReceives = placementReceives .Where(x => (x.rootDnaId == nodeId) || (x.CreatedUserId == UserId)).ToList(); } - else if (role == "PARENT") - { - placementReceives = placementReceives - .Where(x => x.rootDnaId == nodeId && x.child1DnaId != null).ToList(); - } + // else if (role == "PARENT") + // { + // placementReceives = placementReceives + // .Where(x => x.rootDnaId == nodeId && x.child1DnaId != null).ToList(); + // } else if (role == "NORMAL") { placementReceives = placementReceives.Where(x => diff --git a/BMA.EHR.Placement.Service/Controllers/PlacementRepatriationController.cs b/BMA.EHR.Placement.Service/Controllers/PlacementRepatriationController.cs index 45798f6c..5f5668b7 100644 --- a/BMA.EHR.Placement.Service/Controllers/PlacementRepatriationController.cs +++ b/BMA.EHR.Placement.Service/Controllers/PlacementRepatriationController.cs @@ -112,7 +112,7 @@ namespace BMA.EHR.Placement.Service.Controllers ? profileAdmin?.RootDnaId : ""; } - else if (role == "ROOT" || role == "PARENT") + else if (role == "ROOT" /*|| role == "PARENT"*/) { nodeId = profileAdmin?.RootDnaId; } @@ -198,11 +198,11 @@ namespace BMA.EHR.Placement.Service.Controllers placementRepatriations = placementRepatriations .Where(x => x.rootDnaOldId == nodeId).ToList(); } - else if (role == "PARENT") - { - placementRepatriations = placementRepatriations - .Where(x => x.rootDnaOldId == nodeId && x.child1DnaOldId != null).ToList(); - } + // else if (role == "PARENT") + // { + // placementRepatriations = placementRepatriations + // .Where(x => x.rootDnaOldId == nodeId && x.child1DnaOldId != null).ToList(); + // } else if (role == "NORMAL") { placementRepatriations = placementRepatriations.Where(x => diff --git a/BMA.EHR.Placement.Service/Controllers/PlacementTransferController.cs b/BMA.EHR.Placement.Service/Controllers/PlacementTransferController.cs index 68dd42a9..3514e4a5 100644 --- a/BMA.EHR.Placement.Service/Controllers/PlacementTransferController.cs +++ b/BMA.EHR.Placement.Service/Controllers/PlacementTransferController.cs @@ -205,7 +205,7 @@ namespace BMA.EHR.Placement.Service.Controllers ? profileAdmin?.RootDnaId : ""; } - else if (role == "ROOT" || role == "PARENT") + else if (role == "ROOT" /*|| role == "PARENT"*/) { nodeId = profileAdmin?.RootDnaId; } @@ -285,11 +285,11 @@ namespace BMA.EHR.Placement.Service.Controllers placementTransfers = placementTransfers .Where(x => x.rootDnaOldId == nodeId).ToList(); } - else if (role == "PARENT") - { - placementTransfers = placementTransfers - .Where(x => x.rootDnaOldId == nodeId && x.child1DnaOldId != null).ToList(); - } + // else if (role == "PARENT") + // { + // placementTransfers = placementTransfers + // .Where(x => x.rootDnaOldId == nodeId && x.child1DnaOldId != null).ToList(); + // } else if (role == "NORMAL") { placementTransfers = placementTransfers.Where(x => diff --git a/BMA.EHR.Retirement.Service/Controllers/RetirementDeceasedController.cs b/BMA.EHR.Retirement.Service/Controllers/RetirementDeceasedController.cs index 33808435..cf9f153d 100644 --- a/BMA.EHR.Retirement.Service/Controllers/RetirementDeceasedController.cs +++ b/BMA.EHR.Retirement.Service/Controllers/RetirementDeceasedController.cs @@ -116,7 +116,7 @@ namespace BMA.EHR.Retirement.Service.Controllers ? profileAdmin?.RootDnaId : ""; } - else if (role == "ROOT" || role == "PARENT") + else if (role == "ROOT" /*|| role == "PARENT"*/) { nodeId = profileAdmin?.RootDnaId; } @@ -177,11 +177,11 @@ namespace BMA.EHR.Retirement.Service.Controllers retirementDeceaseds = retirementDeceaseds .Where(x => x.rootDnaId == nodeId).ToList(); } - else if (role == "PARENT") - { - retirementDeceaseds = retirementDeceaseds - .Where(x => x.rootDnaId == nodeId && x.child1DnaId != null).ToList(); - } + // else if (role == "PARENT") + // { + // retirementDeceaseds = retirementDeceaseds + // .Where(x => x.rootDnaId == nodeId && x.child1DnaId != null).ToList(); + // } else if (role == "NORMAL") { retirementDeceaseds = retirementDeceaseds.Where(x => diff --git a/BMA.EHR.Retirement.Service/Controllers/RetirementOtherController.cs b/BMA.EHR.Retirement.Service/Controllers/RetirementOtherController.cs index 4db9f778..04a01f77 100644 --- a/BMA.EHR.Retirement.Service/Controllers/RetirementOtherController.cs +++ b/BMA.EHR.Retirement.Service/Controllers/RetirementOtherController.cs @@ -112,7 +112,7 @@ namespace BMA.EHR.Retirement.Service.Controllers ? profileAdmin?.RootDnaId : ""; } - else if (role == "ROOT" || role == "PARENT") + else if (role == "ROOT" /*|| role == "PARENT"*/) { nodeId = profileAdmin?.RootDnaId; } @@ -227,11 +227,11 @@ namespace BMA.EHR.Retirement.Service.Controllers retirementOthers = retirementOthers .Where(x => x.rootDnaOldId == nodeId).ToList(); } - else if (role == "PARENT") - { - retirementOthers = retirementOthers - .Where(x => x.rootDnaOldId == nodeId && x.child1DnaOldId != null).ToList(); - } + // else if (role == "PARENT") + // { + // retirementOthers = retirementOthers + // .Where(x => x.rootDnaOldId == nodeId && x.child1DnaOldId != null).ToList(); + // } else if (role == "NORMAL") { retirementOthers = retirementOthers.Where(x => diff --git a/BMA.EHR.Retirement.Service/Controllers/RetirementOutController.cs b/BMA.EHR.Retirement.Service/Controllers/RetirementOutController.cs index 72a7bc91..ce8b0452 100644 --- a/BMA.EHR.Retirement.Service/Controllers/RetirementOutController.cs +++ b/BMA.EHR.Retirement.Service/Controllers/RetirementOutController.cs @@ -127,7 +127,7 @@ namespace BMA.EHR.Retirement.Service.Controllers ? profileAdmin?.RootDnaId : ""; } - else if (role == "ROOT" || role == "PARENT") + else if (role == "ROOT" /*|| role == "PARENT"*/) { nodeId = profileAdmin?.RootDnaId; } @@ -208,11 +208,11 @@ namespace BMA.EHR.Retirement.Service.Controllers retirementOuts = retirementOuts .Where(x => x.rootDnaOldId == nodeId).ToList(); } - else if (role == "PARENT") - { - retirementOuts = retirementOuts - .Where(x => x.rootDnaOldId == nodeId && x.child1DnaOldId != null).ToList(); - } + // else if (role == "PARENT") + // { + // retirementOuts = retirementOuts + // .Where(x => x.rootDnaOldId == nodeId && x.child1DnaOldId != null).ToList(); + // } else if (role == "NORMAL") { retirementOuts = retirementOuts.Where(x => diff --git a/BMA.EHR.Retirement.Service/Controllers/RetirementResignController.cs b/BMA.EHR.Retirement.Service/Controllers/RetirementResignController.cs index 7b33e0fb..a70e357d 100644 --- a/BMA.EHR.Retirement.Service/Controllers/RetirementResignController.cs +++ b/BMA.EHR.Retirement.Service/Controllers/RetirementResignController.cs @@ -271,7 +271,7 @@ namespace BMA.EHR.Retirement.Service.Controllers ? profileAdmin?.RootDnaId : ""; } - else if (role == "ROOT" || role == "PARENT") + else if (role == "ROOT" /*|| role == "PARENT"*/) { nodeId = profileAdmin?.RootDnaId; } @@ -336,11 +336,11 @@ namespace BMA.EHR.Retirement.Service.Controllers retirementResigns = retirementResigns .Where(x => x.rootDnaOldId == nodeId).ToList(); } - else if (role == "PARENT") - { - retirementResigns = retirementResigns - .Where(x => x.rootDnaOldId == nodeId && x.child1DnaOldId != null).ToList(); - } + // else if (role == "PARENT") + // { + // retirementResigns = retirementResigns + // .Where(x => x.rootDnaOldId == nodeId && x.child1DnaOldId != null).ToList(); + // } else if (role == "NORMAL") { retirementResigns = retirementResigns.Where(x => @@ -403,7 +403,7 @@ namespace BMA.EHR.Retirement.Service.Controllers ? profileAdmin?.RootDnaId : ""; } - else if (role == "ROOT" || role == "PARENT") + else if (role == "ROOT" /*|| role == "PARENT"*/) { nodeId = profileAdmin?.RootDnaId; } @@ -467,11 +467,11 @@ namespace BMA.EHR.Retirement.Service.Controllers retirementResigns = retirementResigns .Where(x => x.rootDnaOldId == nodeId).ToList(); } - else if (role == "PARENT") - { - retirementResigns = retirementResigns - .Where(x => x.rootDnaOldId == nodeId && x.child1DnaOldId != null).ToList(); - } + // else if (role == "PARENT") + // { + // retirementResigns = retirementResigns + // .Where(x => x.rootDnaOldId == nodeId && x.child1DnaOldId != null).ToList(); + // } else if (role == "NORMAL") { retirementResigns = retirementResigns.Where(x => @@ -2170,7 +2170,7 @@ namespace BMA.EHR.Retirement.Service.Controllers ? profileAdmin?.RootDnaId : ""; } - else if (role == "ROOT" || role == "PARENT") + else if (role == "ROOT" /*|| role == "PARENT"*/) { nodeId = profileAdmin?.RootDnaId; } @@ -2228,11 +2228,11 @@ namespace BMA.EHR.Retirement.Service.Controllers data = data .Where(x => x.rootDnaId == nodeId).ToList(); } - else if (role == "PARENT") - { - data = data - .Where(x => x.rootDnaId == nodeId && x.child1DnaId != null).ToList(); - } + // else if (role == "PARENT") + // { + // data = data + // .Where(x => x.rootDnaId == nodeId && x.child1DnaId != null).ToList(); + // } else if (role == "NORMAL") { data = data.Where(x => diff --git a/BMA.EHR.Retirement.Service/Controllers/RetirementResignEmployeeController.cs b/BMA.EHR.Retirement.Service/Controllers/RetirementResignEmployeeController.cs index 8bc6c975..e42e2e6a 100644 --- a/BMA.EHR.Retirement.Service/Controllers/RetirementResignEmployeeController.cs +++ b/BMA.EHR.Retirement.Service/Controllers/RetirementResignEmployeeController.cs @@ -208,7 +208,7 @@ namespace BMA.EHR.Retirement.Service.Controllers ? profileAdmin?.RootDnaId : ""; } - else if (role == "ROOT" || role == "PARENT") + else if (role == "ROOT" /*|| role == "PARENT"*/) { nodeId = profileAdmin?.RootDnaId; } @@ -273,11 +273,11 @@ namespace BMA.EHR.Retirement.Service.Controllers retirementResignEmployees = retirementResignEmployees .Where(x => x.rootDnaOldId == nodeId).ToList(); } - else if (role == "PARENT") - { - retirementResignEmployees = retirementResignEmployees - .Where(x => x.rootDnaOldId == nodeId && x.child1DnaOldId != null).ToList(); - } + // else if (role == "PARENT") + // { + // retirementResignEmployees = retirementResignEmployees + // .Where(x => x.rootDnaOldId == nodeId && x.child1DnaOldId != null).ToList(); + // } else if (role == "NORMAL") { retirementResignEmployees = retirementResignEmployees.Where(x => @@ -340,7 +340,7 @@ namespace BMA.EHR.Retirement.Service.Controllers ? profileAdmin?.RootDnaId : ""; } - else if (role == "ROOT" || role == "PARENT") + else if (role == "ROOT" /*|| role == "PARENT"*/) { nodeId = profileAdmin?.RootDnaId; } @@ -403,11 +403,11 @@ namespace BMA.EHR.Retirement.Service.Controllers retirementResignEmployees = retirementResignEmployees .Where(x => x.rootDnaOldId == nodeId).ToList(); } - else if (role == "PARENT") - { - retirementResignEmployees = retirementResignEmployees - .Where(x => x.rootDnaOldId == nodeId && x.child1DnaOldId != null).ToList(); - } + // else if (role == "PARENT") + // { + // retirementResignEmployees = retirementResignEmployees + // .Where(x => x.rootDnaOldId == nodeId && x.child1DnaOldId != null).ToList(); + // } else if (role == "NORMAL") { retirementResignEmployees = retirementResignEmployees.Where(x => @@ -2078,7 +2078,7 @@ namespace BMA.EHR.Retirement.Service.Controllers ? profileAdmin?.RootDnaId : ""; } - else if (role == "ROOT" || role == "PARENT") + else if (role == "ROOT" /*|| role == "PARENT"*/) { nodeId = profileAdmin?.RootDnaId; } @@ -2136,11 +2136,11 @@ namespace BMA.EHR.Retirement.Service.Controllers data = data .Where(x => x.rootDnaId == nodeId).ToList(); } - else if (role == "PARENT") - { - data = data - .Where(x => x.rootDnaId == nodeId && x.child1DnaId != null).ToList(); - } + // else if (role == "PARENT") + // { + // data = data + // .Where(x => x.rootDnaId == nodeId && x.child1DnaId != null).ToList(); + // } else if (role == "NORMAL") { data = data.Where(x => From 7eade164e9aae4a66a09aee74c4a3dd0f56e23ee Mon Sep 17 00:00:00 2001 From: Suphonchai Phoonsawat Date: Thu, 19 Feb 2026 17:33:37 +0700 Subject: [PATCH 116/183] Update LeaveRequestController to use GetLastLeaveRequestByTypeForUserAsync2 method with CreatedAt for fetching last leave request --- BMA.EHR.Leave/Controllers/LeaveRequestController.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/BMA.EHR.Leave/Controllers/LeaveRequestController.cs b/BMA.EHR.Leave/Controllers/LeaveRequestController.cs index 6d16e8de..cf4a73b3 100644 --- a/BMA.EHR.Leave/Controllers/LeaveRequestController.cs +++ b/BMA.EHR.Leave/Controllers/LeaveRequestController.cs @@ -2497,8 +2497,8 @@ namespace BMA.EHR.Leave.Service.Controllers //var lastSalaryAmount = lastSalary == null ? 0 : lastSalary.Amount ?? 0; var lastLeaveRequest = - await _leaveRequestRepository.GetLastLeaveRequestByTypeForUserAsync(rawData.KeycloakUserId, - rawData.Type.Id, rawData.LeaveStartDate.Date); + await _leaveRequestRepository.GetLastLeaveRequestByTypeForUserAsync2(rawData.KeycloakUserId, + rawData.Type.Id, rawData.CreatedAt); //var rootOc = _userProfileRepository.GetRootOcId(profile.OcId ?? Guid.Empty, AccessToken); //var approver = string.Empty; From ecca345407bb2b54e7520ceebf594a34a0eb047a Mon Sep 17 00:00:00 2001 From: harid Date: Fri, 20 Feb 2026 10:46:15 +0700 Subject: [PATCH 117/183] =?UTF-8?q?=E0=B8=A3=E0=B8=B2=E0=B8=A2=E0=B8=8A?= =?UTF-8?q?=E0=B8=B7=E0=B9=88=E0=B8=AD=E0=B8=9C=E0=B8=B9=E0=B9=89=E0=B8=AA?= =?UTF-8?q?=E0=B8=AD=E0=B8=9A=E0=B8=9C=E0=B9=88=E0=B8=B2=E0=B8=99=20?= =?UTF-8?q?=E0=B8=81=E0=B8=A3=E0=B8=93=E0=B8=B5=20OWNER=20=E0=B9=83?= =?UTF-8?q?=E0=B8=AB=E0=B9=89=E0=B9=80=E0=B8=AB=E0=B9=87=E0=B8=99=E0=B8=A3?= =?UTF-8?q?=E0=B8=B2=E0=B8=A2=E0=B8=8A=E0=B8=B7=E0=B9=88=E0=B8=AD=E0=B9=80?= =?UTF-8?q?=E0=B8=AB=E0=B8=A1=E0=B8=B7=E0=B8=AD=E0=B8=99=20=E0=B8=AA?= =?UTF-8?q?=E0=B8=81=E0=B8=88.=20#2319?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Controllers/PlacementController.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/BMA.EHR.Placement.Service/Controllers/PlacementController.cs b/BMA.EHR.Placement.Service/Controllers/PlacementController.cs index d3c28712..d55ae979 100644 --- a/BMA.EHR.Placement.Service/Controllers/PlacementController.cs +++ b/BMA.EHR.Placement.Service/Controllers/PlacementController.cs @@ -140,6 +140,7 @@ namespace BMA.EHR.Placement.Service.Controllers public async Task> GetExamByPlacement(Guid examId) { var getWorkflow = await _permission.GetPermissionAPIWorkflowAsync(examId.ToString(), "SYS_PLACEMENT_PASS"); + var role = string.Empty; if (getWorkflow == false) { var getPermission = await _permission.GetPermissionAPIAsync("GET", "SYS_PLACEMENT_PASS"); @@ -148,6 +149,7 @@ namespace BMA.EHR.Placement.Service.Controllers { return Error(jsonData["message"]?.ToString(), StatusCodes.Status403Forbidden); } + role = jsonData["result"]?.ToString(); } var rootId = ""; @@ -167,7 +169,7 @@ namespace BMA.EHR.Placement.Service.Controllers if (_res.IsSuccessStatusCode) { var org = JsonConvert.DeserializeObject(_result); - if (org.result.isOfficer == false) + if (org.result.isOfficer == false && role?.Trim().ToUpper() != "OWNER") { rootId = org.result.rootId == null ? "" : org.result.rootId; // child1Id = org.result.child1Id == null ? "" : org.result.child1Id; @@ -302,7 +304,7 @@ namespace BMA.EHR.Placement.Service.Controllers } return Success(result1); } - if (org.result.isOfficer == true) + if (org.result.isOfficer == true || role?.Trim().ToUpper() == "OWNER") { var data = await _context.PlacementProfiles.Where(x => x.Placement.Id == examId).Select(x => new { From ddb35f525aecfaaa861a1e01f890438dbfc80852 Mon Sep 17 00:00:00 2001 From: harid Date: Fri, 20 Feb 2026 13:36:50 +0700 Subject: [PATCH 118/183] Fix #2319 --- .../Controllers/PlacementController.cs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/BMA.EHR.Placement.Service/Controllers/PlacementController.cs b/BMA.EHR.Placement.Service/Controllers/PlacementController.cs index d55ae979..2a1e5399 100644 --- a/BMA.EHR.Placement.Service/Controllers/PlacementController.cs +++ b/BMA.EHR.Placement.Service/Controllers/PlacementController.cs @@ -692,6 +692,13 @@ namespace BMA.EHR.Placement.Service.Controllers public async Task> GetDashboardByPlacement(Guid examId) { + var role = string.Empty; + var getPermission = await _permission.GetPermissionAPIAsync("GET", "SYS_PLACEMENT_PASS"); + var jsonData = JsonConvert.DeserializeObject(getPermission); + if (jsonData["status"]?.ToString() == "200") + { + role = jsonData["result"]?.ToString(); + } var rootId = ""; var child1Id = ""; @@ -711,7 +718,7 @@ namespace BMA.EHR.Placement.Service.Controllers if (_res.IsSuccessStatusCode) { var org = JsonConvert.DeserializeObject(_result); - if (org.result.isOfficer == false) + if (org.result.isOfficer == false && role?.Trim().ToUpper() != "OWNER") { rootId = org.result.rootId == null ? "" : org.result.rootId; // child1Id = org.result.child1Id == null ? "" : org.result.child1Id; @@ -735,7 +742,7 @@ namespace BMA.EHR.Placement.Service.Controllers return Success(placement); } - if (org.result.isOfficer == true) + if (org.result.isOfficer == true || role?.Trim().ToUpper() == "OWNER") { var placement = await _context.Placements .Where(x => x.Id == examId) From 3e34aaa1785ea220cd95420c131bc063533fd95c Mon Sep 17 00:00:00 2001 From: Suphonchai Phoonsawat Date: Fri, 20 Feb 2026 16:32:57 +0700 Subject: [PATCH 119/183] Refactor file upload logic in LeaveController to handle check-in scenarios more effectively #2328 --- BMA.EHR.Leave/Controllers/LeaveController.cs | 46 +++++++++++++++++--- 1 file changed, 41 insertions(+), 5 deletions(-) diff --git a/BMA.EHR.Leave/Controllers/LeaveController.cs b/BMA.EHR.Leave/Controllers/LeaveController.cs index 267abee9..64d7a92e 100644 --- a/BMA.EHR.Leave/Controllers/LeaveController.cs +++ b/BMA.EHR.Leave/Controllers/LeaveController.cs @@ -970,11 +970,11 @@ namespace BMA.EHR.Leave.Service.Controllers try { await _minIOService.UploadFileAsync(fileName, ms); - if (lastCheckIn != null && lastCheckIn.CheckOut == null) - { - // ยังไม่เคย check-out มาก่อน หรือ check-out เป็น null ให้ใช้ชื่อไฟล์แบบ check-out - await _minIOService.UploadFileAsync(fileNameCheckOut, ms); - } + // if (lastCheckIn != null && lastCheckIn.CheckOut == null) + // { + // // ยังไม่เคย check-out มาก่อน หรือ check-out เป็น null ให้ใช้ชื่อไฟล์แบบ check-out + // await _minIOService.UploadFileAsync(fileNameCheckOut, ms); + // } } catch (Exception ex) { @@ -997,6 +997,42 @@ namespace BMA.EHR.Leave.Service.Controllers } + if (lastCheckIn != null && lastCheckIn.CheckOut == null) + { + using (var ms2 = new MemoryStream(data.CheckInFileBytes ?? new byte[0])) + { + 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}"); + + // send notification to user + var noti1 = new Notification + { + Body = $"ประมวลผลการลงเวลาวันที่ {currentDate.ToString("dd-MM-yyyy")} ไม่สำเร็จ \r\nเนื่องจากไม่สามารถอัปโหลดรูปภาพได้ {ex.Message}", + ReceiverUserId = profile.Id, + Type = "", + Payload = "", + }; + _appDbContext.Set().Add(noti1); + await _appDbContext.SaveChangesAsync(); + + + return Error($"ไม่สามารถอัปโหลดรูปภาพได้: {ex.Message}", StatusCodes.Status500InternalServerError); + } + + } + } + + var defaultRound = await _dutyTimeRepository.GetDefaultAsync(); if (defaultRound == null) { From c20e1b48bd7c03d06e7d91a99e34021e334d440c Mon Sep 17 00:00:00 2001 From: Suphonchai Phoonsawat Date: Mon, 23 Feb 2026 10:09:36 +0700 Subject: [PATCH 120/183] Add GetDifference method to DateTimeExtension and implement TimeCheck endpoint in LeaveRequestController --- .../Extensions/DateTimeExtension.cs | 23 ++++++ .../Controllers/LeaveRequestController.cs | 70 ++++++++++--------- 2 files changed, 60 insertions(+), 33 deletions(-) diff --git a/BMA.EHR.Domain/Extensions/DateTimeExtension.cs b/BMA.EHR.Domain/Extensions/DateTimeExtension.cs index a6a07c79..bbfc0a12 100644 --- a/BMA.EHR.Domain/Extensions/DateTimeExtension.cs +++ b/BMA.EHR.Domain/Extensions/DateTimeExtension.cs @@ -174,6 +174,29 @@ namespace BMA.EHR.Domain.Extensions } } + public static (int Years, int Months, int Days) GetDifference(this DateTime from, DateTime to) + { + if (from > to) (from, to) = (to, from); // swap ถ้าลำดับสลับ + + int years = to.Year - from.Year; + int months = to.Month - from.Month; + int days = to.Day - from.Day; + + if (days < 0) + { + months--; + days += DateTime.DaysInMonth(to.Year, to.Month == 1 ? 12 : to.Month - 1); + } + + if (months < 0) + { + years--; + months += 12; + } + + return (years, months, days); + } + public static int CalculateAge(this DateTime date, int plusYear = 0, int subtractYear = 0) { try diff --git a/BMA.EHR.Leave/Controllers/LeaveRequestController.cs b/BMA.EHR.Leave/Controllers/LeaveRequestController.cs index f82269db..0a69836a 100644 --- a/BMA.EHR.Leave/Controllers/LeaveRequestController.cs +++ b/BMA.EHR.Leave/Controllers/LeaveRequestController.cs @@ -966,6 +966,33 @@ namespace BMA.EHR.Leave.Service.Controllers return Success(result); } + [HttpGet("time-check")] + [AllowAnonymous] + public async Task> TimeCheckAsync() + { + var startDate = new DateTime(2017, 1, 6); + var govAge = (startDate).DiffDay(DateTime.Now.Date); + var date1Raw = startDate; + var date1 = new LocalDate(date1Raw.Year, date1Raw.Month, date1Raw.Day); + var date2 = new LocalDate(DateTime.Now.Year, DateTime.Now.Month, DateTime.Now.Day); + + + var (yy, mm, dd) = startDate.GetDifference(DateTime.Now.Date); + + + // Period period = Period.Between(date1, date2); + // var govAgeMonth = period.Months; + // var govAgeYear = period.Years; + + return Success(new + { + GovAge = govAge, + GovAgeDay = dd, + GovAgeMonth = mm, + GovAgeYear = yy + }); + } + /// /// LV2_003 - เช็คการยืนขอลา (USER) /// @@ -987,12 +1014,15 @@ namespace BMA.EHR.Leave.Service.Controllers // var profile = await _userProfileRepository.GetProfileByKeycloakIdAsync(userId, AccessToken); var profile = await _userProfileRepository.GetProfileByKeycloakIdNewAsync(userId, AccessToken); var govAge = (profile?.DateStart?.Date ?? DateTime.Now.Date).DiffDay(DateTime.Now.Date); - var date1Raw = profile?.DateStart?.Date ?? DateTime.Now.Date; - var date1 = new LocalDate(date1Raw.Year, date1Raw.Month, date1Raw.Day); - var date2 = new LocalDate(DateTime.Now.Year, DateTime.Now.Month, DateTime.Now.Day); - Period period = Period.Between(date1, date2); - var govAgeMonth = period.Months; - var govAgeYear = period.Years; + var startDate = profile?.DateStart?.Date ?? DateTime.Now.Date; + // var date1Raw = profile?.DateStart?.Date ?? DateTime.Now.Date; + // var date1 = new LocalDate(date1Raw.Year, date1Raw.Month, date1Raw.Day); + // var date2 = new LocalDate(DateTime.Now.Year, DateTime.Now.Month, DateTime.Now.Day); + // Period period = Period.Between(date1, date2); + // var govAgeMonth = period.Months; + // var govAgeYear = period.Years; + + var (govAgeYear, govAgeMonth, govAgeDay) = startDate.GetDifference(DateTime.Now.Date); var thisYear = DateTime.Now.Year; @@ -1089,37 +1119,11 @@ namespace BMA.EHR.Leave.Service.Controllers break; case "LV-005": // fix issue : ระบบลา (ขรก.) >> ลาพักผ่อน (กรณีรับราชการไม่ถึง 6 เดือน) #838 - //var leavePrevYear = (await _leaveRequestRepository.GetSumApproveLeaveAsync(fiscalYear - 1)).Where(x => x.LeaveTypeCode == "LV-005" && x.KeycloakUserId == userId).FirstOrDefault(); - //var leavePrevYearRemain = 10 - (leavePrevYear == null ? 0 : leavePrevYear.SumLeaveDay); // หายอดวันลาที่เหลือของปีก่อน - // if (profile.IsProbation! == true) - // { - // isLeave = false; - // if (!isLeave) message = "ยังอยู่ในช่วงทดลองปฏิบัติราชการ ไม่สามารถลาพักผ่อนได้"; - // } - // else - if (govAgeMonth >= 6) - //else + if (govAgeYear >= 1 || (govAgeYear == 0 && govAgeMonth >= 6)) { isLeave = (totalDay - (sumWorkDay + sumWeekend) + approveDay) <= (limitDay); if (!isLeave) message = "จำนวนวันลาเกินที่กำหนด"; } - // else if (govAge >= 3650) - // { - // // ถ้าอายุราชการเกิน 10 ปี ได้บวกเพิ่มอีก 10 วัน - // var leavePrevYearRemain = 30 - (leavePrevYear == null ? 0 : leavePrevYear.SumLeaveDay); // หายอดวันลาที่เหลือของปีก่อน - // if (leavePrevYearRemain >= 20) leavePrevYearRemain = 20; - - // isLeave = (totalDay - (sumWorkDay + sumWeekend) + approveDay) <= (limitDay); - // if (!isLeave) message = "จำนวนวันลาเกินที่กำหนด"; - // } - // else if - // { - // //var leavePrevYearRemain = 20 - (leavePrevYear == null ? 0 : leavePrevYear.SumLeaveDay); // หายอดวันลาที่เหลือของปีก่อน - // //if (leavePrevYearRemain >= 10) leavePrevYearRemain = 10; - - // isLeave = (totalDay - (sumWorkDay + sumWeekend) + sumApproveLeave) <= (10 + leavePrevYearRemain); - // if (!isLeave) message = "จำนวนวันลาเกินที่กำหนด"; - // } else { isLeave = false; From cd991796218973a7c428d2ac3449a99441a62c99 Mon Sep 17 00:00:00 2001 From: Suphonchai Phoonsawat Date: Tue, 24 Feb 2026 10:24:24 +0700 Subject: [PATCH 121/183] Enhance leave eligibility check in LeaveRequestController with detailed messaging --- .../Controllers/LeaveRequestController.cs | 25 +++++++++++++------ 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/BMA.EHR.Leave/Controllers/LeaveRequestController.cs b/BMA.EHR.Leave/Controllers/LeaveRequestController.cs index 0a69836a..069d2e31 100644 --- a/BMA.EHR.Leave/Controllers/LeaveRequestController.cs +++ b/BMA.EHR.Leave/Controllers/LeaveRequestController.cs @@ -977,19 +977,30 @@ namespace BMA.EHR.Leave.Service.Controllers var date2 = new LocalDate(DateTime.Now.Year, DateTime.Now.Month, DateTime.Now.Day); - var (yy, mm, dd) = startDate.GetDifference(DateTime.Now.Date); + var (govAgeYear, govAgeMonth, govAgeDay) = startDate.GetDifference(DateTime.Now.Date); + var isLeave = false; + var message = string.Empty; - // Period period = Period.Between(date1, date2); - // var govAgeMonth = period.Months; - // var govAgeYear = period.Years; + if (govAgeYear >= 1 || (govAgeYear == 0 && govAgeMonth >= 6)) + { + isLeave = true; + if (!isLeave) message = "จำนวนวันลาเกินที่กำหนด"; + } + else + { + isLeave = false; + if (!isLeave) message = "อายุราชการน้อยกว่า 6 เดือนหรือ 180 วัน"; + } return Success(new { GovAge = govAge, - GovAgeDay = dd, - GovAgeMonth = mm, - GovAgeYear = yy + GovAgeDay = govAgeDay, + GovAgeMonth = govAgeMonth, + GovAgeYear = govAgeYear, + IsLeave = isLeave, + Message = message }); } From 2ee36af76326eb96c8816d1ecc3f00b92607b144 Mon Sep 17 00:00:00 2001 From: Suphonchai Phoonsawat Date: Tue, 24 Feb 2026 19:43:58 +0700 Subject: [PATCH 122/183] Test --- BMA.EHR.Leave/Controllers/LeaveRequestController.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/BMA.EHR.Leave/Controllers/LeaveRequestController.cs b/BMA.EHR.Leave/Controllers/LeaveRequestController.cs index 069d2e31..e658788d 100644 --- a/BMA.EHR.Leave/Controllers/LeaveRequestController.cs +++ b/BMA.EHR.Leave/Controllers/LeaveRequestController.cs @@ -1001,6 +1001,7 @@ namespace BMA.EHR.Leave.Service.Controllers GovAgeYear = govAgeYear, IsLeave = isLeave, Message = message + }); } From 9a74b690cd6574e12210e68a68e1b8ca7c144c80 Mon Sep 17 00:00:00 2001 From: Suphonchai Phoonsawat Date: Wed, 25 Feb 2026 10:13:27 +0700 Subject: [PATCH 123/183] =?UTF-8?q?=E0=B8=97=E0=B8=94=E0=B8=AA=E0=B8=AD?= =?UTF-8?q?=E0=B8=9A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- BMA.EHR.Leave/Controllers/LeaveRequestController.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/BMA.EHR.Leave/Controllers/LeaveRequestController.cs b/BMA.EHR.Leave/Controllers/LeaveRequestController.cs index e658788d..2300e9ec 100644 --- a/BMA.EHR.Leave/Controllers/LeaveRequestController.cs +++ b/BMA.EHR.Leave/Controllers/LeaveRequestController.cs @@ -1002,6 +1002,8 @@ namespace BMA.EHR.Leave.Service.Controllers IsLeave = isLeave, Message = message + + }); } From 006cea048d78e1862e54d5d51d122153d04a1a47 Mon Sep 17 00:00:00 2001 From: Suphonchai Phoonsawat Date: Wed, 25 Feb 2026 15:26:49 +0700 Subject: [PATCH 124/183] Add ScheduleUpdateDna endpoint and DTO for updating DNA information in LeaveBeginningController --- .../Controllers/LeaveBeginningController.cs | 38 +++++++++++++++++++ .../LeaveBeginnings/EditLeaveBeginningDto.cs | 15 ++++++++ 2 files changed, 53 insertions(+) diff --git a/BMA.EHR.Leave/Controllers/LeaveBeginningController.cs b/BMA.EHR.Leave/Controllers/LeaveBeginningController.cs index f3b661cf..29ec361e 100644 --- a/BMA.EHR.Leave/Controllers/LeaveBeginningController.cs +++ b/BMA.EHR.Leave/Controllers/LeaveBeginningController.cs @@ -577,6 +577,44 @@ namespace BMA.EHR.Leave.Service.Controllers } } + + [HttpPut("schedule/update-dna")] + [AllowAnonymous] + public async Task> ScheduleUpdateDnaAsync([FromBody] ScheduleUpdateDnaDto req) + { + try + { + var profile = await _userProfileRepository.GetProfileByProfileIdNoAuthAsync(req.ProfileId, AccessToken); + if(profile == null) + { + return Error("ไม่พบข้อมูลข้าราชการหรือลูกจ้าง", StatusCodes.Status404NotFound); + } + // check duplicate + var oldData = await _context.LeaveBeginnings.Where(x => x.ProfileId == req.ProfileId + && x.LeaveYear == req.LeaveYear).ToListAsync(); + + foreach(var item in oldData) + { + item.RootDnaId = profile.RootDnaId; + item.Child1DnaId = profile.Child1DnaId; + item.Child2DnaId = profile.Child2DnaId; + item.Child3DnaId = profile.Child3DnaId; + item.Child4DnaId = profile.Child4DnaId; + + item.LastUpdateUserId = ""; + item.LastUpdateFullName = "System"; + item.LastUpdatedAt = DateTime.Now; + + await _leaveBeginningRepository.UpdateAsync(item); + } + return Success(); + } + catch (Exception ex) + { + return Error(ex); + } + } + #endregion } } diff --git a/BMA.EHR.Leave/DTOs/LeaveBeginnings/EditLeaveBeginningDto.cs b/BMA.EHR.Leave/DTOs/LeaveBeginnings/EditLeaveBeginningDto.cs index fe0c433f..03c96a49 100644 --- a/BMA.EHR.Leave/DTOs/LeaveBeginnings/EditLeaveBeginningDto.cs +++ b/BMA.EHR.Leave/DTOs/LeaveBeginnings/EditLeaveBeginningDto.cs @@ -45,4 +45,19 @@ namespace BMA.EHR.Leave.Service.DTOs.LeaveBeginnings [Required, Comment("จำนวนวันลายกมา")] public double LeaveDays { get; set; } = 0.0; } + + public class ScheduleUpdateDnaDto + { + [Required] + public Guid ProfileId { get; set; } = Guid.Empty; + + [Required, Comment("ปีงบประมาณ")] + public int LeaveYear { get; set; } = 0; + + public Guid? RootDnaId { get; set; } + public Guid? Child1DnaId { get; set; } + public Guid? Child2DnaId { get; set; } + public Guid? Child3DnaId { get; set; } + public Guid? Child4DnaId { get; set; } + } } From f866435897836b7369c6e41896040fd04c646836 Mon Sep 17 00:00:00 2001 From: Suphonchai Phoonsawat Date: Wed, 25 Feb 2026 16:26:28 +0700 Subject: [PATCH 125/183] Refactor LeaveBeginningController to simplify duplicate check and comment out LeaveYear property in EditLeaveBeginningDto #2341 --- BMA.EHR.Leave/Controllers/LeaveBeginningController.cs | 3 +-- BMA.EHR.Leave/DTOs/LeaveBeginnings/EditLeaveBeginningDto.cs | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/BMA.EHR.Leave/Controllers/LeaveBeginningController.cs b/BMA.EHR.Leave/Controllers/LeaveBeginningController.cs index 29ec361e..5e94c1e4 100644 --- a/BMA.EHR.Leave/Controllers/LeaveBeginningController.cs +++ b/BMA.EHR.Leave/Controllers/LeaveBeginningController.cs @@ -590,8 +590,7 @@ namespace BMA.EHR.Leave.Service.Controllers return Error("ไม่พบข้อมูลข้าราชการหรือลูกจ้าง", StatusCodes.Status404NotFound); } // check duplicate - var oldData = await _context.LeaveBeginnings.Where(x => x.ProfileId == req.ProfileId - && x.LeaveYear == req.LeaveYear).ToListAsync(); + var oldData = await _context.LeaveBeginnings.Where(x => x.ProfileId == req.ProfileId).ToListAsync(); foreach(var item in oldData) { diff --git a/BMA.EHR.Leave/DTOs/LeaveBeginnings/EditLeaveBeginningDto.cs b/BMA.EHR.Leave/DTOs/LeaveBeginnings/EditLeaveBeginningDto.cs index 03c96a49..524072ec 100644 --- a/BMA.EHR.Leave/DTOs/LeaveBeginnings/EditLeaveBeginningDto.cs +++ b/BMA.EHR.Leave/DTOs/LeaveBeginnings/EditLeaveBeginningDto.cs @@ -51,8 +51,8 @@ namespace BMA.EHR.Leave.Service.DTOs.LeaveBeginnings [Required] public Guid ProfileId { get; set; } = Guid.Empty; - [Required, Comment("ปีงบประมาณ")] - public int LeaveYear { get; set; } = 0; + // [Required, Comment("ปีงบประมาณ")] + // public int LeaveYear { get; set; } = 0; public Guid? RootDnaId { get; set; } public Guid? Child1DnaId { get; set; } From 4650f7a2ab574b1eef78e6987fb3d2ee8a7107a7 Mon Sep 17 00:00:00 2001 From: Suphonchai Phoonsawat Date: Thu, 26 Feb 2026 20:36:48 +0700 Subject: [PATCH 126/183] Refactor ScheduleUpdateDnaAsync to handle a list of ScheduleUpdateDnaDto and streamline profile updates #2341 --- .../Controllers/LeaveBeginningController.cs | 43 +++++++++++-------- 1 file changed, 24 insertions(+), 19 deletions(-) diff --git a/BMA.EHR.Leave/Controllers/LeaveBeginningController.cs b/BMA.EHR.Leave/Controllers/LeaveBeginningController.cs index 5e94c1e4..e580b00a 100644 --- a/BMA.EHR.Leave/Controllers/LeaveBeginningController.cs +++ b/BMA.EHR.Leave/Controllers/LeaveBeginningController.cs @@ -580,32 +580,37 @@ namespace BMA.EHR.Leave.Service.Controllers [HttpPut("schedule/update-dna")] [AllowAnonymous] - public async Task> ScheduleUpdateDnaAsync([FromBody] ScheduleUpdateDnaDto req) + public async Task> ScheduleUpdateDnaAsync([FromBody] List req) { try { - var profile = await _userProfileRepository.GetProfileByProfileIdNoAuthAsync(req.ProfileId, AccessToken); - if(profile == null) + foreach(var item in req) { - return Error("ไม่พบข้อมูลข้าราชการหรือลูกจ้าง", StatusCodes.Status404NotFound); - } - // check duplicate - var oldData = await _context.LeaveBeginnings.Where(x => x.ProfileId == req.ProfileId).ToListAsync(); - - foreach(var item in oldData) - { - item.RootDnaId = profile.RootDnaId; - item.Child1DnaId = profile.Child1DnaId; - item.Child2DnaId = profile.Child2DnaId; - item.Child3DnaId = profile.Child3DnaId; - item.Child4DnaId = profile.Child4DnaId; + // var profile = await _userProfileRepository.GetProfileByProfileIdNoAuthAsync(item.ProfileId, AccessToken); + // if(profile == null) + // { + // return Error("ไม่พบข้อมูลข้าราชการหรือลูกจ้าง", StatusCodes.Status404NotFound); + // } + // check duplicate + var oldData = await _context.LeaveBeginnings.Where(x => x.ProfileId == item.ProfileId).ToListAsync(); + + foreach(var o in oldData) + { + o.RootDnaId = item.RootDnaId; + o.Child1DnaId = item.Child1DnaId; + o.Child2DnaId = item.Child2DnaId; + o.Child3DnaId = item.Child3DnaId; + o.Child4DnaId = item.Child4DnaId; - item.LastUpdateUserId = ""; - item.LastUpdateFullName = "System"; - item.LastUpdatedAt = DateTime.Now; + o.LastUpdateUserId = ""; + o.LastUpdateFullName = "System"; + o.LastUpdatedAt = DateTime.Now; - await _leaveBeginningRepository.UpdateAsync(item); + await _leaveBeginningRepository.UpdateAsync(o); + } } + + return Success(); } catch (Exception ex) From 4562029e6ea899524eb922b1ca716494234816bb Mon Sep 17 00:00:00 2001 From: Suphonchai Phoonsawat Date: Tue, 10 Mar 2026 14:10:35 +0700 Subject: [PATCH 127/183] Update GetTimeStampHistoryAsync call to include pagination and keyword filtering --- BMA.EHR.Leave/Controllers/LeaveController.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BMA.EHR.Leave/Controllers/LeaveController.cs b/BMA.EHR.Leave/Controllers/LeaveController.cs index 64d7a92e..5bedbae5 100644 --- a/BMA.EHR.Leave/Controllers/LeaveController.cs +++ b/BMA.EHR.Leave/Controllers/LeaveController.cs @@ -1762,7 +1762,7 @@ namespace BMA.EHR.Leave.Service.Controllers // var test = await _processUserTimeStampRepository.GetTimeStampHistoryAsync(userId, year); // return Success(test); - var data = (await _processUserTimeStampRepository.GetTimeStampHistoryAsync(userId, year)) + var data = (await _processUserTimeStampRepository.GetTimeStampHistoryAsync(userId, year, page, pageSize, keyword)) .Select(d => new CheckInHistoryDto { CheckInId = d.Id, From 6902236f48b1111ae7d01ab153ec0c97f1d7e58a Mon Sep 17 00:00:00 2001 From: Suphonchai Phoonsawat Date: Wed, 11 Mar 2026 11:57:00 +0700 Subject: [PATCH 128/183] Add GetTimeStampHistoryAsync2 method for fiscal year timestamp retrieval --- .../ProcessUserTimeStampRepository.cs | 13 +++++++++++++ BMA.EHR.Leave/Controllers/LeaveController.cs | 2 +- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/BMA.EHR.Application/Repositories/Leaves/TimeAttendants/ProcessUserTimeStampRepository.cs b/BMA.EHR.Application/Repositories/Leaves/TimeAttendants/ProcessUserTimeStampRepository.cs index c1df8798..e1a25780 100644 --- a/BMA.EHR.Application/Repositories/Leaves/TimeAttendants/ProcessUserTimeStampRepository.cs +++ b/BMA.EHR.Application/Repositories/Leaves/TimeAttendants/ProcessUserTimeStampRepository.cs @@ -227,6 +227,19 @@ namespace BMA.EHR.Application.Repositories.Leaves.TimeAttendants return data; } + public async Task> GetTimeStampHistoryAsync2(Guid keycloakId, int year) + { + var fiscalDateStart = new DateTime(year - 1, 10, 1); + var fiscalDateEnd = new DateTime(year, 9, 30); + + var data = await _dbContext.Set() + .Where(u => u.KeycloakUserId == keycloakId) + .Where(u => u.CheckIn.Date >= fiscalDateStart && u.CheckIn.Date <= fiscalDateEnd) + .OrderByDescending(u => u.CheckIn.Date) + .ToListAsync(); + return data; + } + public async Task GetTimeStampHistoryForAdminCountAsync(DateTime startDate, DateTime endDate) { var data = await _dbContext.Set() diff --git a/BMA.EHR.Leave/Controllers/LeaveController.cs b/BMA.EHR.Leave/Controllers/LeaveController.cs index 5bedbae5..d3907e66 100644 --- a/BMA.EHR.Leave/Controllers/LeaveController.cs +++ b/BMA.EHR.Leave/Controllers/LeaveController.cs @@ -1762,7 +1762,7 @@ namespace BMA.EHR.Leave.Service.Controllers // var test = await _processUserTimeStampRepository.GetTimeStampHistoryAsync(userId, year); // return Success(test); - var data = (await _processUserTimeStampRepository.GetTimeStampHistoryAsync(userId, year, page, pageSize, keyword)) + var data = (await _processUserTimeStampRepository.GetTimeStampHistoryAsync2(userId, year)) .Select(d => new CheckInHistoryDto { CheckInId = d.Id, From b1df33dc209fbc9fa0796a6d6c1604da4b46fd2b Mon Sep 17 00:00:00 2001 From: harid Date: Wed, 11 Mar 2026 14:01:34 +0700 Subject: [PATCH 129/183] fix bug #2183 --- .../Controllers/RetirementResignController.cs | 56 +++++++++---------- .../RetirementResignEmployeeController.cs | 56 +++++++++---------- 2 files changed, 56 insertions(+), 56 deletions(-) diff --git a/BMA.EHR.Retirement.Service/Controllers/RetirementResignController.cs b/BMA.EHR.Retirement.Service/Controllers/RetirementResignController.cs index a70e357d..7b8d27b1 100644 --- a/BMA.EHR.Retirement.Service/Controllers/RetirementResignController.cs +++ b/BMA.EHR.Retirement.Service/Controllers/RetirementResignController.cs @@ -3104,13 +3104,13 @@ namespace BMA.EHR.Retirement.Service.Controllers }).ToList(); var baseAPIOrg = _configuration["API"]; - var reportDone = false; - if (data.Where(profile => profile.Status == "DONE").Any()) - { - reportDone = true; - } - if (reportDone == true) - { + //var reportDone = false; + //if (data.Where(profile => profile.Status == "DONE").Any()) + //{ + // reportDone = true; + //} + //if (reportDone == true) + //{ var apiUrlOrg = $"{baseAPIOrg}/org/command/excexute/salary-leave"; using (var client = new HttpClient()) { @@ -3128,27 +3128,27 @@ namespace BMA.EHR.Retirement.Service.Controllers await _context.SaveChangesAsync(); } } - } - else - { - var apiUrlOrg = $"{baseAPIOrg}/org/command/cancel-resign"; - using (var client = new HttpClient()) - { - client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token.Replace("Bearer ", "")); - client.DefaultRequestHeaders.Add("api-key", _configuration["API_KEY"]); - var _res = await client.PostAsJsonAsync(apiUrlOrg, new - { - resignId = resultData.Select(x => x.resignId).ToList(), - }); - var _result = await _res.Content.ReadAsStringAsync(); - if (_res.IsSuccessStatusCode) - { - data.ForEach(profile => profile.Status = "DONE"); - data.ForEach(profile => profile.RetirementResign.Status = "CANCEL"); - await _context.SaveChangesAsync(); - } - } - } + //} + //else + //{ + // var apiUrlOrg = $"{baseAPIOrg}/org/command/cancel-resign"; + // using (var client = new HttpClient()) + // { + // client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token.Replace("Bearer ", "")); + // client.DefaultRequestHeaders.Add("api-key", _configuration["API_KEY"]); + // var _res = await client.PostAsJsonAsync(apiUrlOrg, new + // { + // resignId = resultData.Select(x => x.resignId).ToList(), + // }); + // var _result = await _res.Content.ReadAsStringAsync(); + // if (_res.IsSuccessStatusCode) + // { + // data.ForEach(profile => profile.Status = "DONE"); + // data.ForEach(profile => profile.RetirementResign.Status = "CANCEL"); + // await _context.SaveChangesAsync(); + // } + // } + //} return Success(); } diff --git a/BMA.EHR.Retirement.Service/Controllers/RetirementResignEmployeeController.cs b/BMA.EHR.Retirement.Service/Controllers/RetirementResignEmployeeController.cs index e42e2e6a..9b176503 100644 --- a/BMA.EHR.Retirement.Service/Controllers/RetirementResignEmployeeController.cs +++ b/BMA.EHR.Retirement.Service/Controllers/RetirementResignEmployeeController.cs @@ -2598,14 +2598,14 @@ namespace BMA.EHR.Retirement.Service.Controllers }).ToList(); var baseAPIOrg = _configuration["API"]; - var reportDone = false; - if (data.Where(profile => profile.Status == "DONE").Any()) - { - reportDone = true; - } + //var reportDone = false; + //if (data.Where(profile => profile.Status == "DONE").Any()) + //{ + // reportDone = true; + //} - if (reportDone == true) - { + //if (reportDone == true) + //{ var apiUrlOrg = $"{baseAPIOrg}/org/command/excexute/salary-employee-leave"; using (var client = new HttpClient()) { @@ -2623,27 +2623,27 @@ namespace BMA.EHR.Retirement.Service.Controllers await _context.SaveChangesAsync(); } } - } - else - { - var apiUrlOrg = $"{baseAPIOrg}/org/command/cancel-resign"; - using (var client = new HttpClient()) - { - client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token.Replace("Bearer ", "")); - client.DefaultRequestHeaders.Add("api-key", _configuration["API_KEY"]); - var _res = await client.PostAsJsonAsync(apiUrlOrg, new - { - resignId = resultData.Select(x => x.resignId).ToList(), - }); - var _result = await _res.Content.ReadAsStringAsync(); - if (_res.IsSuccessStatusCode) - { - data.ForEach(profile => profile.Status = "DONE"); - data.ForEach(profile => profile.RetirementResignEmployee.Status = "CANCEL"); - await _context.SaveChangesAsync(); - } - } - } + //} + //else + //{ + // var apiUrlOrg = $"{baseAPIOrg}/org/command/cancel-resign"; + // using (var client = new HttpClient()) + // { + // client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token.Replace("Bearer ", "")); + // client.DefaultRequestHeaders.Add("api-key", _configuration["API_KEY"]); + // var _res = await client.PostAsJsonAsync(apiUrlOrg, new + // { + // resignId = resultData.Select(x => x.resignId).ToList(), + // }); + // var _result = await _res.Content.ReadAsStringAsync(); + // if (_res.IsSuccessStatusCode) + // { + // data.ForEach(profile => profile.Status = "DONE"); + // data.ForEach(profile => profile.RetirementResignEmployee.Status = "CANCEL"); + // await _context.SaveChangesAsync(); + // } + // } + //} return Success(); } From 7e0f0485fd0a46910fff7899bee593d6773d14f1 Mon Sep 17 00:00:00 2001 From: Suphonchai Phoonsawat Date: Tue, 17 Mar 2026 15:47:03 +0700 Subject: [PATCH 130/183] Add TokenUserInfo class and extend ClaimsPrincipal with methods for user claims retrieval --- BMA.EHR.Domain/Common/BaseController.cs | 17 ++- BMA.EHR.Domain/Common/TokenUserInfo.cs | 39 ++++++ .../Extensions/ClaimsPrincipalExtensions.cs | 30 +++++ ...ombinedErrorHandlerAndLoggingMiddleware.cs | 119 ++++++++++++++++-- BMA.EHR.Leave/Controllers/LeaveController.cs | 8 +- 5 files changed, 201 insertions(+), 12 deletions(-) create mode 100644 BMA.EHR.Domain/Common/TokenUserInfo.cs create mode 100644 BMA.EHR.Domain/Extensions/ClaimsPrincipalExtensions.cs diff --git a/BMA.EHR.Domain/Common/BaseController.cs b/BMA.EHR.Domain/Common/BaseController.cs index 44d8dac0..26f71bf5 100644 --- a/BMA.EHR.Domain/Common/BaseController.cs +++ b/BMA.EHR.Domain/Common/BaseController.cs @@ -1,4 +1,5 @@ -using BMA.EHR.Domain.Shared; +using BMA.EHR.Domain.Extensions; +using BMA.EHR.Domain.Shared; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; @@ -81,6 +82,20 @@ namespace BMA.EHR.Domain.Common } + #endregion + + #region " Properties " + + protected string? EmpType => User.GetEmpType(); + protected Guid? OrgChild1DnaId => User.GetOrgChild1DnaId(); + protected Guid? OrgChild2DnaId => User.GetOrgChild2DnaId(); + protected Guid? OrgChild3DnaId => User.GetOrgChild3DnaId(); + protected Guid? OrgChild4DnaId => User.GetOrgChild4DnaId(); + protected Guid? OrgRootDnaId => User.GetOrgRootDnaId(); + protected Guid? ProfileId => User.GetProfileId(); + protected string? Prefix => User.GetPrefix(); + protected string? FullNameFromClaim => User.GetName(); + #endregion #endregion diff --git a/BMA.EHR.Domain/Common/TokenUserInfo.cs b/BMA.EHR.Domain/Common/TokenUserInfo.cs new file mode 100644 index 00000000..cdae6fb1 --- /dev/null +++ b/BMA.EHR.Domain/Common/TokenUserInfo.cs @@ -0,0 +1,39 @@ +namespace BMA.EHR.Domain.Common +{ + public class TokenUserInfo + { + // Existing properties + public string KeycloakId { get; set; } = string.Empty; + public string? PreferredUsername { get; set; } + public string? GivenName { get; set; } + public string? FamilyName { get; set; } + + // New properties to add + public string? EmpType { get; set; } + public Guid? OrgChild1DnaId { get; set; } + public Guid? OrgChild2DnaId { get; set; } + public Guid? OrgChild3DnaId { get; set; } + public Guid? OrgChild4DnaId { get; set; } + public Guid? OrgRootDnaId { get; set; } + public Guid? ProfileId { get; set; } + public string? Prefix { get; set; } + public string? Name { get; set; } + } + + // Claim type constants + public static class BmaClaimTypes + { + public const string EmpType = "empType"; + public const string OrgChild1DnaId = "orgChild1DnaId"; + public const string OrgChild2DnaId = "orgChild2DnaId"; + public const string OrgChild3DnaId = "orgChild3DnaId"; + public const string OrgChild4DnaId = "orgChild4DnaId"; + public const string OrgRootDnaId = "orgRootDnaId"; + public const string ProfileId = "profileId"; + public const string Prefix = "prefix"; + public const string Name = "name"; + public const string GivenName = "given_name"; + public const string FamilyName = "family_name"; + public const string PreferredUsername = "preferred_username"; + } +} diff --git a/BMA.EHR.Domain/Extensions/ClaimsPrincipalExtensions.cs b/BMA.EHR.Domain/Extensions/ClaimsPrincipalExtensions.cs new file mode 100644 index 00000000..26a7c189 --- /dev/null +++ b/BMA.EHR.Domain/Extensions/ClaimsPrincipalExtensions.cs @@ -0,0 +1,30 @@ +using BMA.EHR.Domain.Common; +using System.Security.Claims; + +namespace BMA.EHR.Domain.Extensions +{ + public static class ClaimsPrincipalExtensions + { + public static string? GetClaimValue(this ClaimsPrincipal user, string claimType) + { + return user?.FindFirst(claimType)?.Value; + } + + public static Guid? GetGuidClaim(this ClaimsPrincipal user, string claimType) + { + var value = user?.GetClaimValue(claimType); + return Guid.TryParse(value, out var guid) ? guid : null; + } + + // Convenience methods for common claims + public static string? GetEmpType(this ClaimsPrincipal user) => user.GetClaimValue(BmaClaimTypes.EmpType); + public static Guid? GetOrgChild1DnaId(this ClaimsPrincipal user) => user.GetGuidClaim(BmaClaimTypes.OrgChild1DnaId); + public static Guid? GetOrgChild2DnaId(this ClaimsPrincipal user) => user.GetGuidClaim(BmaClaimTypes.OrgChild2DnaId); + public static Guid? GetOrgChild3DnaId(this ClaimsPrincipal user) => user.GetGuidClaim(BmaClaimTypes.OrgChild3DnaId); + public static Guid? GetOrgChild4DnaId(this ClaimsPrincipal user) => user.GetGuidClaim(BmaClaimTypes.OrgChild4DnaId); + public static Guid? GetOrgRootDnaId(this ClaimsPrincipal user) => user.GetGuidClaim(BmaClaimTypes.OrgRootDnaId); + public static Guid? GetProfileId(this ClaimsPrincipal user) => user.GetGuidClaim(BmaClaimTypes.ProfileId); + public static string? GetPrefix(this ClaimsPrincipal user) => user.GetClaimValue(BmaClaimTypes.Prefix); + public static string? GetName(this ClaimsPrincipal user) => user.GetClaimValue(BmaClaimTypes.Name); + } +} diff --git a/BMA.EHR.Domain/Middlewares/CombinedErrorHandlerAndLoggingMiddleware.cs b/BMA.EHR.Domain/Middlewares/CombinedErrorHandlerAndLoggingMiddleware.cs index 15c88592..4216fa43 100644 --- a/BMA.EHR.Domain/Middlewares/CombinedErrorHandlerAndLoggingMiddleware.cs +++ b/BMA.EHR.Domain/Middlewares/CombinedErrorHandlerAndLoggingMiddleware.cs @@ -79,13 +79,39 @@ namespace BMA.EHR.Domain.Middlewares GetProfileByKeycloakIdLocal? pf = null; var tokenUserInfo = await ExtractTokenUserInfoAsync(token); + // Store tokenUserInfo in HttpContext.Items for controllers to use + context.Items["TokenUserInfo"] = tokenUserInfo; + // ดึง keycloakId จาก JWT token keycloakId = tokenUserInfo.KeycloakId; - // ดึง profile จาก cache หรือ API + // ดึง profile จาก claims หรือ cache หรือ API if (Guid.TryParse(keycloakId, out var parsedId) && parsedId != Guid.Empty) { - pf = await GetProfileWithCacheAsync(parsedId, token); + // Build profile from token claims if available + if (tokenUserInfo.OrgRootDnaId.HasValue && tokenUserInfo.ProfileId.HasValue) + { + pf = new GetProfileByKeycloakIdLocal + { + Id = tokenUserInfo.ProfileId.Value, + CitizenId = tokenUserInfo.PreferredUsername, + Prefix = tokenUserInfo.Prefix, + FirstName = tokenUserInfo.GivenName, + LastName = tokenUserInfo.FamilyName, + RootDnaId = tokenUserInfo.OrgRootDnaId, + Child1DnaId = tokenUserInfo.OrgChild1DnaId, + Child2DnaId = tokenUserInfo.OrgChild2DnaId, + Child3DnaId = tokenUserInfo.OrgChild3DnaId, + Child4DnaId = tokenUserInfo.OrgChild4DnaId, + }; + Console.WriteLine($"[INFO] Using claims for profile - OrgRootDnaId: {pf.RootDnaId}, ProfileId: {pf.Id}"); + } + else + { + // Fallback to API only if critical claims are missing + Console.WriteLine("[WARN] Critical claims missing, falling back to API call"); + pf = await GetProfileWithCacheAsync(parsedId, token); + } } try @@ -649,6 +675,87 @@ namespace BMA.EHR.Domain.Middlewares Console.WriteLine($"Extracted family_name: {result.FamilyName}"); } + // ดึง empType + if (jsonDoc.RootElement.TryGetProperty("empType", out var empTypeElement)) + { + result.EmpType = empTypeElement.GetString(); + Console.WriteLine($"Extracted empType: {result.EmpType}"); + } + + // ดึง orgChild1DnaId + if (jsonDoc.RootElement.TryGetProperty("orgChild1DnaId", out var orgChild1Element)) + { + if (Guid.TryParse(orgChild1Element.GetString(), out var orgChild1Guid)) + { + result.OrgChild1DnaId = orgChild1Guid; + Console.WriteLine($"Extracted orgChild1DnaId: {result.OrgChild1DnaId}"); + } + } + + // ดึง orgChild2DnaId + if (jsonDoc.RootElement.TryGetProperty("orgChild2DnaId", out var orgChild2Element)) + { + if (Guid.TryParse(orgChild2Element.GetString(), out var orgChild2Guid)) + { + result.OrgChild2DnaId = orgChild2Guid; + Console.WriteLine($"Extracted orgChild2DnaId: {result.OrgChild2DnaId}"); + } + } + + // ดึง orgChild3DnaId + if (jsonDoc.RootElement.TryGetProperty("orgChild3DnaId", out var orgChild3Element)) + { + if (Guid.TryParse(orgChild3Element.GetString(), out var orgChild3Guid)) + { + result.OrgChild3DnaId = orgChild3Guid; + Console.WriteLine($"Extracted orgChild3DnaId: {result.OrgChild3DnaId}"); + } + } + + // ดึง orgChild4DnaId + if (jsonDoc.RootElement.TryGetProperty("orgChild4DnaId", out var orgChild4Element)) + { + if (Guid.TryParse(orgChild4Element.GetString(), out var orgChild4Guid)) + { + result.OrgChild4DnaId = orgChild4Guid; + Console.WriteLine($"Extracted orgChild4DnaId: {result.OrgChild4DnaId}"); + } + } + + // ดึง orgRootDnaId + if (jsonDoc.RootElement.TryGetProperty("orgRootDnaId", out var orgRootElement)) + { + if (Guid.TryParse(orgRootElement.GetString(), out var orgRootGuid)) + { + result.OrgRootDnaId = orgRootGuid; + Console.WriteLine($"Extracted orgRootDnaId: {result.OrgRootDnaId}"); + } + } + + // ดึง profileId + if (jsonDoc.RootElement.TryGetProperty("profileId", out var profileIdElement)) + { + if (Guid.TryParse(profileIdElement.GetString(), out var profileIdGuid)) + { + result.ProfileId = profileIdGuid; + Console.WriteLine($"Extracted profileId: {result.ProfileId}"); + } + } + + // ดึง prefix + if (jsonDoc.RootElement.TryGetProperty("prefix", out var prefixElement)) + { + result.Prefix = prefixElement.GetString(); + Console.WriteLine($"Extracted prefix: {result.Prefix}"); + } + + // ดึง name + if (jsonDoc.RootElement.TryGetProperty("name", out var nameElement)) + { + result.Name = nameElement.GetString(); + Console.WriteLine($"Extracted name: {result.Name}"); + } + return result; } catch (Exception ex) @@ -767,14 +874,6 @@ namespace BMA.EHR.Domain.Middlewares } // Model classes - public class TokenUserInfo - { - public string KeycloakId { get; set; } = string.Empty; - public string? PreferredUsername { get; set; } - public string? GivenName { get; set; } - public string? FamilyName { get; set; } - } - public class GetProfileByKeycloakIdLocal { public Guid Id { get; set; } diff --git a/BMA.EHR.Leave/Controllers/LeaveController.cs b/BMA.EHR.Leave/Controllers/LeaveController.cs index d3907e66..acb10e8b 100644 --- a/BMA.EHR.Leave/Controllers/LeaveController.cs +++ b/BMA.EHR.Leave/Controllers/LeaveController.cs @@ -144,7 +144,13 @@ namespace BMA.EHR.Leave.Service.Controllers { get { - if (UserId != null || UserId != "") + // First try to get from claims + var ocIdFromClaims = OrgRootDnaId; + if (ocIdFromClaims.HasValue && ocIdFromClaims.Value != Guid.Empty) + return ocIdFromClaims.Value; + + // Fallback to API call for backward compatibility + if (UserId != null && UserId != "") return _userProfileRepository.GetUserOCId(Guid.Parse(UserId!), AccessToken); else return Guid.Empty; From 23bbd9791e5f1a5f21a2ea3685c7f88714a5aba5 Mon Sep 17 00:00:00 2001 From: Suphonchai Phoonsawat Date: Mon, 23 Mar 2026 09:49:17 +0700 Subject: [PATCH 131/183] Add CreateChangeRoundMultipleAsync method for batch processing of duty time changes #1555 --- BMA.EHR.Leave/Controllers/LeaveController.cs | 53 ++++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/BMA.EHR.Leave/Controllers/LeaveController.cs b/BMA.EHR.Leave/Controllers/LeaveController.cs index acb10e8b..d9e612e0 100644 --- a/BMA.EHR.Leave/Controllers/LeaveController.cs +++ b/BMA.EHR.Leave/Controllers/LeaveController.cs @@ -2554,6 +2554,59 @@ namespace BMA.EHR.Leave.Service.Controllers } + [HttpPost("round/multiple")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status401Unauthorized)] + [ProducesResponseType(StatusCodes.Status500InternalServerError)] + public async Task> CreateChangeRoundMultipleAsync([FromBody] List reqs) + { + var getPermission = await _permission.GetPermissionAPIAsync("UPDATE", "SYS_WORK_ROUND_EDIT"); + var jsonData = JsonConvert.DeserializeObject(getPermission); + if (jsonData["status"]?.ToString() != "200") + { + return Error(jsonData["message"]?.ToString(), StatusCodes.Status403Forbidden); + } + var currentDate = DateTime.Now.Date; + + foreach(var req in reqs) + { + var profile = await _userProfileRepository.GetProfileByProfileIdAsync(req.ProfileId, AccessToken); + if (profile == null) + { + return Error(GlobalMessages.DataNotFound, StatusCodes.Status404NotFound); + } + + if (req.EffectiveDate.Date < currentDate) + { + return Error(new Exception($"กำหนดรอบลงเวลาของ {profile.FirstName} {profile.LastName} ผิดพลาด เนื่องจากวันที่มีผลต้องมากกว่าหรือเท่ากับวันที่ปัจจุบัน({currentDate.ToString("yyyy-MM-dd")})"), StatusCodes.Status400BadRequest); + } + + var old = await _userDutyTimeRepository.GetExist(req.ProfileId, req.EffectiveDate); + + if (old != null) + { + return Error(new Exception($"กำหนดรอบลงเวลาของ {profile.FirstName} {profile.LastName} ผิดพลาด เนื่องจากมีการกำหนดรอบการทำงานในวันที่นี้ไว้แล้ว"), StatusCodes.Status400BadRequest); + } + + var data = new UserDutyTime + { + ProfileId = req.ProfileId, + DutyTimeId = req.RoundId, + EffectiveDate = req.EffectiveDate, + Remark = req.Remark, + + RootDnaId = profile.RootDnaId, + Child1DnaId = profile.Child1DnaId, + Child2DnaId = profile.Child2DnaId, + Child3DnaId = profile.Child3DnaId, + Child4DnaId = profile.Child4DnaId, + }; + + await _userDutyTimeRepository.AddAsync(data); + } + return Success(); + } + /// /// LV1_015 - ประวัติการเปลี่ยนรอบการลงเวลา (ADMIN) /// From 818ff38e9995c8e3ddae0f09c8d8ec5b6f224fd6 Mon Sep 17 00:00:00 2001 From: Suphonchai Phoonsawat Date: Mon, 23 Mar 2026 10:04:09 +0700 Subject: [PATCH 132/183] Add SelectedNodeId parameter to SearchProfile method and update related DTO #1555 --- BMA.EHR.Application/Repositories/UserProfileRepository.cs | 3 ++- BMA.EHR.Leave/Controllers/LeaveController.cs | 2 +- BMA.EHR.Leave/DTOs/ChangeRound/SearchProfileDto.cs | 2 ++ 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/BMA.EHR.Application/Repositories/UserProfileRepository.cs b/BMA.EHR.Application/Repositories/UserProfileRepository.cs index 6ac6b9ed..cb260158 100644 --- a/BMA.EHR.Application/Repositories/UserProfileRepository.cs +++ b/BMA.EHR.Application/Repositories/UserProfileRepository.cs @@ -970,7 +970,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,Guid? selectedNodeId) { try { @@ -986,6 +986,7 @@ namespace BMA.EHR.Application.Repositories node = node, page = page, pageSize = pageSize, + selectedNodeId = selectedNodeId }; var profiles = new List(); diff --git a/BMA.EHR.Leave/Controllers/LeaveController.cs b/BMA.EHR.Leave/Controllers/LeaveController.cs index d9e612e0..2008620a 100644 --- a/BMA.EHR.Leave/Controllers/LeaveController.cs +++ b/BMA.EHR.Leave/Controllers/LeaveController.cs @@ -2443,7 +2443,7 @@ namespace BMA.EHR.Leave.Service.Controllers { nodeId = profileAdmin?.RootDnaId; } - var profile = await _userProfileRepository.SearchProfile(req.CitizenId, req.FirstName, req.LastName, AccessToken ?? "", req.Page, req.PageSize, role, nodeId, profileAdmin?.Node); + var profile = await _userProfileRepository.SearchProfile(req.CitizenId, req.FirstName, req.LastName, AccessToken ?? "", req.Page, req.PageSize, role, nodeId, profileAdmin?.Node, req.SelectedNodeId); // Get default round once var getDefaultRound = await _dutyTimeRepository.GetDefaultAsync(); diff --git a/BMA.EHR.Leave/DTOs/ChangeRound/SearchProfileDto.cs b/BMA.EHR.Leave/DTOs/ChangeRound/SearchProfileDto.cs index 3b05ad29..4ae9b3c7 100644 --- a/BMA.EHR.Leave/DTOs/ChangeRound/SearchProfileDto.cs +++ b/BMA.EHR.Leave/DTOs/ChangeRound/SearchProfileDto.cs @@ -17,5 +17,7 @@ public string? sortBy { get; set; } public bool? descending { get; set; } + + public Guid? SelectedNodeId { get; set; } } } From 58aca3a328b5e66c719923a0a0796e4ffa71d92e Mon Sep 17 00:00:00 2001 From: Suphonchai Phoonsawat Date: Mon, 23 Mar 2026 10:13:13 +0700 Subject: [PATCH 133/183] Add SelectedNode parameter to SearchProfile method for enhanced profile retrieval #1555 --- BMA.EHR.Application/Repositories/UserProfileRepository.cs | 5 +++-- BMA.EHR.Leave/Controllers/LeaveController.cs | 2 +- BMA.EHR.Leave/DTOs/ChangeRound/SearchProfileDto.cs | 2 ++ 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/BMA.EHR.Application/Repositories/UserProfileRepository.cs b/BMA.EHR.Application/Repositories/UserProfileRepository.cs index cb260158..8c5eacb0 100644 --- a/BMA.EHR.Application/Repositories/UserProfileRepository.cs +++ b/BMA.EHR.Application/Repositories/UserProfileRepository.cs @@ -970,7 +970,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,Guid? selectedNodeId) + public async Task SearchProfile(string? citizenId, string? firstName, string? lastName, string accessToken, int page, int pageSize, string? role, string? nodeId, int? node,Guid? selectedNodeId,int? selectedNode ) { try { @@ -986,7 +986,8 @@ namespace BMA.EHR.Application.Repositories node = node, page = page, pageSize = pageSize, - selectedNodeId = selectedNodeId + selectedNodeId = selectedNodeId, + selectedNode = selectedNode }; var profiles = new List(); diff --git a/BMA.EHR.Leave/Controllers/LeaveController.cs b/BMA.EHR.Leave/Controllers/LeaveController.cs index 2008620a..a1b7d381 100644 --- a/BMA.EHR.Leave/Controllers/LeaveController.cs +++ b/BMA.EHR.Leave/Controllers/LeaveController.cs @@ -2443,7 +2443,7 @@ namespace BMA.EHR.Leave.Service.Controllers { nodeId = profileAdmin?.RootDnaId; } - var profile = await _userProfileRepository.SearchProfile(req.CitizenId, req.FirstName, req.LastName, AccessToken ?? "", req.Page, req.PageSize, role, nodeId, profileAdmin?.Node, req.SelectedNodeId); + var profile = await _userProfileRepository.SearchProfile(req.CitizenId, req.FirstName, req.LastName, AccessToken ?? "", req.Page, req.PageSize, role, nodeId, profileAdmin?.Node, req.SelectedNodeId,req.SelectedNode); // Get default round once var getDefaultRound = await _dutyTimeRepository.GetDefaultAsync(); diff --git a/BMA.EHR.Leave/DTOs/ChangeRound/SearchProfileDto.cs b/BMA.EHR.Leave/DTOs/ChangeRound/SearchProfileDto.cs index 4ae9b3c7..69f5cbba 100644 --- a/BMA.EHR.Leave/DTOs/ChangeRound/SearchProfileDto.cs +++ b/BMA.EHR.Leave/DTOs/ChangeRound/SearchProfileDto.cs @@ -19,5 +19,7 @@ public bool? descending { get; set; } public Guid? SelectedNodeId { get; set; } + + public int? SelectedNode { get; set; } } } From 252d8b5fa3b5bbe7677b8bec6796cf640b398499 Mon Sep 17 00:00:00 2001 From: Suphonchai Phoonsawat Date: Mon, 23 Mar 2026 10:40:54 +0700 Subject: [PATCH 134/183] Update SearchProfile method parameters to use string for SelectedNodeId #1555 --- BMA.EHR.Application/Repositories/UserProfileRepository.cs | 2 +- BMA.EHR.Leave/Controllers/LeaveController.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/BMA.EHR.Application/Repositories/UserProfileRepository.cs b/BMA.EHR.Application/Repositories/UserProfileRepository.cs index 8c5eacb0..ba1f70c3 100644 --- a/BMA.EHR.Application/Repositories/UserProfileRepository.cs +++ b/BMA.EHR.Application/Repositories/UserProfileRepository.cs @@ -970,7 +970,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,Guid? selectedNodeId,int? selectedNode ) + public async Task SearchProfile(string? citizenId, string? firstName, string? lastName, string accessToken, int page, int pageSize, string? role, string? nodeId, int? node,string? selectedNodeId,int? selectedNode ) { try { diff --git a/BMA.EHR.Leave/Controllers/LeaveController.cs b/BMA.EHR.Leave/Controllers/LeaveController.cs index a1b7d381..23d38e3a 100644 --- a/BMA.EHR.Leave/Controllers/LeaveController.cs +++ b/BMA.EHR.Leave/Controllers/LeaveController.cs @@ -2443,7 +2443,7 @@ namespace BMA.EHR.Leave.Service.Controllers { nodeId = profileAdmin?.RootDnaId; } - var profile = await _userProfileRepository.SearchProfile(req.CitizenId, req.FirstName, req.LastName, AccessToken ?? "", req.Page, req.PageSize, role, nodeId, profileAdmin?.Node, req.SelectedNodeId,req.SelectedNode); + var profile = await _userProfileRepository.SearchProfile(req.CitizenId, req.FirstName, req.LastName, AccessToken ?? "", req.Page, req.PageSize, role, nodeId, profileAdmin?.Node, req.SelectedNodeId == null ? null : req.SelectedNodeId.Value.ToString("D"), req.SelectedNode); // Get default round once var getDefaultRound = await _dutyTimeRepository.GetDefaultAsync(); From 6427cb4344e5d14444f435945fb3f7add44df44d Mon Sep 17 00:00:00 2001 From: Suphonchai Phoonsawat Date: Tue, 24 Mar 2026 09:00:06 +0700 Subject: [PATCH 135/183] Comment out probation-related leave limit checks in LeaveRequestController and update appsettings.json to disable unused database connections --- .../Controllers/LeaveRequestController.cs | 16 ++++++++-------- BMA.EHR.Leave/appsettings.json | 6 +++--- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/BMA.EHR.Leave/Controllers/LeaveRequestController.cs b/BMA.EHR.Leave/Controllers/LeaveRequestController.cs index 2300e9ec..8d626d20 100644 --- a/BMA.EHR.Leave/Controllers/LeaveRequestController.cs +++ b/BMA.EHR.Leave/Controllers/LeaveRequestController.cs @@ -898,9 +898,9 @@ namespace BMA.EHR.Leave.Service.Controllers if (leaveType.Code.Trim().ToUpper() == "LV-005") { - if (profile.IsProbation! == true) - leaveLimit = 0; - else + // if (profile.IsProbation! == true) + // leaveLimit = 0; + // else { leaveLimit = leaveData == null ? 10 @@ -912,11 +912,11 @@ namespace BMA.EHR.Leave.Service.Controllers var restOldDay = leaveData == null ? 0 : leaveData.LeaveDays - 10; var restCurrentDay = 10.0; - if (profile.IsProbation! == true) - { - restOldDay = 0; - restCurrentDay = 0; - } + // if (profile.IsProbation! == true) + // { + // restOldDay = 0; + // restCurrentDay = 0; + // } if(restOldDay < 0) restOldDay = 0; var sumLeave = leaveData == null ? 0 : leaveData.LeaveDaysUsed; diff --git a/BMA.EHR.Leave/appsettings.json b/BMA.EHR.Leave/appsettings.json index 40b464cf..f8562d6e 100644 --- a/BMA.EHR.Leave/appsettings.json +++ b/BMA.EHR.Leave/appsettings.json @@ -23,9 +23,9 @@ "ExamConnection": "server=192.168.1.63;user=root;password=12345678;port=3306;database=hrms_exam;Convert Zero Datetime=True;Allow User Variables=true;Pooling=True;", "LeaveConnection": "server=192.168.1.63;user=root;password=12345678;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;", - // "LeaveConnection": "server=172.27.17.68;user=user;password=cDldaqkwESWvuZ37Gr0n;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;", + //"LeaveConnection": "server=172.27.17.68;user=user;password=cDldaqkwESWvuZ37Gr0n;port=3306;database=hrms_leave;Convert Zero Datetime=True;Allow User Variables=true;Pooling=True;" }, "Jwt": { //"Key": "HP-FnQMUj9msHMSD3T9HtdEnphAKoCJLEl85CIqROFI", From aef81e9f4e7b8de198e30095c318b1aa2dac0f2c Mon Sep 17 00:00:00 2001 From: Suphonchai Phoonsawat Date: Wed, 25 Mar 2026 15:17:54 +0700 Subject: [PATCH 136/183] Add support for multiple child DNA IDs in leave processing and enhance batch creation of duty time changes --- .../Leaves/GenericLeaveRepository.cs | 18 ++++++++ .../Profiles/GetProfileByKeycloakIdRootDto.cs | 6 +++ BMA.EHR.Leave/Controllers/LeaveController.cs | 46 ++++++++++++------- .../DTOs/ChangeRound/CreateChangeRoundDto.cs | 21 +++++++++ .../ChangeRound/SearchProfileResultDto.cs | 6 +++ 5 files changed, 81 insertions(+), 16 deletions(-) diff --git a/BMA.EHR.Application/Repositories/Leaves/GenericLeaveRepository.cs b/BMA.EHR.Application/Repositories/Leaves/GenericLeaveRepository.cs index 5fbbb8a1..93ae9cec 100644 --- a/BMA.EHR.Application/Repositories/Leaves/GenericLeaveRepository.cs +++ b/BMA.EHR.Application/Repositories/Leaves/GenericLeaveRepository.cs @@ -68,6 +68,24 @@ namespace BMA.EHR.Application.Repositories.Leaves return entity; } + public virtual async Task> AddRangeAsync(List entities) + { + foreach (var entity in entities) + { + if (entity is EntityBase) + { + (entity as EntityBase).CreatedUserId = UserId ?? ""; + (entity as EntityBase).CreatedFullName = FullName ?? "System Administrator"; + (entity as EntityBase).CreatedAt = DateTime.Now; + } + } + + await _dbSet.AddRangeAsync(entities); + await _dbContext.SaveChangesAsync(); + + return entities; + } + public virtual async Task UpdateAsync(T entity) { if (entity is EntityBase) diff --git a/BMA.EHR.Application/Responses/Profiles/GetProfileByKeycloakIdRootDto.cs b/BMA.EHR.Application/Responses/Profiles/GetProfileByKeycloakIdRootDto.cs index 1110ca9e..2eff51dd 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 string? RootDnaId { get; set; } + public string? Child1DnaId { get; set; } + public string? Child2DnaId { get; set; } + public string? Child3DnaId { get; set; } + public string? Child4DnaId { get; set; } } public class GetProfileByKeycloakIdRootAddTotalDto diff --git a/BMA.EHR.Leave/Controllers/LeaveController.cs b/BMA.EHR.Leave/Controllers/LeaveController.cs index 23d38e3a..0adb3a3a 100644 --- a/BMA.EHR.Leave/Controllers/LeaveController.cs +++ b/BMA.EHR.Leave/Controllers/LeaveController.cs @@ -2487,7 +2487,16 @@ namespace BMA.EHR.Leave.Service.Controllers FullName = $"{p.Prefix ?? ""}{p.FirstName ?? ""} {p.LastName ?? ""}", StartTimeMorning = duty.StartTimeMorning, LeaveTimeAfterNoon = duty.EndTimeAfternoon, - EffectiveDate = effectiveDate?.EffectiveDate?.Date + EffectiveDate = effectiveDate?.EffectiveDate?.Date, + Prefix = p.Prefix ?? "", + FirstName = p.FirstName ?? "", + LastName = p.LastName ?? "", + RootDnaId = p.RootDnaId, + Child1DnaId = p.Child1DnaId, + Child2DnaId = p.Child2DnaId, + Child3DnaId = p.Child3DnaId, + Child4DnaId = p.Child4DnaId + }; resultSet.Add(res); } @@ -2558,7 +2567,7 @@ namespace BMA.EHR.Leave.Service.Controllers [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status401Unauthorized)] [ProducesResponseType(StatusCodes.Status500InternalServerError)] - public async Task> CreateChangeRoundMultipleAsync([FromBody] List reqs) + public async Task> CreateChangeRoundMultipleAsync([FromBody] List reqs) { var getPermission = await _permission.GetPermissionAPIAsync("UPDATE", "SYS_WORK_ROUND_EDIT"); var jsonData = JsonConvert.DeserializeObject(getPermission); @@ -2568,24 +2577,28 @@ namespace BMA.EHR.Leave.Service.Controllers } var currentDate = DateTime.Now.Date; + List dataList = new List(); + foreach(var req in reqs) { - var profile = await _userProfileRepository.GetProfileByProfileIdAsync(req.ProfileId, AccessToken); - if (profile == null) - { - return Error(GlobalMessages.DataNotFound, StatusCodes.Status404NotFound); - } + // var profile = await _userProfileRepository.GetProfileByProfileIdAsync(req.ProfileId, AccessToken); + // if (profile == null) + // { + // return Error(GlobalMessages.DataNotFound, StatusCodes.Status404NotFound); + // } if (req.EffectiveDate.Date < currentDate) { - return Error(new Exception($"กำหนดรอบลงเวลาของ {profile.FirstName} {profile.LastName} ผิดพลาด เนื่องจากวันที่มีผลต้องมากกว่าหรือเท่ากับวันที่ปัจจุบัน({currentDate.ToString("yyyy-MM-dd")})"), StatusCodes.Status400BadRequest); + continue; // move to next item if effective date is in the past, not return error + // return Error(new Exception($"กำหนดรอบลงเวลาของ {req.FirstName} {req.LastName} ผิดพลาด เนื่องจากวันที่มีผลต้องมากกว่าหรือเท่ากับวันที่ปัจจุบัน({currentDate.ToString("yyyy-MM-dd")})"), StatusCodes.Status400BadRequest); } var old = await _userDutyTimeRepository.GetExist(req.ProfileId, req.EffectiveDate); if (old != null) { - return Error(new Exception($"กำหนดรอบลงเวลาของ {profile.FirstName} {profile.LastName} ผิดพลาด เนื่องจากมีการกำหนดรอบการทำงานในวันที่นี้ไว้แล้ว"), StatusCodes.Status400BadRequest); + continue; // move to next item if already exist, not return error + //return Error(new Exception($"กำหนดรอบลงเวลาของ {req.FirstName} {req.LastName} ผิดพลาด เนื่องจากมีการกำหนดรอบการทำงานในวันที่นี้ไว้แล้ว"), StatusCodes.Status400BadRequest); } var data = new UserDutyTime @@ -2595,15 +2608,16 @@ namespace BMA.EHR.Leave.Service.Controllers EffectiveDate = req.EffectiveDate, Remark = req.Remark, - RootDnaId = profile.RootDnaId, - Child1DnaId = profile.Child1DnaId, - Child2DnaId = profile.Child2DnaId, - Child3DnaId = profile.Child3DnaId, - Child4DnaId = profile.Child4DnaId, + RootDnaId = req.RootDnaId, + Child1DnaId = req.Child1DnaId, + Child2DnaId = req.Child2DnaId, + Child3DnaId = req.Child3DnaId, + Child4DnaId = req.Child4DnaId, }; - - await _userDutyTimeRepository.AddAsync(data); + dataList.Add(data); } + + await _userDutyTimeRepository.AddRangeAsync(dataList); return Success(); } diff --git a/BMA.EHR.Leave/DTOs/ChangeRound/CreateChangeRoundDto.cs b/BMA.EHR.Leave/DTOs/ChangeRound/CreateChangeRoundDto.cs index 6ad96fcd..5379abda 100644 --- a/BMA.EHR.Leave/DTOs/ChangeRound/CreateChangeRoundDto.cs +++ b/BMA.EHR.Leave/DTOs/ChangeRound/CreateChangeRoundDto.cs @@ -12,4 +12,25 @@ namespace BMA.EHR.Leave.Service.DTOs.ChangeRound public string Remark { get; set; } } + + public class CreateChangeRoundMultipleDto + { + public Guid ProfileId { get; set; } + + public Guid RoundId { get; set; } + + public DateTime EffectiveDate { get; set; } + + public string Remark { get; set; } + + public Guid? RootDnaId { get; set; } + public Guid? Child1DnaId { get; set; } + public Guid? Child2DnaId { get; set; } + public Guid? Child3DnaId { get; set; } + public Guid? Child4DnaId { get; set; } + + public string? Prefix { get; set; } + public string? FirstName { get; set; } + public string? LastName { get; set; } + } } diff --git a/BMA.EHR.Leave/DTOs/ChangeRound/SearchProfileResultDto.cs b/BMA.EHR.Leave/DTOs/ChangeRound/SearchProfileResultDto.cs index 83b4d7b9..00df91a9 100644 --- a/BMA.EHR.Leave/DTOs/ChangeRound/SearchProfileResultDto.cs +++ b/BMA.EHR.Leave/DTOs/ChangeRound/SearchProfileResultDto.cs @@ -17,5 +17,11 @@ public string LeaveTimeAfterNoon { get;set; } public DateTime? EffectiveDate { get; set; } + + public string? RootDnaId { get; set; } + public string? Child1DnaId { get; set; } + public string? Child2DnaId { get; set; } + public string? Child3DnaId { get; set; } + public string? Child4DnaId { get; set; } } } From a09d5937f9f170d3b96d1346219b34c10d95e3cd Mon Sep 17 00:00:00 2001 From: Suphonchai Phoonsawat Date: Thu, 26 Mar 2026 10:33:00 +0700 Subject: [PATCH 137/183] Add leave subtype and couple day level country to leave approval response #2366 --- .../Repositories/Leaves/LeaveRequests/LeaveRequestRepository.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/BMA.EHR.Application/Repositories/Leaves/LeaveRequests/LeaveRequestRepository.cs b/BMA.EHR.Application/Repositories/Leaves/LeaveRequests/LeaveRequestRepository.cs index 2caecc9f..d01dbc55 100644 --- a/BMA.EHR.Application/Repositories/Leaves/LeaveRequests/LeaveRequestRepository.cs +++ b/BMA.EHR.Application/Repositories/Leaves/LeaveRequests/LeaveRequestRepository.cs @@ -1285,6 +1285,8 @@ namespace BMA.EHR.Application.Repositories.Leaves.LeaveRequests status = "approve", reason = rawData.LeaveDetail, leaveId = rawData.Id, + leaveSubTypeName = rawData.LeaveSubTypeName, + coupleDayLevelCountry = rawData.CoupleDayLevelCountry, }); // var _result = await _res.Content.ReadAsStringAsync(); } From 19b79a162d5cb2f02b182f62af696e5f564903b2 Mon Sep 17 00:00:00 2001 From: Suphonchai Phoonsawat Date: Thu, 26 Mar 2026 10:33:44 +0700 Subject: [PATCH 138/183] Add leave subtype name and couple day level country to leave approval response (Employee)#2366 --- .../Repositories/Leaves/LeaveRequests/LeaveRequestRepository.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/BMA.EHR.Application/Repositories/Leaves/LeaveRequests/LeaveRequestRepository.cs b/BMA.EHR.Application/Repositories/Leaves/LeaveRequests/LeaveRequestRepository.cs index d01dbc55..f465191e 100644 --- a/BMA.EHR.Application/Repositories/Leaves/LeaveRequests/LeaveRequestRepository.cs +++ b/BMA.EHR.Application/Repositories/Leaves/LeaveRequests/LeaveRequestRepository.cs @@ -1310,6 +1310,8 @@ namespace BMA.EHR.Application.Repositories.Leaves.LeaveRequests status = "approve", reason = rawData.LeaveDetail, leaveId = rawData.Id, + leaveSubTypeName = rawData.LeaveSubTypeName, + coupleDayLevelCountry = rawData.CoupleDayLevelCountry, }); } } From 3e3bfff7ba3d3d5c2159f7b275b196538c65b564 Mon Sep 17 00:00:00 2001 From: Suphonchai Phoonsawat Date: Thu, 26 Mar 2026 14:10:37 +0700 Subject: [PATCH 139/183] Refactor leave date overlap check in LeaveRequestController for improved readability and performance --- .../Controllers/LeaveRequestController.cs | 50 ++++++++++++------- 1 file changed, 31 insertions(+), 19 deletions(-) diff --git a/BMA.EHR.Leave/Controllers/LeaveRequestController.cs b/BMA.EHR.Leave/Controllers/LeaveRequestController.cs index 8d626d20..7bc3b151 100644 --- a/BMA.EHR.Leave/Controllers/LeaveRequestController.cs +++ b/BMA.EHR.Leave/Controllers/LeaveRequestController.cs @@ -1070,26 +1070,38 @@ namespace BMA.EHR.Leave.Service.Controllers fiscalYear = req.StartLeaveDate.Year + 1; var sumLeaveDay = await _leaveBeginningRepository.GetByYearAndTypeIdForUserAsync(fiscalYear, req.Type, userId); - - - var minLeave = (await _context.Set().Where(x => x.Type.Id == req.Type && - (x.LeaveStatus == "PENDING" || x.LeaveStatus == "APPROVE") && - x.KeycloakUserId == userId) - .OrderBy(x => x.LeaveStartDate) - .FirstOrDefaultAsync()); - - var maxLeave = (await _context.Set().Where(x => x.Type.Id == req.Type && - (x.LeaveStatus == "PENDING" || x.LeaveStatus == "APPROVE") && - x.KeycloakUserId == userId) - .OrderByDescending(x => x.LeaveEndDate) - .FirstOrDefaultAsync()); - var isBetween = false; - if (minLeave != null && maxLeave != null) - { - isBetween = (req.StartLeaveDate.Date >= minLeave.LeaveStartDate.Date && req.StartLeaveDate.Date <= maxLeave.LeaveEndDate.Date) || - (req.EndLeaveDate.Date >= minLeave.LeaveStartDate.Date && req.EndLeaveDate.Date <= maxLeave.LeaveEndDate.Date); - } + var existingLeaves = await _context.Set() + .Where(x => x.Type.Id == req.Type && + (x.LeaveStatus == "PENDING" || x.LeaveStatus == "APPROVE") && + x.KeycloakUserId == userId) + .ToListAsync(); + + isBetween = existingLeaves.Any(leave => + req.StartLeaveDate.Date <= leave.LeaveEndDate.Date && + req.EndLeaveDate.Date >= leave.LeaveStartDate.Date); + + // var minLeave = (await _context.Set().Where(x => x.Type.Id == req.Type && + // (x.LeaveStatus == "PENDING" || x.LeaveStatus == "APPROVE") && + // x.KeycloakUserId == userId) + // .OrderBy(x => x.LeaveStartDate) + // .FirstOrDefaultAsync()); + + // var maxLeave = (await _context.Set().Where(x => x.Type.Id == req.Type && + // (x.LeaveStatus == "PENDING" || x.LeaveStatus == "APPROVE") && + // x.KeycloakUserId == userId) + // .OrderByDescending(x => x.LeaveEndDate) + // .FirstOrDefaultAsync()); + + // var isBetween = false; + // if (minLeave != null && maxLeave != null) + // { + // // isBetween = (req.StartLeaveDate.Date >= minLeave.LeaveStartDate.Date && req.StartLeaveDate.Date <= maxLeave.LeaveEndDate.Date) || + // // (req.EndLeaveDate.Date >= minLeave.LeaveStartDate.Date && req.EndLeaveDate.Date <= maxLeave.LeaveEndDate.Date); + // isBetween = req.StartLeaveDate.Date <= maxLeave.LeaveEndDate.Date && + // req.EndLeaveDate.Date >= minLeave.LeaveStartDate.Date; + + // } var isLeave = false; var approveDay = sumLeaveDay == null ? 0.0 : sumLeaveDay.LeaveDaysUsed; From 7ba429bb64ca711ef27c4e148ab23452bdc457da Mon Sep 17 00:00:00 2001 From: Suphonchai Phoonsawat Date: Fri, 27 Mar 2026 09:48:10 +0700 Subject: [PATCH 140/183] Refactor checkout status logic in LeaveController for improved clarity and handling of check-in dates --- BMA.EHR.Leave/Controllers/LeaveController.cs | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/BMA.EHR.Leave/Controllers/LeaveController.cs b/BMA.EHR.Leave/Controllers/LeaveController.cs index 0adb3a3a..2f033adc 100644 --- a/BMA.EHR.Leave/Controllers/LeaveController.cs +++ b/BMA.EHR.Leave/Controllers/LeaveController.cs @@ -1412,11 +1412,20 @@ namespace BMA.EHR.Leave.Service.Controllers var leaveRange = leaveReq.LeaveRangeEnd == null ? "" : leaveReq.LeaveRangeEnd.ToUpper(); if (leaveRange == "AFTERNOON" || leaveRange == "ALL") { - if(DateTime.Parse(currentDate.ToString("yyyy-MM-dd HH:mm")) < - DateTime.Parse($"{currentDate.ToString("yyyy-MM-dd")} {endTimeMorning}")) - checkOutStatus = "ABSENT"; - else + if (checkout.CheckIn.Date < currentDate.Date) + { + // ถ้า check-out เป็นวันถัดไป สถานะปกติเสมอ checkOutStatus = "NORMAL"; + } + else + { + if(DateTime.Parse(currentDate.ToString("yyyy-MM-dd HH:mm")) < + DateTime.Parse($"{currentDate.ToString("yyyy-MM-dd")} {endTimeMorning}")) + checkOutStatus = "ABSENT"; + else + checkOutStatus = "NORMAL"; + } + } else { From d8f11267645d217398d6bb0b38f33d45cb1740f7 Mon Sep 17 00:00:00 2001 From: harid Date: Fri, 27 Mar 2026 14:31:42 +0700 Subject: [PATCH 141/183] =?UTF-8?q?fix=20=E0=B8=AA=E0=B9=88=E0=B8=87?= =?UTF-8?q?=E0=B8=A3=E0=B8=B2=E0=B8=A2=E0=B8=8A=E0=B8=B7=E0=B9=88=E0=B8=AD?= =?UTF-8?q?=E0=B8=9C=E0=B8=B9=E0=B9=89=E0=B8=96=E0=B8=B9=E0=B8=81=E0=B8=9E?= =?UTF-8?q?=E0=B8=B1=E0=B8=81=E0=B8=A3=E0=B8=B2=E0=B8=8A=E0=B8=81=E0=B8=B2?= =?UTF-8?q?=E0=B8=A3=E0=B9=84=E0=B8=9B=E0=B8=AD=E0=B8=AD=E0=B8=81=E0=B8=84?= =?UTF-8?q?=E0=B8=B3=E0=B8=AA=E0=B8=B1=E0=B9=88=E0=B8=87=20#2364?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../DisciplineSuspendController.cs | 244 +++++++++--------- 1 file changed, 125 insertions(+), 119 deletions(-) diff --git a/BMA.EHR.Discipline.Service/Controllers/DisciplineSuspendController.cs b/BMA.EHR.Discipline.Service/Controllers/DisciplineSuspendController.cs index 39b5f06a..3e171f9b 100644 --- a/BMA.EHR.Discipline.Service/Controllers/DisciplineSuspendController.cs +++ b/BMA.EHR.Discipline.Service/Controllers/DisciplineSuspendController.cs @@ -71,6 +71,13 @@ namespace BMA.EHR.DisciplineSuspend.Service.Controllers { return Error(jsonData["message"]?.ToString(), StatusCodes.Status403Forbidden); } + + // ถ้า FE ส่ง status = PENDING กรอง start/end suspend not null + bool isPending = + !string.IsNullOrEmpty(profileType) && + !string.IsNullOrEmpty(status) && + status.Trim().ToUpper() == "PENDING"; + // กรองสิทธิ์ string role = jsonData["result"]?.ToString() ?? ""; var nodeId = string.Empty; @@ -109,7 +116,7 @@ namespace BMA.EHR.DisciplineSuspend.Service.Controllers var data_search = (from x in _context.DisciplineReport_Profiles.Include(x => x.DisciplineDisciplinary) where ( - endDate != null && startDate != null? + endDate != null && startDate != null ? ( (x.StartDateSuspend.Value.Date >= startDate.Value.Date && x.StartDateSuspend.Value.Date <= endDate.Value.Date) || (x.EndDateSuspend.Value.Date >= startDate.Value.Date && x.EndDateSuspend.Value.Date <= endDate.Value.Date) || @@ -137,14 +144,19 @@ namespace BMA.EHR.DisciplineSuspend.Service.Controllers ( !string.IsNullOrEmpty(status) ? x.Status!.Trim().ToUpper() == status : true ) + // ถ้า FE ส่ง status = PENDING กรอง start/end suspend not null + && + ( + isPending + ? x.StartDateSuspend != null && x.EndDateSuspend != null + : true + ) && ( role == "OWNER" ? true : role == "ROOT" ? x.rootDnaId == nodeId - // : role == "PARENT" - // ? x.rootDnaId == nodeId && x.child1DnaId != null : role == "CHILD" ? ( profileAdmin.Node == 4 ? x.child4DnaId == nodeId : @@ -177,125 +189,119 @@ namespace BMA.EHR.DisciplineSuspend.Service.Controllers ) select x).ToList(); var query = data_search - .Select(x => new - { - Id = x.Id, - CitizenId = x.CitizenId, - Prefix = x.Prefix, - FirstName = x.FirstName, - LastName = x.LastName, - ProfileId = x.PersonId, - Organization = x.Organization, - root = x.root, - rootId = x.rootId, - rootDnaId = x.rootDnaId, - rootShortName = x.rootShortName, - child1 = x.child1, - child1Id = x.child1Id, - child1DnaId = x.child1DnaId, - child1ShortName = x.child1ShortName, - child2 = x.child2, - child2Id = x.child2Id, - child2DnaId = x.child2DnaId, - child2ShortName = x.child2ShortName, - child3 = x.child3, - child3Id = x.child3Id, - child3DnaId = x.child3DnaId, - child3ShortName = x.child3ShortName, - child4 = x.child4, - child4Id = x.child4Id, - child4DnaId = x.child4DnaId, - child4ShortName = x.child4ShortName, - posMasterNo = x.posMasterNo, - posTypeId = x.posTypeId, - posTypeName = x.posTypeName, - posLevelId = x.posLevelId, - posLevelName = x.posLevelName, + .Select(x => new + { + Id = x.Id, + CitizenId = x.CitizenId, + Prefix = x.Prefix, + FirstName = x.FirstName, + LastName = x.LastName, + ProfileId = x.PersonId, + Organization = x.Organization, + root = x.root, + rootId = x.rootId, + rootDnaId = x.rootDnaId, + rootShortName = x.rootShortName, + child1 = x.child1, + child1Id = x.child1Id, + child1DnaId = x.child1DnaId, + child1ShortName = x.child1ShortName, + child2 = x.child2, + child2Id = x.child2Id, + child2DnaId = x.child2DnaId, + child2ShortName = x.child2ShortName, + child3 = x.child3, + child3Id = x.child3Id, + child3DnaId = x.child3DnaId, + child3ShortName = x.child3ShortName, + child4 = x.child4, + child4Id = x.child4Id, + child4DnaId = x.child4DnaId, + child4ShortName = x.child4ShortName, + posMasterNo = x.posMasterNo, + posTypeId = x.posTypeId, + posTypeName = x.posTypeName, + posLevelId = x.posLevelId, + posLevelName = x.posLevelName, - Position = x.Position, - PosNo = x.PosNo, - PositionLevel = x.PositionLevel == null ? "" : x.PositionLevel, - PositionType = x.PositionType == null ? "" : x.PositionType, - Salary = x.Salary, - Status = x.Status, - DescriptionSuspend = x.DescriptionSuspend, - StartDateSuspend = x.StartDateSuspend, - EndDateSuspend = x.EndDateSuspend, - Title = x.DisciplineDisciplinary.Title, - OffenseDetails = x.DisciplineDisciplinary.OffenseDetails,//ลักษณะความผิด - DisciplinaryFaultLevel = x.DisciplineDisciplinary.DisciplinaryFaultLevel,//ระดับโทษความผิด - DisciplinaryCaseFault = x.DisciplineDisciplinary.DisciplinaryCaseFault,//กรณีความผิด - profileType = x.profileType, - CreatedAt = x.CreatedAt, - }); + Position = x.Position, + PosNo = x.PosNo, + PositionLevel = x.PositionLevel == null ? "" : x.PositionLevel, + PositionType = x.PositionType == null ? "" : x.PositionType, + Salary = x.Salary, + Status = x.Status, + DescriptionSuspend = x.DescriptionSuspend, + StartDateSuspend = x.StartDateSuspend, + EndDateSuspend = x.EndDateSuspend, + Title = x.DisciplineDisciplinary.Title, + OffenseDetails = x.DisciplineDisciplinary.OffenseDetails,//ลักษณะความผิด + DisciplinaryFaultLevel = x.DisciplineDisciplinary.DisciplinaryFaultLevel,//ระดับโทษความผิด + DisciplinaryCaseFault = x.DisciplineDisciplinary.DisciplinaryCaseFault,//กรณีความผิด + profileType = x.profileType, + CreatedAt = x.CreatedAt, + }); - bool desc = descending ?? false; - if (!string.IsNullOrEmpty(sortBy)) - { - if (sortBy == "title") - { - query = desc ? query.OrderByDescending(x => x.Title) - : query.OrderBy(x => x.Title); - } - else if (sortBy == "prefix" || sortBy == "firstName" || sortBy == "lastName") - { - query = desc ? - query - //.OrderByDescending(x => x.Prefix) - .OrderByDescending(x => x.FirstName) - .ThenByDescending(x => x.LastName) : - query - //.OrderBy(x => x.Prefix) - .OrderBy(x => x.FirstName) - .ThenBy(x => x.LastName); - } - else if (sortBy == "position") - { - query = desc ? query.OrderByDescending(x => x.Position) - : query.OrderBy(x => x.Position); - } - else if (sortBy == "positionType" || sortBy == "positionLevel") - { - query = desc ? - query - .OrderByDescending(x => x.PositionType) - .ThenByDescending(x => x.PositionLevel) : - query - .OrderBy(x => x.PositionType) - .ThenBy(x => x.PositionLevel); - } - else if (sortBy == "organization") - { - query = desc ? query.OrderByDescending(x => x.Organization) - : query.OrderBy(x => x.Organization); - } - else if (sortBy == "startDateSuspend") - { - query = desc ? query.OrderByDescending(x => x.StartDateSuspend) - : query.OrderBy(x => x.StartDateSuspend); - } - else if (sortBy == "endDateSuspend") - { - query = desc ? query.OrderByDescending(x => x.EndDateSuspend) - : query.OrderBy(x => x.EndDateSuspend); - } - else if (sortBy == "descriptionSuspend") - { - query = desc ? query.OrderByDescending(x => x.DescriptionSuspend) - : query.OrderBy(x => x.DescriptionSuspend); - } - else - { - query = query.OrderByDescending(x => x.profileType) - .ThenByDescending(x => x.CreatedAt) - .ThenByDescending(x => x.CitizenId); - } - } + bool desc = descending ?? false; + if (!string.IsNullOrEmpty(sortBy)) + { + if (sortBy == "title") + { + query = desc ? query.OrderByDescending(x => x.Title) + : query.OrderBy(x => x.Title); + } + else if (sortBy == "prefix" || sortBy == "firstName" || sortBy == "lastName") + { + query = desc ? + query.OrderByDescending(x => x.FirstName).ThenByDescending(x => x.LastName) : + query.OrderBy(x => x.FirstName).ThenBy(x => x.LastName); + } + else if (sortBy == "position") + { + query = desc ? query.OrderByDescending(x => x.Position) + : query.OrderBy(x => x.Position); + } + else if (sortBy == "positionType" || sortBy == "positionLevel") + { + query = desc ? + query + .OrderByDescending(x => x.PositionType) + .ThenByDescending(x => x.PositionLevel) : + query + .OrderBy(x => x.PositionType) + .ThenBy(x => x.PositionLevel); + } + else if (sortBy == "organization") + { + query = desc ? query.OrderByDescending(x => x.Organization) + : query.OrderBy(x => x.Organization); + } + else if (sortBy == "startDateSuspend") + { + query = desc ? query.OrderByDescending(x => x.StartDateSuspend) + : query.OrderBy(x => x.StartDateSuspend); + } + else if (sortBy == "endDateSuspend") + { + query = desc ? query.OrderByDescending(x => x.EndDateSuspend) + : query.OrderBy(x => x.EndDateSuspend); + } + else if (sortBy == "descriptionSuspend") + { + query = desc ? query.OrderByDescending(x => x.DescriptionSuspend) + : query.OrderBy(x => x.DescriptionSuspend); + } + else + { + query = query.OrderByDescending(x => x.profileType) + .ThenByDescending(x => x.CreatedAt) + .ThenByDescending(x => x.CitizenId); + } + } - var data = query - .Skip((page - 1) * pageSize) - .Take(pageSize) - .ToList(); + var data = query + .Skip((page - 1) * pageSize) + .Take(pageSize) + .ToList(); return Success(new { data, total = data_search.Count() }); } From c91e6c80307aba151522405ba3f5656778016259 Mon Sep 17 00:00:00 2001 From: Suphonchai Phoonsawat Date: Mon, 30 Mar 2026 09:23:13 +0700 Subject: [PATCH 142/183] Add migration for LeaveProcessJobStatuses table - Created a new migration to add the LeaveProcessJobStatuses table. - The table includes fields for job status, timestamps, user information, and error messages. - Supports tracking of leave process job statuses with relevant metadata. --- .../TimeAttendants/LeaveProcessJobStatus.cs | 37 + ...9_Add Leave Process Job Status.Designer.cs | 1802 +++++++++++++++++ ...0330020909_Add Leave Process Job Status.cs | 54 + .../LeaveDb/LeaveDbContextModelSnapshot.cs | 85 + .../Persistence/LeaveDbContext.cs | 2 + 5 files changed, 1980 insertions(+) create mode 100644 BMA.EHR.Domain/Models/Leave/TimeAttendants/LeaveProcessJobStatus.cs create mode 100644 BMA.EHR.Infrastructure/Migrations/LeaveDb/20260330020909_Add Leave Process Job Status.Designer.cs create mode 100644 BMA.EHR.Infrastructure/Migrations/LeaveDb/20260330020909_Add Leave Process Job Status.cs diff --git a/BMA.EHR.Domain/Models/Leave/TimeAttendants/LeaveProcessJobStatus.cs b/BMA.EHR.Domain/Models/Leave/TimeAttendants/LeaveProcessJobStatus.cs new file mode 100644 index 00000000..be986189 --- /dev/null +++ b/BMA.EHR.Domain/Models/Leave/TimeAttendants/LeaveProcessJobStatus.cs @@ -0,0 +1,37 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using System.Threading.Tasks; +using BMA.EHR.Domain.Models.Base; +using Microsoft.EntityFrameworkCore; + +namespace BMA.EHR.Domain.Models.Leave.TimeAttendants +{ + public class LeaveProcessJobStatus: EntityBase + { + [Required, Comment("วันเริ่มต้น")] + public DateTime StartDate { get; set; } + + [Required, Comment("วันสิ้นสุด")] + public DateTime EndDate { get; set; } + + [Required, Comment("รหัส Root DNA Id")] + public Guid RootDnaId { get; set; } = Guid.Empty; + + [Comment("วันเวลาที่สร้างงาน")] + public DateTime CreatedDate { get; set; } = DateTime.Now; + + [Comment("วันเวลาที่เริ่มประมวลผล")] + public DateTime? ProcessingDate { get; set; } + + [Comment("วันเวลาที่เสร็จสิ้นการประมวลผล")] + public DateTime? CompletedDate { get; set; } + + [Required, Comment("สถานะงาน: PENDING, PROCESSING, COMPLETED, FAILED")] + public string Status { get; set; } = "PENDING"; + + [Comment("ข้อความแสดงข้อผิดพลาด")] + public string? ErrorMessage { get; set; } + } +} \ No newline at end of file diff --git a/BMA.EHR.Infrastructure/Migrations/LeaveDb/20260330020909_Add Leave Process Job Status.Designer.cs b/BMA.EHR.Infrastructure/Migrations/LeaveDb/20260330020909_Add Leave Process Job Status.Designer.cs new file mode 100644 index 00000000..815f8188 --- /dev/null +++ b/BMA.EHR.Infrastructure/Migrations/LeaveDb/20260330020909_Add Leave Process Job Status.Designer.cs @@ -0,0 +1,1802 @@ +// +using System; +using BMA.EHR.Infrastructure.Persistence; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace BMA.EHR.Infrastructure.Migrations.LeaveDb +{ + [DbContext(typeof(LeaveDbContext))] + [Migration("20260330020909_Add Leave Process Job Status")] + partial class AddLeaveProcessJobStatus + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "7.0.9") + .HasAnnotation("Relational:MaxIdentifierLength", 64); + + modelBuilder.Entity("BMA.EHR.Domain.Models.Documents.Document", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CreatedDate") + .HasColumnType("datetime(6)"); + + b.Property("Detail") + .IsRequired() + .HasColumnType("text"); + + b.Property("FileName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("varchar(255)"); + + b.Property("FileSize") + .HasColumnType("int"); + + b.Property("FileType") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("varchar(128)"); + + b.Property("ObjectRefId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.ToTable("Document"); + }); + + modelBuilder.Entity("BMA.EHR.Domain.Models.Leave.Commons.LeaveType", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)") + .HasColumnOrder(0) + .HasComment("PrimaryKey") + .HasAnnotation("Relational:JsonPropertyName", "id"); + + b.Property("Code") + .IsRequired() + .HasColumnType("longtext") + .HasComment("รหัสประเภทการลา"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)") + .HasColumnOrder(100) + .HasComment("สร้างข้อมูลเมื่อ"); + + b.Property("CreatedFullName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)") + .HasColumnOrder(104) + .HasComment("ชื่อ User ที่สร้างข้อมูล"); + + b.Property("CreatedUserId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("varchar(40)") + .HasColumnOrder(101) + .HasComment("User Id ที่สร้างข้อมูล"); + + b.Property("LastUpdateFullName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)") + .HasColumnOrder(105) + .HasComment("ชื่อ User ที่แก้ไขข้อมูลล่าสุด"); + + b.Property("LastUpdateUserId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("varchar(40)") + .HasColumnOrder(103) + .HasComment("User Id ที่แก้ไขข้อมูลล่าสุด"); + + b.Property("LastUpdatedAt") + .HasColumnType("datetime(6)") + .HasColumnOrder(102) + .HasComment("แก้ไขข้อมูลล่าสุดเมื่อ"); + + b.Property("Limit") + .HasColumnType("int") + .HasComment("จำนวนวันลาสูงสุดประจำปี"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext") + .HasComment("ชื่อประเภทการลา"); + + b.HasKey("Id"); + + b.ToTable("LeaveTypes"); + }); + + modelBuilder.Entity("BMA.EHR.Domain.Models.Leave.Requests.LeaveBeginning", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)") + .HasColumnOrder(0) + .HasComment("PrimaryKey") + .HasAnnotation("Relational:JsonPropertyName", "id"); + + b.Property("BeginningLeaveCount") + .HasColumnType("int") + .HasComment("จำนวนครั้งที่ลายกมา"); + + b.Property("BeginningLeaveDays") + .HasColumnType("double") + .HasComment("จำนวนวันลายกมา"); + + b.Property("Child1DnaId") + .HasColumnType("char(36)"); + + b.Property("Child2DnaId") + .HasColumnType("char(36)"); + + b.Property("Child3DnaId") + .HasColumnType("char(36)"); + + b.Property("Child4DnaId") + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)") + .HasColumnOrder(100) + .HasComment("สร้างข้อมูลเมื่อ"); + + b.Property("CreatedFullName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)") + .HasColumnOrder(104) + .HasComment("ชื่อ User ที่สร้างข้อมูล"); + + b.Property("CreatedUserId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("varchar(40)") + .HasColumnOrder(101) + .HasComment("User Id ที่สร้างข้อมูล"); + + b.Property("FirstName") + .HasColumnType("longtext"); + + b.Property("LastName") + .HasColumnType("longtext"); + + b.Property("LastUpdateFullName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)") + .HasColumnOrder(105) + .HasComment("ชื่อ User ที่แก้ไขข้อมูลล่าสุด"); + + b.Property("LastUpdateUserId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("varchar(40)") + .HasColumnOrder(103) + .HasComment("User Id ที่แก้ไขข้อมูลล่าสุด"); + + b.Property("LastUpdatedAt") + .HasColumnType("datetime(6)") + .HasColumnOrder(102) + .HasComment("แก้ไขข้อมูลล่าสุดเมื่อ"); + + b.Property("LeaveCount") + .HasColumnType("int") + .HasComment("จำนวนครั้งที่ลาสะสม"); + + b.Property("LeaveDays") + .HasColumnType("double") + .HasComment("จำนวนวันลาทั้งหมด"); + + b.Property("LeaveDaysUsed") + .HasColumnType("double") + .HasComment("จำนวนวันลาที่ใช้ไป"); + + b.Property("LeaveTypeId") + .HasColumnType("char(36)") + .HasComment("รหัสประเภทการลา"); + + b.Property("LeaveYear") + .HasColumnType("int") + .HasComment("ปีงบประมาณ"); + + b.Property("Prefix") + .HasColumnType("longtext"); + + b.Property("ProfileId") + .HasColumnType("char(36)") + .HasComment("รหัส Profile ในระบบทะเบียนประวัติ"); + + b.Property("RootDnaId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("LeaveTypeId"); + + b.ToTable("LeaveBeginnings"); + }); + + modelBuilder.Entity("BMA.EHR.Domain.Models.Leave.Requests.LeaveDocument", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)") + .HasColumnOrder(0) + .HasComment("PrimaryKey") + .HasAnnotation("Relational:JsonPropertyName", "id"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)") + .HasColumnOrder(100) + .HasComment("สร้างข้อมูลเมื่อ"); + + b.Property("CreatedFullName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)") + .HasColumnOrder(104) + .HasComment("ชื่อ User ที่สร้างข้อมูล"); + + b.Property("CreatedUserId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("varchar(40)") + .HasColumnOrder(101) + .HasComment("User Id ที่สร้างข้อมูล"); + + b.Property("DocumentId") + .HasColumnType("char(36)"); + + b.Property("LastUpdateFullName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)") + .HasColumnOrder(105) + .HasComment("ชื่อ User ที่แก้ไขข้อมูลล่าสุด"); + + b.Property("LastUpdateUserId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("varchar(40)") + .HasColumnOrder(103) + .HasComment("User Id ที่แก้ไขข้อมูลล่าสุด"); + + b.Property("LastUpdatedAt") + .HasColumnType("datetime(6)") + .HasColumnOrder(102) + .HasComment("แก้ไขข้อมูลล่าสุดเมื่อ"); + + b.Property("LeaveRequestId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("DocumentId"); + + b.HasIndex("LeaveRequestId"); + + b.ToTable("LeaveDocuments"); + }); + + modelBuilder.Entity("BMA.EHR.Domain.Models.Leave.Requests.LeaveRequest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)") + .HasColumnOrder(0) + .HasComment("PrimaryKey") + .HasAnnotation("Relational:JsonPropertyName", "id"); + + b.Property("AbsentDayAt") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("AbsentDayGetIn") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("AbsentDayLocation") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("AbsentDayRegistorDate") + .HasColumnType("datetime(6)"); + + b.Property("AbsentDaySummon") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Amount") + .HasColumnType("double"); + + b.Property("ApproveStep") + .HasColumnType("longtext") + .HasComment("step การอนุมัติ st1 = จทน.อนุมัตื,st2 = ผู้บังคับบัญชา อนุมัติ "); + + b.Property("BirthDate") + .HasColumnType("datetime(6)"); + + b.Property("CancelLeaveWrote") + .HasColumnType("longtext") + .HasComment("เขียนที่ (ขอยกเลิก)"); + + b.Property("Child1") + .HasColumnType("longtext"); + + b.Property("Child1DnaId") + .HasColumnType("char(36)"); + + b.Property("Child1Id") + .HasColumnType("char(36)"); + + b.Property("Child2") + .HasColumnType("longtext"); + + b.Property("Child2DnaId") + .HasColumnType("char(36)"); + + b.Property("Child2Id") + .HasColumnType("char(36)"); + + b.Property("Child3") + .HasColumnType("longtext"); + + b.Property("Child3DnaId") + .HasColumnType("char(36)"); + + b.Property("Child3Id") + .HasColumnType("char(36)"); + + b.Property("Child4") + .HasColumnType("longtext"); + + b.Property("Child4DnaId") + .HasColumnType("char(36)"); + + b.Property("Child4Id") + .HasColumnType("char(36)"); + + b.Property("CitizenId") + .HasColumnType("longtext"); + + b.Property("CommanderPosition") + .HasColumnType("longtext"); + + b.Property("CoupleDayCountryHistory") + .HasColumnType("longtext"); + + b.Property("CoupleDayEndDateHistory") + .HasColumnType("datetime(6)"); + + b.Property("CoupleDayLevel") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("CoupleDayLevelCountry") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("CoupleDayName") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("CoupleDayPosition") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("CoupleDayStartDateHistory") + .HasColumnType("datetime(6)"); + + b.Property("CoupleDaySumTotalHistory") + .HasColumnType("longtext"); + + b.Property("CoupleDayTotalHistory") + .HasColumnType("longtext"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)") + .HasColumnOrder(100) + .HasComment("สร้างข้อมูลเมื่อ"); + + b.Property("CreatedFullName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)") + .HasColumnOrder(104) + .HasComment("ชื่อ User ที่สร้างข้อมูล"); + + b.Property("CreatedUserId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("varchar(40)") + .HasColumnOrder(101) + .HasComment("User Id ที่สร้างข้อมูล"); + + b.Property("DateAppoint") + .HasColumnType("datetime(6)"); + + b.Property("Dear") + .HasColumnType("longtext") + .HasComment("เรียนใคร"); + + b.Property("FirstName") + .HasColumnType("longtext"); + + b.Property("Gender") + .HasColumnType("longtext"); + + b.Property("HajjDayStatus") + .HasColumnType("tinyint(1)"); + + b.Property("KeycloakUserId") + .HasColumnType("char(36)"); + + b.Property("LastName") + .HasColumnType("longtext"); + + b.Property("LastUpdateFullName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)") + .HasColumnOrder(105) + .HasComment("ชื่อ User ที่แก้ไขข้อมูลล่าสุด"); + + b.Property("LastUpdateUserId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("varchar(40)") + .HasColumnOrder(103) + .HasComment("User Id ที่แก้ไขข้อมูลล่าสุด"); + + b.Property("LastUpdatedAt") + .HasColumnType("datetime(6)") + .HasColumnOrder(102) + .HasComment("แก้ไขข้อมูลล่าสุดเมื่อ"); + + b.Property("LeaveAddress") + .IsRequired() + .HasColumnType("longtext") + .HasComment("สถานที่ติดต่อขณะลา"); + + b.Property("LeaveBirthDate") + .HasColumnType("datetime(6)"); + + b.Property("LeaveCancelComment") + .HasColumnType("longtext") + .HasComment("เหตุผลในการขอยกเลิก"); + + b.Property("LeaveCancelDocumentId") + .HasColumnType("char(36)"); + + b.Property("LeaveCancelStatus") + .HasColumnType("longtext") + .HasComment("สถานะของคำขอยกเลิก"); + + b.Property("LeaveComment") + .HasColumnType("longtext") + .HasComment("ความเห็นของผู้บังคับบัญชา"); + + b.Property("LeaveDetail") + .IsRequired() + .HasColumnType("longtext") + .HasComment("รายละเอียดการลา"); + + b.Property("LeaveDirectorComment") + .HasColumnType("longtext") + .HasComment("ความเห็นของผู้อำนวยการสำนัก"); + + b.Property("LeaveDraftDocumentId") + .HasColumnType("char(36)"); + + b.Property("LeaveEndDate") + .HasColumnType("datetime(6)") + .HasComment("วัน เดือน ปีสิ้นสุดลา"); + + b.Property("LeaveGovernmentDate") + .HasColumnType("datetime(6)"); + + b.Property("LeaveLast") + .HasColumnType("datetime(6)"); + + b.Property("LeaveNumber") + .IsRequired() + .HasColumnType("longtext") + .HasComment("หมายเลขที่ติดต่อขณะลา"); + + b.Property("LeaveRange") + .HasColumnType("longtext") + .HasComment("ช่วงของการลาของวันเริ่ม เช่น ลาทั้งวัน ครึ่งวันเช้า ครึ่งวันบ่าย"); + + b.Property("LeaveRangeEnd") + .HasColumnType("longtext") + .HasComment("ช่วงของการลาของวันสิ้นสุด เช่น ลาทั้งวัน ครึ่งวันเช้า ครึ่งวันบ่าย"); + + b.Property("LeaveSalary") + .HasColumnType("int"); + + b.Property("LeaveSalaryText") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("LeaveStartDate") + .HasColumnType("datetime(6)") + .HasComment("วัน เดือน ปีเริ่มต้นลา"); + + b.Property("LeaveStatus") + .IsRequired() + .HasColumnType("longtext") + .HasComment("สถานะของคำร้อง"); + + b.Property("LeaveSubTypeName") + .HasColumnType("longtext"); + + b.Property("LeaveTotal") + .HasColumnType("double"); + + b.Property("LeaveTypeCode") + .HasColumnType("longtext") + .HasComment("code ของประเภทการลา"); + + b.Property("LeaveWrote") + .IsRequired() + .HasColumnType("longtext") + .HasComment("เขียนที่"); + + b.Property("OrdainDayBuddhistLentAddress") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("OrdainDayBuddhistLentName") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("OrdainDayLocationAddress") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("OrdainDayLocationName") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("OrdainDayLocationNumber") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("OrdainDayOrdination") + .HasColumnType("datetime(6)"); + + b.Property("OrdainDayStatus") + .HasColumnType("tinyint(1)"); + + b.Property("OrganizationName") + .HasColumnType("longtext") + .HasComment("สังกัดผู้ยื่นขอ"); + + b.Property("PositionLevelName") + .HasColumnType("longtext") + .HasComment("ระดับผู้ยื่นขอ"); + + b.Property("PositionName") + .HasColumnType("longtext") + .HasComment("ตำแหน่งผู้ยื่นขอ"); + + b.Property("Prefix") + .HasColumnType("longtext"); + + b.Property("ProfileId") + .HasColumnType("char(36)"); + + b.Property("ProfileType") + .HasColumnType("longtext"); + + b.Property("RestDayCurrentTotal") + .HasColumnType("double"); + + b.Property("RestDayOldTotal") + .HasColumnType("double"); + + b.Property("Root") + .HasColumnType("longtext"); + + b.Property("RootDnaId") + .HasColumnType("char(36)"); + + b.Property("RootId") + .HasColumnType("char(36)"); + + b.Property("StudyDayCountry") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("StudyDayDegreeLevel") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("StudyDayScholarship") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("StudyDaySubject") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("StudyDayTrainingName") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("StudyDayTrainingSubject") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("StudyDayUniversityName") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("TypeId") + .HasColumnType("char(36)"); + + b.Property("WifeDayDateBorn") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("WifeDayName") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("LeaveCancelDocumentId"); + + b.HasIndex("LeaveDraftDocumentId"); + + b.HasIndex("TypeId"); + + b.ToTable("LeaveRequests"); + }); + + modelBuilder.Entity("BMA.EHR.Domain.Models.Leave.Requests.LeaveRequestApprover", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)") + .HasColumnOrder(0) + .HasComment("PrimaryKey") + .HasAnnotation("Relational:JsonPropertyName", "id"); + + b.Property("ApproveStatus") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("ApproveType") + .HasColumnType("longtext"); + + b.Property("Comment") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)") + .HasColumnOrder(100) + .HasComment("สร้างข้อมูลเมื่อ"); + + b.Property("CreatedFullName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)") + .HasColumnOrder(104) + .HasComment("ชื่อ User ที่สร้างข้อมูล"); + + b.Property("CreatedUserId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("varchar(40)") + .HasColumnOrder(101) + .HasComment("User Id ที่สร้างข้อมูล"); + + b.Property("FirstName") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("KeycloakId") + .HasColumnType("char(36)"); + + b.Property("LastName") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("LastUpdateFullName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)") + .HasColumnOrder(105) + .HasComment("ชื่อ User ที่แก้ไขข้อมูลล่าสุด"); + + b.Property("LastUpdateUserId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("varchar(40)") + .HasColumnOrder(103) + .HasComment("User Id ที่แก้ไขข้อมูลล่าสุด"); + + b.Property("LastUpdatedAt") + .HasColumnType("datetime(6)") + .HasColumnOrder(102) + .HasComment("แก้ไขข้อมูลล่าสุดเมื่อ"); + + b.Property("LeaveRequestId") + .HasColumnType("char(36)"); + + b.Property("OrganizationName") + .IsRequired() + .HasColumnType("longtext") + .HasComment("สังกัด"); + + b.Property("PosExecutiveName") + .IsRequired() + .HasColumnType("longtext") + .HasComment("ตำแหน่งทางการบริหาร"); + + b.Property("PositionLevelName") + .IsRequired() + .HasColumnType("longtext") + .HasComment("ประเภทระดับตำแหน่ง"); + + b.Property("PositionName") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("PositionSign") + .HasColumnType("longtext") + .HasComment("ตำแหน่งใต้ลายเช็นต์"); + + b.Property("Prefix") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("ProfileId") + .HasColumnType("char(36)"); + + b.Property("Seq") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("LeaveRequestId"); + + b.ToTable("LeaveRequestApprovers"); + }); + + modelBuilder.Entity("BMA.EHR.Domain.Models.Leave.TimeAttendants.AdditionalCheckRequest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)") + .HasColumnOrder(0) + .HasComment("PrimaryKey") + .HasAnnotation("Relational:JsonPropertyName", "id"); + + b.Property("CheckDate") + .HasColumnType("datetime(6)") + .HasComment("*วันที่ลงเวลา"); + + b.Property("CheckInEdit") + .HasColumnType("tinyint(1)") + .HasComment("*ขอลงเวลาช่วงเช้า"); + + b.Property("CheckOutEdit") + .HasColumnType("tinyint(1)") + .HasComment("*ขอลงเวลาช่วงบ่าย"); + + b.Property("Child1DnaId") + .HasColumnType("char(36)"); + + b.Property("Child2DnaId") + .HasColumnType("char(36)"); + + b.Property("Child3DnaId") + .HasColumnType("char(36)"); + + b.Property("Child4DnaId") + .HasColumnType("char(36)"); + + b.Property("Comment") + .HasColumnType("longtext") + .HasComment("หมายเหตุในการการอนุมัติ/ไม่อนุมัติ"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)") + .HasColumnOrder(100) + .HasComment("สร้างข้อมูลเมื่อ"); + + b.Property("CreatedFullName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)") + .HasColumnOrder(104) + .HasComment("ชื่อ User ที่สร้างข้อมูล"); + + b.Property("CreatedUserId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("varchar(40)") + .HasColumnOrder(101) + .HasComment("User Id ที่สร้างข้อมูล"); + + b.Property("Description") + .IsRequired() + .HasColumnType("longtext") + .HasComment("*หมายเหตุขอลงเวลาพิเศษ"); + + b.Property("FirstName") + .HasColumnType("longtext"); + + b.Property("KeycloakUserId") + .HasColumnType("char(36)") + .HasComment("รหัส User ของ Keycloak ที่ร้องขอ"); + + b.Property("LastName") + .HasColumnType("longtext"); + + b.Property("LastUpdateFullName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)") + .HasColumnOrder(105) + .HasComment("ชื่อ User ที่แก้ไขข้อมูลล่าสุด"); + + b.Property("LastUpdateUserId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("varchar(40)") + .HasColumnOrder(103) + .HasComment("User Id ที่แก้ไขข้อมูลล่าสุด"); + + b.Property("LastUpdatedAt") + .HasColumnType("datetime(6)") + .HasColumnOrder(102) + .HasComment("แก้ไขข้อมูลล่าสุดเมื่อ"); + + b.Property("Latitude") + .HasColumnType("double"); + + b.Property("Longitude") + .HasColumnType("double"); + + b.Property("POI") + .HasColumnType("longtext"); + + b.Property("Prefix") + .HasColumnType("longtext"); + + b.Property("RootDnaId") + .HasColumnType("char(36)"); + + b.Property("Status") + .IsRequired() + .HasColumnType("longtext") + .HasComment("สถานะการอนุมัติ"); + + b.HasKey("Id"); + + b.ToTable("AdditionalCheckRequests"); + }); + + modelBuilder.Entity("BMA.EHR.Domain.Models.Leave.TimeAttendants.CheckInJobStatus", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)") + .HasColumnOrder(0) + .HasComment("PrimaryKey") + .HasAnnotation("Relational:JsonPropertyName", "id"); + + b.Property("AdditionalData") + .HasColumnType("longtext") + .HasComment("ข้อมูลเพิ่มเติม (JSON)"); + + b.Property("CheckInId") + .HasColumnType("char(36)") + .HasComment("CheckInId สำหรับ Check-Out"); + + b.Property("CheckType") + .HasColumnType("longtext") + .HasComment("ประเภทการลงเวลา: CHECK_IN, CHECK_OUT"); + + b.Property("CompletedDate") + .HasColumnType("datetime(6)") + .HasComment("วันเวลาที่เสร็จสิ้นการประมวลผล"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)") + .HasColumnOrder(100) + .HasComment("สร้างข้อมูลเมื่อ"); + + b.Property("CreatedDate") + .HasColumnType("datetime(6)") + .HasComment("วันเวลาที่สร้างงาน"); + + b.Property("CreatedFullName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)") + .HasColumnOrder(104) + .HasComment("ชื่อ User ที่สร้างข้อมูล"); + + b.Property("CreatedUserId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("varchar(40)") + .HasColumnOrder(101) + .HasComment("User Id ที่สร้างข้อมูล"); + + b.Property("ErrorMessage") + .HasColumnType("longtext") + .HasComment("ข้อความแสดงข้อผิดพลาด"); + + b.Property("KeycloakUserId") + .HasColumnType("char(36)") + .HasComment("รหัส User ของ Keycloak"); + + b.Property("LastUpdateFullName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)") + .HasColumnOrder(105) + .HasComment("ชื่อ User ที่แก้ไขข้อมูลล่าสุด"); + + b.Property("LastUpdateUserId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("varchar(40)") + .HasColumnOrder(103) + .HasComment("User Id ที่แก้ไขข้อมูลล่าสุด"); + + b.Property("LastUpdatedAt") + .HasColumnType("datetime(6)") + .HasColumnOrder(102) + .HasComment("แก้ไขข้อมูลล่าสุดเมื่อ"); + + b.Property("ProcessingDate") + .HasColumnType("datetime(6)") + .HasComment("วันเวลาที่เริ่มประมวลผล"); + + b.Property("Status") + .IsRequired() + .HasColumnType("longtext") + .HasComment("สถานะงาน: PENDING, PROCESSING, COMPLETED, FAILED"); + + b.Property("TaskId") + .HasColumnType("char(36)") + .HasComment("Task ID สำหรับติดตามสถานะงาน"); + + b.HasKey("Id"); + + b.ToTable("CheckInJobStatuses"); + }); + + modelBuilder.Entity("BMA.EHR.Domain.Models.Leave.TimeAttendants.DutyTime", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)") + .HasColumnOrder(0) + .HasComment("PrimaryKey") + .HasAnnotation("Relational:JsonPropertyName", "id"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)") + .HasColumnOrder(100) + .HasComment("สร้างข้อมูลเมื่อ"); + + b.Property("CreatedFullName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)") + .HasColumnOrder(104) + .HasComment("ชื่อ User ที่สร้างข้อมูล"); + + b.Property("CreatedUserId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("varchar(40)") + .HasColumnOrder(101) + .HasComment("User Id ที่สร้างข้อมูล"); + + b.Property("Description") + .IsRequired() + .HasColumnType("longtext") + .HasComment("คำอธิบาย"); + + b.Property("EndTimeAfternoon") + .IsRequired() + .HasColumnType("longtext") + .HasComment("เวลาออกงานช่วงบ่าย"); + + b.Property("EndTimeMorning") + .IsRequired() + .HasColumnType("longtext") + .HasComment("เวลาออกงานช่วงเช้า"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)") + .HasComment("สถานะการเปิดใช้งาน (เปิด/ปิด)"); + + b.Property("IsDefault") + .HasColumnType("tinyint(1)") + .HasComment("สถานะว่ารอบใดเป็นค่า Default ของข้าราชการ (สำหรับทุกคนที่ยังไม่ได้ทำการเลือกรอบ)"); + + b.Property("LastUpdateFullName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)") + .HasColumnOrder(105) + .HasComment("ชื่อ User ที่แก้ไขข้อมูลล่าสุด"); + + b.Property("LastUpdateUserId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("varchar(40)") + .HasColumnOrder(103) + .HasComment("User Id ที่แก้ไขข้อมูลล่าสุด"); + + b.Property("LastUpdatedAt") + .HasColumnType("datetime(6)") + .HasColumnOrder(102) + .HasComment("แก้ไขข้อมูลล่าสุดเมื่อ"); + + b.Property("StartTimeAfternoon") + .IsRequired() + .HasColumnType("longtext") + .HasComment("เวลาเข้างานช่วงบ่าย"); + + b.Property("StartTimeMorning") + .IsRequired() + .HasColumnType("longtext") + .HasComment("เวลาเข้างานช่วงเช้า"); + + b.HasKey("Id"); + + b.ToTable("DutyTimes"); + }); + + modelBuilder.Entity("BMA.EHR.Domain.Models.Leave.TimeAttendants.LeaveProcessJobStatus", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)") + .HasColumnOrder(0) + .HasComment("PrimaryKey") + .HasAnnotation("Relational:JsonPropertyName", "id"); + + b.Property("CompletedDate") + .HasColumnType("datetime(6)") + .HasComment("วันเวลาที่เสร็จสิ้นการประมวลผล"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)") + .HasColumnOrder(100) + .HasComment("สร้างข้อมูลเมื่อ"); + + b.Property("CreatedDate") + .HasColumnType("datetime(6)") + .HasComment("วันเวลาที่สร้างงาน"); + + b.Property("CreatedFullName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)") + .HasColumnOrder(104) + .HasComment("ชื่อ User ที่สร้างข้อมูล"); + + b.Property("CreatedUserId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("varchar(40)") + .HasColumnOrder(101) + .HasComment("User Id ที่สร้างข้อมูล"); + + b.Property("EndDate") + .HasColumnType("datetime(6)") + .HasComment("วันสิ้นสุด"); + + b.Property("ErrorMessage") + .HasColumnType("longtext") + .HasComment("ข้อความแสดงข้อผิดพลาด"); + + b.Property("LastUpdateFullName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)") + .HasColumnOrder(105) + .HasComment("ชื่อ User ที่แก้ไขข้อมูลล่าสุด"); + + b.Property("LastUpdateUserId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("varchar(40)") + .HasColumnOrder(103) + .HasComment("User Id ที่แก้ไขข้อมูลล่าสุด"); + + b.Property("LastUpdatedAt") + .HasColumnType("datetime(6)") + .HasColumnOrder(102) + .HasComment("แก้ไขข้อมูลล่าสุดเมื่อ"); + + b.Property("ProcessingDate") + .HasColumnType("datetime(6)") + .HasComment("วันเวลาที่เริ่มประมวลผล"); + + b.Property("RootDnaId") + .HasColumnType("char(36)") + .HasComment("รหัส Root DNA Id"); + + b.Property("StartDate") + .HasColumnType("datetime(6)") + .HasComment("วันเริ่มต้น"); + + b.Property("Status") + .IsRequired() + .HasColumnType("longtext") + .HasComment("สถานะงาน: PENDING, PROCESSING, COMPLETED, FAILED"); + + b.HasKey("Id"); + + b.ToTable("LeaveProcessJobStatuses"); + }); + + modelBuilder.Entity("BMA.EHR.Domain.Models.Leave.TimeAttendants.ProcessUserTimeStamp", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)") + .HasColumnOrder(0) + .HasComment("PrimaryKey") + .HasAnnotation("Relational:JsonPropertyName", "id"); + + b.Property("CheckIn") + .HasColumnType("datetime(6)") + .HasComment("วัน เวลา เข้างาน"); + + b.Property("CheckInImageUrl") + .IsRequired() + .HasColumnType("longtext") + .HasComment("รูปถ่ายสถานที่ Check-In"); + + b.Property("CheckInLat") + .HasColumnType("double") + .HasComment("พิกัดละติจูด Check-In"); + + b.Property("CheckInLocationName") + .HasColumnType("longtext") + .HasComment("กรณีเลือกนอกสถานที่ตั้ง ต้องระบุข้อมูลชื่อสถานะที่ Check-In"); + + b.Property("CheckInLon") + .HasColumnType("double") + .HasComment("พิกัดลองจิจูด Check-In"); + + b.Property("CheckInPOI") + .IsRequired() + .HasColumnType("longtext") + .HasComment("ชื่อสถานที่ ได้มาจากระบบ ArcGis ของกองสารสนเทศภูมิศาสตร์ Check-In"); + + b.Property("CheckInRemark") + .HasColumnType("longtext") + .HasComment("ข้อความหมายเหตุที่ต้องการระบุเพิ่ม(มีเผื่อไว้อาจไม่ได้ใช้) Check-In"); + + b.Property("CheckInStatus") + .IsRequired() + .HasColumnType("longtext") + .HasComment("สถานะ Check-In"); + + b.Property("CheckOut") + .HasColumnType("datetime(6)") + .HasComment("วัน เวลา ออกงาน"); + + b.Property("CheckOutImageUrl") + .IsRequired() + .HasColumnType("longtext") + .HasComment("รูปถ่ายสถานที่ Check-Out"); + + b.Property("CheckOutLat") + .HasColumnType("double") + .HasComment("พิกัดละติจูด Check-Out"); + + b.Property("CheckOutLocationName") + .HasColumnType("longtext") + .HasComment("กรณีเลือกนอกสถานที่ตั้ง ต้องระบุข้อมูลชื่อสถานะที่ Check-Out"); + + b.Property("CheckOutLon") + .HasColumnType("double") + .HasComment("พิกัดลองจิจูด Check-Out"); + + b.Property("CheckOutPOI") + .IsRequired() + .HasColumnType("longtext") + .HasComment("ชื่อสถานที่ ได้มาจากระบบ ArcGis ของกองสารสนเทศภูมิศาสตร์ Check-Out"); + + b.Property("CheckOutRemark") + .HasColumnType("longtext") + .HasComment("ข้อความหมายเหตุที่ต้องการระบุเพิ่ม(มีเผื่อไว้อาจไม่ได้ใช้) Check-Out"); + + b.Property("CheckOutStatus") + .HasColumnType("longtext") + .HasComment("สถานะ Check-Out"); + + b.Property("Child1") + .HasColumnType("longtext"); + + b.Property("Child1DnaId") + .HasColumnType("char(36)"); + + b.Property("Child1Id") + .HasColumnType("char(36)"); + + b.Property("Child2") + .HasColumnType("longtext"); + + b.Property("Child2DnaId") + .HasColumnType("char(36)"); + + b.Property("Child2Id") + .HasColumnType("char(36)"); + + b.Property("Child3") + .HasColumnType("longtext"); + + b.Property("Child3DnaId") + .HasColumnType("char(36)"); + + b.Property("Child3Id") + .HasColumnType("char(36)"); + + b.Property("Child4") + .HasColumnType("longtext"); + + b.Property("Child4DnaId") + .HasColumnType("char(36)"); + + b.Property("Child4Id") + .HasColumnType("char(36)"); + + b.Property("CitizenId") + .HasColumnType("longtext"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)") + .HasColumnOrder(100) + .HasComment("สร้างข้อมูลเมื่อ"); + + b.Property("CreatedFullName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)") + .HasColumnOrder(104) + .HasComment("ชื่อ User ที่สร้างข้อมูล"); + + b.Property("CreatedUserId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("varchar(40)") + .HasColumnOrder(101) + .HasComment("User Id ที่สร้างข้อมูล"); + + b.Property("EditReason") + .HasColumnType("longtext") + .HasComment("เหตุผลการอนุมัติ/ไม่อนุมัติขอลงเวลาพิเศษ"); + + b.Property("EditStatus") + .HasColumnType("longtext") + .HasComment("สถานะการของลงเวลาพิเศษ"); + + b.Property("FirstName") + .HasColumnType("longtext"); + + b.Property("Gender") + .HasColumnType("longtext"); + + b.Property("IsLocationCheckIn") + .HasColumnType("tinyint(1)") + .HasComment("true คือ ณ สถานที่ตั้ง, false คือ นอกสถานที่ตั้ง Check-In"); + + b.Property("IsLocationCheckOut") + .HasColumnType("tinyint(1)") + .HasComment("true คือ ณ สถานที่ตั้ง, false คือ นอกสถานที่ตั้ง Check-Out"); + + b.Property("IsProcess") + .HasColumnType("tinyint(1)") + .HasComment("นำไปประมวลผลแล้วหรือยัง"); + + b.Property("KeycloakUserId") + .HasColumnType("char(36)") + .HasComment("รหัส User ของ Keycloak"); + + b.Property("LastName") + .HasColumnType("longtext"); + + b.Property("LastUpdateFullName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)") + .HasColumnOrder(105) + .HasComment("ชื่อ User ที่แก้ไขข้อมูลล่าสุด"); + + b.Property("LastUpdateUserId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("varchar(40)") + .HasColumnOrder(103) + .HasComment("User Id ที่แก้ไขข้อมูลล่าสุด"); + + b.Property("LastUpdatedAt") + .HasColumnType("datetime(6)") + .HasColumnOrder(102) + .HasComment("แก้ไขข้อมูลล่าสุดเมื่อ"); + + b.Property("Prefix") + .HasColumnType("longtext"); + + b.Property("ProfileId") + .HasColumnType("char(36)"); + + b.Property("ProfileType") + .HasColumnType("longtext"); + + b.Property("Root") + .HasColumnType("longtext"); + + b.Property("RootDnaId") + .HasColumnType("char(36)"); + + b.Property("RootId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.ToTable("ProcessUserTimeStamps"); + }); + + modelBuilder.Entity("BMA.EHR.Domain.Models.Leave.TimeAttendants.UserCalendar", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)") + .HasColumnOrder(0) + .HasComment("PrimaryKey") + .HasAnnotation("Relational:JsonPropertyName", "id"); + + b.Property("Calendar") + .IsRequired() + .HasColumnType("longtext") + .HasComment("ปฏิทินการทำงานของ ขรก ปกติ หรือ 6 วันต่อสัปดาห์"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)") + .HasColumnOrder(100) + .HasComment("สร้างข้อมูลเมื่อ"); + + b.Property("CreatedFullName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)") + .HasColumnOrder(104) + .HasComment("ชื่อ User ที่สร้างข้อมูล"); + + b.Property("CreatedUserId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("varchar(40)") + .HasColumnOrder(101) + .HasComment("User Id ที่สร้างข้อมูล"); + + b.Property("LastUpdateFullName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)") + .HasColumnOrder(105) + .HasComment("ชื่อ User ที่แก้ไขข้อมูลล่าสุด"); + + b.Property("LastUpdateUserId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("varchar(40)") + .HasColumnOrder(103) + .HasComment("User Id ที่แก้ไขข้อมูลล่าสุด"); + + b.Property("LastUpdatedAt") + .HasColumnType("datetime(6)") + .HasColumnOrder(102) + .HasComment("แก้ไขข้อมูลล่าสุดเมื่อ"); + + b.Property("ProfileId") + .HasColumnType("char(36)") + .HasComment("รหัส Profile ในระบบทะเบียนประวัติ"); + + b.HasKey("Id"); + + b.ToTable("UserCalendars"); + }); + + modelBuilder.Entity("BMA.EHR.Domain.Models.Leave.TimeAttendants.UserDutyTime", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)") + .HasColumnOrder(0) + .HasComment("PrimaryKey") + .HasAnnotation("Relational:JsonPropertyName", "id"); + + b.Property("Child1DnaId") + .HasColumnType("char(36)"); + + b.Property("Child2DnaId") + .HasColumnType("char(36)"); + + b.Property("Child3DnaId") + .HasColumnType("char(36)"); + + b.Property("Child4DnaId") + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)") + .HasColumnOrder(100) + .HasComment("สร้างข้อมูลเมื่อ"); + + b.Property("CreatedFullName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)") + .HasColumnOrder(104) + .HasComment("ชื่อ User ที่สร้างข้อมูล"); + + b.Property("CreatedUserId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("varchar(40)") + .HasColumnOrder(101) + .HasComment("User Id ที่สร้างข้อมูล"); + + b.Property("DutyTimeId") + .HasColumnType("char(36)") + .HasComment("รหัสรอบการลงเวลา"); + + b.Property("EffectiveDate") + .HasColumnType("datetime(6)") + .HasComment("วันที่มีผล"); + + b.Property("IsProcess") + .HasColumnType("tinyint(1)") + .HasComment("ทำการประมวลผลแล้วหรือยัง"); + + b.Property("LastUpdateFullName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)") + .HasColumnOrder(105) + .HasComment("ชื่อ User ที่แก้ไขข้อมูลล่าสุด"); + + b.Property("LastUpdateUserId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("varchar(40)") + .HasColumnOrder(103) + .HasComment("User Id ที่แก้ไขข้อมูลล่าสุด"); + + b.Property("LastUpdatedAt") + .HasColumnType("datetime(6)") + .HasColumnOrder(102) + .HasComment("แก้ไขข้อมูลล่าสุดเมื่อ"); + + b.Property("ProfileId") + .HasColumnType("char(36)") + .HasComment("รหัส Profile ในระบบทะเบียนประวัติ"); + + b.Property("Remark") + .HasColumnType("longtext") + .HasComment("หมายเหตุ"); + + b.Property("RootDnaId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("DutyTimeId"); + + b.ToTable("UserDutyTimes"); + }); + + modelBuilder.Entity("BMA.EHR.Domain.Models.Leave.TimeAttendants.UserTimeStamp", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)") + .HasColumnOrder(0) + .HasComment("PrimaryKey") + .HasAnnotation("Relational:JsonPropertyName", "id"); + + b.Property("CheckIn") + .HasColumnType("datetime(6)") + .HasComment("วัน เวลา เข้างาน"); + + b.Property("CheckInImageUrl") + .IsRequired() + .HasColumnType("longtext") + .HasComment("รูปถ่ายสถานที่ Check-In"); + + b.Property("CheckInLat") + .HasColumnType("double") + .HasComment("พิกัดละติจูด Check-In"); + + b.Property("CheckInLocationName") + .HasColumnType("longtext") + .HasComment("กรณีเลือกนอกสถานที่ตั้ง ต้องระบุข้อมูลชื่อสถานะที่ Check-In"); + + b.Property("CheckInLon") + .HasColumnType("double") + .HasComment("พิกัดลองจิจูด Check-In"); + + b.Property("CheckInPOI") + .IsRequired() + .HasColumnType("longtext") + .HasComment("ชื่อสถานที่ ได้มาจากระบบ ArcGis ของกองสารสนเทศภูมิศาสตร์ Check-In"); + + b.Property("CheckInRemark") + .HasColumnType("longtext") + .HasComment("ข้อความหมายเหตุที่ต้องการระบุเพิ่ม(มีเผื่อไว้อาจไม่ได้ใช้) Check-In"); + + b.Property("CheckOut") + .HasColumnType("datetime(6)") + .HasComment("วัน เวลา ออกงาน"); + + b.Property("CheckOutImageUrl") + .IsRequired() + .HasColumnType("longtext") + .HasComment("รูปถ่ายสถานที่ Check-Out"); + + b.Property("CheckOutLat") + .HasColumnType("double") + .HasComment("พิกัดละติจูด Check-Out"); + + b.Property("CheckOutLocationName") + .HasColumnType("longtext") + .HasComment("กรณีเลือกนอกสถานที่ตั้ง ต้องระบุข้อมูลชื่อสถานะที่ Check-Out"); + + b.Property("CheckOutLon") + .HasColumnType("double") + .HasComment("พิกัดลองจิจูด Check-Out"); + + b.Property("CheckOutPOI") + .IsRequired() + .HasColumnType("longtext") + .HasComment("ชื่อสถานที่ ได้มาจากระบบ ArcGis ของกองสารสนเทศภูมิศาสตร์ Check-Out"); + + b.Property("CheckOutRemark") + .HasColumnType("longtext") + .HasComment("ข้อความหมายเหตุที่ต้องการระบุเพิ่ม(มีเผื่อไว้อาจไม่ได้ใช้) Check-Out"); + + b.Property("Child1") + .HasColumnType("longtext"); + + b.Property("Child1DnaId") + .HasColumnType("char(36)"); + + b.Property("Child1Id") + .HasColumnType("char(36)"); + + b.Property("Child2") + .HasColumnType("longtext"); + + b.Property("Child2DnaId") + .HasColumnType("char(36)"); + + b.Property("Child2Id") + .HasColumnType("char(36)"); + + b.Property("Child3") + .HasColumnType("longtext"); + + b.Property("Child3DnaId") + .HasColumnType("char(36)"); + + b.Property("Child3Id") + .HasColumnType("char(36)"); + + b.Property("Child4") + .HasColumnType("longtext"); + + b.Property("Child4DnaId") + .HasColumnType("char(36)"); + + b.Property("Child4Id") + .HasColumnType("char(36)"); + + b.Property("CitizenId") + .HasColumnType("longtext"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)") + .HasColumnOrder(100) + .HasComment("สร้างข้อมูลเมื่อ"); + + b.Property("CreatedFullName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)") + .HasColumnOrder(104) + .HasComment("ชื่อ User ที่สร้างข้อมูล"); + + b.Property("CreatedUserId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("varchar(40)") + .HasColumnOrder(101) + .HasComment("User Id ที่สร้างข้อมูล"); + + b.Property("FirstName") + .HasColumnType("longtext"); + + b.Property("Gender") + .HasColumnType("longtext"); + + b.Property("IsLocationCheckIn") + .HasColumnType("tinyint(1)") + .HasComment("true คือ ณ สถานที่ตั้ง, false คือ นอกสถานที่ตั้ง Check-In"); + + b.Property("IsLocationCheckOut") + .HasColumnType("tinyint(1)") + .HasComment("true คือ ณ สถานที่ตั้ง, false คือ นอกสถานที่ตั้ง Check-Out"); + + b.Property("IsProcess") + .HasColumnType("tinyint(1)") + .HasComment("นำไปประมวลผลแล้วหรือยัง"); + + b.Property("KeycloakUserId") + .HasColumnType("char(36)") + .HasComment("รหัส User ของ Keycloak"); + + b.Property("LastName") + .HasColumnType("longtext"); + + b.Property("LastUpdateFullName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)") + .HasColumnOrder(105) + .HasComment("ชื่อ User ที่แก้ไขข้อมูลล่าสุด"); + + b.Property("LastUpdateUserId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("varchar(40)") + .HasColumnOrder(103) + .HasComment("User Id ที่แก้ไขข้อมูลล่าสุด"); + + b.Property("LastUpdatedAt") + .HasColumnType("datetime(6)") + .HasColumnOrder(102) + .HasComment("แก้ไขข้อมูลล่าสุดเมื่อ"); + + b.Property("Prefix") + .HasColumnType("longtext"); + + b.Property("ProfileId") + .HasColumnType("char(36)"); + + b.Property("ProfileType") + .HasColumnType("longtext"); + + b.Property("Root") + .HasColumnType("longtext"); + + b.Property("RootDnaId") + .HasColumnType("char(36)"); + + b.Property("RootId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.ToTable("UserTimeStamps"); + }); + + modelBuilder.Entity("BMA.EHR.Domain.Models.Leave.Requests.LeaveBeginning", b => + { + b.HasOne("BMA.EHR.Domain.Models.Leave.Commons.LeaveType", "LeaveType") + .WithMany() + .HasForeignKey("LeaveTypeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("LeaveType"); + }); + + modelBuilder.Entity("BMA.EHR.Domain.Models.Leave.Requests.LeaveDocument", b => + { + b.HasOne("BMA.EHR.Domain.Models.Documents.Document", "Document") + .WithMany() + .HasForeignKey("DocumentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("BMA.EHR.Domain.Models.Leave.Requests.LeaveRequest", "LeaveRequest") + .WithMany("LeaveDocument") + .HasForeignKey("LeaveRequestId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Document"); + + b.Navigation("LeaveRequest"); + }); + + modelBuilder.Entity("BMA.EHR.Domain.Models.Leave.Requests.LeaveRequest", b => + { + b.HasOne("BMA.EHR.Domain.Models.Documents.Document", "LeaveCancelDocument") + .WithMany() + .HasForeignKey("LeaveCancelDocumentId"); + + b.HasOne("BMA.EHR.Domain.Models.Documents.Document", "LeaveDraftDocument") + .WithMany() + .HasForeignKey("LeaveDraftDocumentId"); + + b.HasOne("BMA.EHR.Domain.Models.Leave.Commons.LeaveType", "Type") + .WithMany() + .HasForeignKey("TypeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("LeaveCancelDocument"); + + b.Navigation("LeaveDraftDocument"); + + b.Navigation("Type"); + }); + + modelBuilder.Entity("BMA.EHR.Domain.Models.Leave.Requests.LeaveRequestApprover", b => + { + b.HasOne("BMA.EHR.Domain.Models.Leave.Requests.LeaveRequest", "LeaveRequest") + .WithMany("Approvers") + .HasForeignKey("LeaveRequestId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("LeaveRequest"); + }); + + modelBuilder.Entity("BMA.EHR.Domain.Models.Leave.TimeAttendants.UserDutyTime", b => + { + b.HasOne("BMA.EHR.Domain.Models.Leave.TimeAttendants.DutyTime", "DutyTime") + .WithMany() + .HasForeignKey("DutyTimeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("DutyTime"); + }); + + modelBuilder.Entity("BMA.EHR.Domain.Models.Leave.Requests.LeaveRequest", b => + { + b.Navigation("Approvers"); + + b.Navigation("LeaveDocument"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/BMA.EHR.Infrastructure/Migrations/LeaveDb/20260330020909_Add Leave Process Job Status.cs b/BMA.EHR.Infrastructure/Migrations/LeaveDb/20260330020909_Add Leave Process Job Status.cs new file mode 100644 index 00000000..1567dc5e --- /dev/null +++ b/BMA.EHR.Infrastructure/Migrations/LeaveDb/20260330020909_Add Leave Process Job Status.cs @@ -0,0 +1,54 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace BMA.EHR.Infrastructure.Migrations.LeaveDb +{ + /// + public partial class AddLeaveProcessJobStatus : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "LeaveProcessJobStatuses", + columns: table => new + { + Id = table.Column(type: "char(36)", nullable: false, comment: "PrimaryKey", collation: "ascii_general_ci"), + CreatedAt = table.Column(type: "datetime(6)", nullable: false, comment: "สร้างข้อมูลเมื่อ"), + CreatedUserId = table.Column(type: "varchar(40)", maxLength: 40, nullable: false, comment: "User Id ที่สร้างข้อมูล") + .Annotation("MySql:CharSet", "utf8mb4"), + LastUpdatedAt = table.Column(type: "datetime(6)", nullable: true, comment: "แก้ไขข้อมูลล่าสุดเมื่อ"), + LastUpdateUserId = table.Column(type: "varchar(40)", maxLength: 40, nullable: false, comment: "User Id ที่แก้ไขข้อมูลล่าสุด") + .Annotation("MySql:CharSet", "utf8mb4"), + CreatedFullName = table.Column(type: "varchar(200)", maxLength: 200, nullable: false, comment: "ชื่อ User ที่สร้างข้อมูล") + .Annotation("MySql:CharSet", "utf8mb4"), + LastUpdateFullName = table.Column(type: "varchar(200)", maxLength: 200, nullable: false, comment: "ชื่อ User ที่แก้ไขข้อมูลล่าสุด") + .Annotation("MySql:CharSet", "utf8mb4"), + StartDate = table.Column(type: "datetime(6)", nullable: false, comment: "วันเริ่มต้น"), + EndDate = table.Column(type: "datetime(6)", nullable: false, comment: "วันสิ้นสุด"), + RootDnaId = table.Column(type: "char(36)", nullable: false, comment: "รหัส Root DNA Id", collation: "ascii_general_ci"), + CreatedDate = table.Column(type: "datetime(6)", nullable: false, comment: "วันเวลาที่สร้างงาน"), + ProcessingDate = table.Column(type: "datetime(6)", nullable: true, comment: "วันเวลาที่เริ่มประมวลผล"), + CompletedDate = table.Column(type: "datetime(6)", nullable: true, comment: "วันเวลาที่เสร็จสิ้นการประมวลผล"), + Status = table.Column(type: "longtext", nullable: false, comment: "สถานะงาน: PENDING, PROCESSING, COMPLETED, FAILED") + .Annotation("MySql:CharSet", "utf8mb4"), + ErrorMessage = table.Column(type: "longtext", nullable: true, comment: "ข้อความแสดงข้อผิดพลาด") + .Annotation("MySql:CharSet", "utf8mb4") + }, + constraints: table => + { + table.PrimaryKey("PK_LeaveProcessJobStatuses", x => x.Id); + }) + .Annotation("MySql:CharSet", "utf8mb4"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "LeaveProcessJobStatuses"); + } + } +} diff --git a/BMA.EHR.Infrastructure/Migrations/LeaveDb/LeaveDbContextModelSnapshot.cs b/BMA.EHR.Infrastructure/Migrations/LeaveDb/LeaveDbContextModelSnapshot.cs index 510601d8..4e384f55 100644 --- a/BMA.EHR.Infrastructure/Migrations/LeaveDb/LeaveDbContextModelSnapshot.cs +++ b/BMA.EHR.Infrastructure/Migrations/LeaveDb/LeaveDbContextModelSnapshot.cs @@ -1072,6 +1072,91 @@ namespace BMA.EHR.Infrastructure.Migrations.LeaveDb b.ToTable("DutyTimes"); }); + modelBuilder.Entity("BMA.EHR.Domain.Models.Leave.TimeAttendants.LeaveProcessJobStatus", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)") + .HasColumnOrder(0) + .HasComment("PrimaryKey") + .HasAnnotation("Relational:JsonPropertyName", "id"); + + b.Property("CompletedDate") + .HasColumnType("datetime(6)") + .HasComment("วันเวลาที่เสร็จสิ้นการประมวลผล"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)") + .HasColumnOrder(100) + .HasComment("สร้างข้อมูลเมื่อ"); + + b.Property("CreatedDate") + .HasColumnType("datetime(6)") + .HasComment("วันเวลาที่สร้างงาน"); + + b.Property("CreatedFullName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)") + .HasColumnOrder(104) + .HasComment("ชื่อ User ที่สร้างข้อมูล"); + + b.Property("CreatedUserId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("varchar(40)") + .HasColumnOrder(101) + .HasComment("User Id ที่สร้างข้อมูล"); + + b.Property("EndDate") + .HasColumnType("datetime(6)") + .HasComment("วันสิ้นสุด"); + + b.Property("ErrorMessage") + .HasColumnType("longtext") + .HasComment("ข้อความแสดงข้อผิดพลาด"); + + b.Property("LastUpdateFullName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)") + .HasColumnOrder(105) + .HasComment("ชื่อ User ที่แก้ไขข้อมูลล่าสุด"); + + b.Property("LastUpdateUserId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("varchar(40)") + .HasColumnOrder(103) + .HasComment("User Id ที่แก้ไขข้อมูลล่าสุด"); + + b.Property("LastUpdatedAt") + .HasColumnType("datetime(6)") + .HasColumnOrder(102) + .HasComment("แก้ไขข้อมูลล่าสุดเมื่อ"); + + b.Property("ProcessingDate") + .HasColumnType("datetime(6)") + .HasComment("วันเวลาที่เริ่มประมวลผล"); + + b.Property("RootDnaId") + .HasColumnType("char(36)") + .HasComment("รหัส Root DNA Id"); + + b.Property("StartDate") + .HasColumnType("datetime(6)") + .HasComment("วันเริ่มต้น"); + + b.Property("Status") + .IsRequired() + .HasColumnType("longtext") + .HasComment("สถานะงาน: PENDING, PROCESSING, COMPLETED, FAILED"); + + b.HasKey("Id"); + + b.ToTable("LeaveProcessJobStatuses"); + }); + modelBuilder.Entity("BMA.EHR.Domain.Models.Leave.TimeAttendants.ProcessUserTimeStamp", b => { b.Property("Id") diff --git a/BMA.EHR.Infrastructure/Persistence/LeaveDbContext.cs b/BMA.EHR.Infrastructure/Persistence/LeaveDbContext.cs index 19848c6b..10064197 100644 --- a/BMA.EHR.Infrastructure/Persistence/LeaveDbContext.cs +++ b/BMA.EHR.Infrastructure/Persistence/LeaveDbContext.cs @@ -40,6 +40,8 @@ namespace BMA.EHR.Infrastructure.Persistence #endregion + public DbSet LeaveProcessJobStatuses { get; set; } + public LeaveDbContext(DbContextOptions options) : base(options) { From de1773880b671153983a3e137faa11fcbee0d6d6 Mon Sep 17 00:00:00 2001 From: Suphonchai Phoonsawat Date: Mon, 30 Mar 2026 09:31:19 +0700 Subject: [PATCH 143/183] Add LeaveProcessJobStatusRepository and register it in ApplicationServicesRegistration --- .../ApplicationServicesRegistration.cs | 2 + .../LeaveProcessJobStatusRepository.cs | 119 ++++++++++++++++++ 2 files changed, 121 insertions(+) create mode 100644 BMA.EHR.Application/Repositories/Leaves/TimeAttendants/LeaveProcessJobStatusRepository.cs diff --git a/BMA.EHR.Application/ApplicationServicesRegistration.cs b/BMA.EHR.Application/ApplicationServicesRegistration.cs index bf6dc6df..0b99a7b6 100644 --- a/BMA.EHR.Application/ApplicationServicesRegistration.cs +++ b/BMA.EHR.Application/ApplicationServicesRegistration.cs @@ -61,6 +61,8 @@ namespace BMA.EHR.Application services.AddTransient(); + services.AddTransient(); + return services; } diff --git a/BMA.EHR.Application/Repositories/Leaves/TimeAttendants/LeaveProcessJobStatusRepository.cs b/BMA.EHR.Application/Repositories/Leaves/TimeAttendants/LeaveProcessJobStatusRepository.cs new file mode 100644 index 00000000..114087b5 --- /dev/null +++ b/BMA.EHR.Application/Repositories/Leaves/TimeAttendants/LeaveProcessJobStatusRepository.cs @@ -0,0 +1,119 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using BMA.EHR.Application.Common.Interfaces; +using BMA.EHR.Domain.Models.Leave.TimeAttendants; +using Microsoft.AspNetCore.Http; +using Microsoft.EntityFrameworkCore; + +namespace BMA.EHR.Application.Repositories.Leaves.TimeAttendants +{ + public class LeaveProcessJobStatusRepository: GenericLeaveRepository + { + #region " Fields " + + private readonly ILeaveDbContext _dbContext; + + #endregion + + #region " Constructor and Destructor " + + public LeaveProcessJobStatusRepository(ILeaveDbContext dbContext, + IHttpContextAccessor httpContextAccessor) : base(dbContext, httpContextAccessor) + { + _dbContext = dbContext; + } + + #endregion + + #region " Methods " + + /// + /// ดึงข้อมูล Job Status จาก TaskId + /// + public async Task GetByTaskIdAsync(Guid id) + { + var data = await _dbContext.Set() + .Where(x => x.Id == id) + .FirstOrDefaultAsync(); + + return data; + } + + /// + /// ดึงข้อมูล Job Status จาก UserId และสถานะ + /// + public async Task> GetByUserIdAndStatusAsync(Guid userId, string status) + { + var data = await _dbContext.Set() + .Where(x => x.CreatedUserId == userId.ToString("D") && x.Status == status) + .OrderByDescending(x => x.CreatedDate) + .ToListAsync(); + + return data; + } + + /// + /// ดึงข้อมูล Job Status ที่ยัง pending หรือ processing + /// + public async Task> GetPendingOrProcessingJobsAsync(Guid userId) + { + var data = await _dbContext.Set() + .Where(x => x.CreatedUserId == userId.ToString("D") && + (x.Status == "PENDING" || x.Status == "PROCESSING")) + //.OrderByDescending(x => x.CreatedDate) + .ToListAsync(); + + return data; + } + + /// + /// อัปเดตสถานะเป็น Processing + /// + public async Task UpdateToProcessingAsync(Guid id) + { + var job = await GetByTaskIdAsync(id); + if (job != null) + { + job.Status = "PROCESSING"; + job.ProcessingDate = DateTime.Now; + await UpdateAsync(job); + } + return job!; + } + + /// + /// อัปเดตสถานะเป็น Completed + /// + public async Task UpdateToCompletedAsync(Guid id, string? additionalData = null) + { + var job = await GetByTaskIdAsync(id); + if (job != null) + { + job.Status = "COMPLETED"; + job.CompletedDate = DateTime.Now; + await UpdateAsync(job); + } + return job!; + } + + /// + /// อัปเดตสถานะเป็น Failed + /// + public async Task UpdateToFailedAsync(Guid id, string errorMessage) + { + var job = await GetByTaskIdAsync(id); + if (job != null) + { + job.Status = "FAILED"; + job.CompletedDate = DateTime.Now; + job.ErrorMessage = errorMessage; + await UpdateAsync(job); + } + return job!; + } + + #endregion + } +} \ No newline at end of file From c1ac687101162d641b3db3ce5ebd09337e263072 Mon Sep 17 00:00:00 2001 From: Suphonchai Phoonsawat Date: Mon, 30 Mar 2026 09:52:27 +0700 Subject: [PATCH 144/183] Add CreateLeaveProcessJobDto and implement CreateProcessTaskAsync in LeaveController --- BMA.EHR.Leave/Controllers/LeaveController.cs | 50 ++++++++++++++++++- .../LeaveRequest/CreateLeaveProcessJobDto.cs | 23 +++++++++ 2 files changed, 71 insertions(+), 2 deletions(-) create mode 100644 BMA.EHR.Leave/DTOs/LeaveRequest/CreateLeaveProcessJobDto.cs diff --git a/BMA.EHR.Leave/Controllers/LeaveController.cs b/BMA.EHR.Leave/Controllers/LeaveController.cs index 2f033adc..930c4043 100644 --- a/BMA.EHR.Leave/Controllers/LeaveController.cs +++ b/BMA.EHR.Leave/Controllers/LeaveController.cs @@ -74,6 +74,8 @@ namespace BMA.EHR.Leave.Service.Controllers private readonly HttpClient _httpClient; + private readonly LeaveProcessJobStatusRepository _leaveProcessJobStatusRepository; + #endregion #region " Constuctor and Destructor " @@ -97,7 +99,8 @@ namespace BMA.EHR.Leave.Service.Controllers NotificationRepository notificationRepository, CheckInJobStatusRepository checkInJobStatusRepository, HttpClient httpClient, - ApplicationDBContext appDbContext) + ApplicationDBContext appDbContext, + LeaveProcessJobStatusRepository leaveProcessJobStatusRepository) { _dutyTimeRepository = dutyTimeRepository; _context = context; @@ -116,7 +119,7 @@ namespace BMA.EHR.Leave.Service.Controllers _leaveRequestRepository = leaveRequestRepository; _notificationRepository = notificationRepository; _checkInJobStatusRepository = checkInJobStatusRepository; - + _leaveProcessJobStatusRepository = leaveProcessJobStatusRepository; _objectPool = objectPool; _permission = permission; @@ -4156,6 +4159,49 @@ namespace BMA.EHR.Leave.Service.Controllers #endregion + #region " Process - Leave and Absence " + + + /// + /// สร้าง Task สำหรับ Process ข้อมูลวันลาและขาดราชการ (ADMIN) + /// + /// + /// + /// เมื่อทำรายการสำเร็จ + /// ไม่ได้ Login เข้าระบบ + /// เมื่อเกิดข้อผิดพลาดในการทำงาน + [HttpPost("admin/leave-task/process")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status401Unauthorized)] + [ProducesResponseType(StatusCodes.Status500InternalServerError)] + public async Task> CreateProcessTaskAsync([FromBody] CreateLeaveProcessJobDto req) + { + var userId = UserId == null ? Guid.Empty : Guid.Parse(UserId); + + var profile = await _userProfileRepository.GetProfileByKeycloakIdNewAsync(userId, AccessToken); + if (profile == null) + { + return Error(GlobalMessages.DataNotFound, StatusCodes.Status404NotFound); + } + + var task = new LeaveProcessJobStatus + { + RootDnaId = profile.RootDnaId ?? Guid.Empty, + CreatedUserId = profile.Keycloak?.ToString("D") ?? "", + CreatedFullName = profile.FirstName + " " + profile.LastName, + CreatedAt = DateTime.Now, + Status = "PENDING", + StartDate = req.StartDate, + EndDate = req.EndDate + }; + + await _leaveProcessJobStatusRepository.AddAsync(task); + + return Success(); + } + + #endregion + #endregion } diff --git a/BMA.EHR.Leave/DTOs/LeaveRequest/CreateLeaveProcessJobDto.cs b/BMA.EHR.Leave/DTOs/LeaveRequest/CreateLeaveProcessJobDto.cs new file mode 100644 index 00000000..ae4aacc4 --- /dev/null +++ b/BMA.EHR.Leave/DTOs/LeaveRequest/CreateLeaveProcessJobDto.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace BMA.EHR.Leave.Service.DTOs.LeaveRequest +{ + /// + /// ข้อมูลสำหรับสร้าง Job ประมวลผลวันลา โดยมีช่วงวันที่เริ่มต้นและสิ้นสุดของการประมวลผลวันลา + /// + public class CreateLeaveProcessJobDto + { + /// + /// วันที่เริ่มต้นของการประมวลผลวันลา + /// + public DateTime StartDate { get; set; } + + /// + /// วันที่สิ้นสุดของการประมวลผลวันลา + /// + public DateTime EndDate { get; set; } + } +} \ No newline at end of file From 91e6b1b35bdb8529e1d3b713fbaa385fc99fee3c Mon Sep 17 00:00:00 2001 From: Suphonchai Phoonsawat Date: Mon, 30 Mar 2026 10:02:57 +0700 Subject: [PATCH 145/183] Add methods to process pending jobs and update their statuses in LeaveProcessJobStatusRepository --- .../LeaveProcessJobStatusRepository.cs | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/BMA.EHR.Application/Repositories/Leaves/TimeAttendants/LeaveProcessJobStatusRepository.cs b/BMA.EHR.Application/Repositories/Leaves/TimeAttendants/LeaveProcessJobStatusRepository.cs index 114087b5..02670144 100644 --- a/BMA.EHR.Application/Repositories/Leaves/TimeAttendants/LeaveProcessJobStatusRepository.cs +++ b/BMA.EHR.Application/Repositories/Leaves/TimeAttendants/LeaveProcessJobStatusRepository.cs @@ -68,6 +68,15 @@ namespace BMA.EHR.Application.Repositories.Leaves.TimeAttendants return data; } + public async Task> GetPendingOrProcessingJobsAsync() + { + var data = await _dbContext.Set() + .Where(x => x.Status == "PENDING" || x.Status == "PROCESSING") + .ToListAsync(); + + return data; + } + /// /// อัปเดตสถานะเป็น Processing /// @@ -113,6 +122,41 @@ namespace BMA.EHR.Application.Repositories.Leaves.TimeAttendants } return job!; } + + public async Task ProcessTaskAsync(Guid id, CancellationToken cancellationToken = default) + { + + } + + public async Task ProcessPendingJobsAsync(CancellationToken cancellationToken = default) + { + // กำหนด timeout เป็น 60 นาที + using var timeoutCts = new CancellationTokenSource(TimeSpan.FromMinutes(60)); + using var combinedCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, timeoutCts.Token); + + + var pendingJobs = await GetPendingOrProcessingJobsAsync(); + + foreach (var job in pendingJobs) + { + try + { + // อัปเดตสถานะเป็น Processing + await UpdateToProcessingAsync(job.Id); + + // ทำงานที่ต้องการที่นี่ (เช่น เรียก API, ประมวลผลข้อมูล ฯลฯ) + await ProcessTaskAsync(job.RootDnaId, combinedCts.Token); + + // อัปเดตสถานะเป็น Completed + await UpdateToCompletedAsync(job.Id); + } + catch (Exception ex) + { + // หากเกิดข้อผิดพลาด อัปเดตสถานะเป็น Failed พร้อมข้อความแสดงข้อผิดพลาด + await UpdateToFailedAsync(job.Id, ex.Message); + } + } + } #endregion } From 8732c345649c6c8a4d18a864f3c763b8407871a8 Mon Sep 17 00:00:00 2001 From: Suphonchai Phoonsawat Date: Mon, 30 Mar 2026 10:06:21 +0700 Subject: [PATCH 146/183] Add scheduled job to process pending jobs in LeaveProcessJobStatusRepository --- .../TimeAttendants/LeaveProcessJobStatusRepository.cs | 8 +++----- BMA.EHR.Leave/Program.cs | 2 ++ 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/BMA.EHR.Application/Repositories/Leaves/TimeAttendants/LeaveProcessJobStatusRepository.cs b/BMA.EHR.Application/Repositories/Leaves/TimeAttendants/LeaveProcessJobStatusRepository.cs index 02670144..87940757 100644 --- a/BMA.EHR.Application/Repositories/Leaves/TimeAttendants/LeaveProcessJobStatusRepository.cs +++ b/BMA.EHR.Application/Repositories/Leaves/TimeAttendants/LeaveProcessJobStatusRepository.cs @@ -128,11 +128,9 @@ namespace BMA.EHR.Application.Repositories.Leaves.TimeAttendants } - public async Task ProcessPendingJobsAsync(CancellationToken cancellationToken = default) + public async Task ProcessPendingJobsAsync() { - // กำหนด timeout เป็น 60 นาที - using var timeoutCts = new CancellationTokenSource(TimeSpan.FromMinutes(60)); - using var combinedCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, timeoutCts.Token); + var pendingJobs = await GetPendingOrProcessingJobsAsync(); @@ -145,7 +143,7 @@ namespace BMA.EHR.Application.Repositories.Leaves.TimeAttendants await UpdateToProcessingAsync(job.Id); // ทำงานที่ต้องการที่นี่ (เช่น เรียก API, ประมวลผลข้อมูล ฯลฯ) - await ProcessTaskAsync(job.RootDnaId, combinedCts.Token); + await ProcessTaskAsync(job.RootDnaId); // อัปเดตสถานะเป็น Completed await UpdateToCompletedAsync(job.Id); diff --git a/BMA.EHR.Leave/Program.cs b/BMA.EHR.Leave/Program.cs index e25a59fe..19959f37 100644 --- a/BMA.EHR.Leave/Program.cs +++ b/BMA.EHR.Leave/Program.cs @@ -190,6 +190,8 @@ if (manager != null) manager.AddOrUpdate("ปรับปรุงรอบการลงเวลาทำงาน", Job.FromExpression(x => x.UpdateUserDutyTime()), "0 1 * * *", bangkokTimeZone); // ทำความสะอาดข้อมูล 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 * * *", bangkokTimeZone); } // apply migrations From 3dee5f716670a27c10bdb0cfd1bcc9156310d847 Mon Sep 17 00:00:00 2001 From: Suphonchai Phoonsawat Date: Mon, 30 Mar 2026 12:08:30 +0700 Subject: [PATCH 147/183] Refactor LeaveProcessJobStatusRepository methods and update Hangfire configuration for improved job processing --- .../LeaveProcessJobStatusRepository.cs | 11 +++++----- BMA.EHR.Insignia/Program.cs | 2 +- BMA.EHR.Leave/Program.cs | 22 ++++++++++++++----- BMA.EHR.Leave/appsettings.json | 6 ++--- 4 files changed, 25 insertions(+), 16 deletions(-) diff --git a/BMA.EHR.Application/Repositories/Leaves/TimeAttendants/LeaveProcessJobStatusRepository.cs b/BMA.EHR.Application/Repositories/Leaves/TimeAttendants/LeaveProcessJobStatusRepository.cs index 87940757..9543820f 100644 --- a/BMA.EHR.Application/Repositories/Leaves/TimeAttendants/LeaveProcessJobStatusRepository.cs +++ b/BMA.EHR.Application/Repositories/Leaves/TimeAttendants/LeaveProcessJobStatusRepository.cs @@ -68,10 +68,10 @@ namespace BMA.EHR.Application.Repositories.Leaves.TimeAttendants return data; } - public async Task> GetPendingOrProcessingJobsAsync() + public async Task> GetPendingJobsAsync() { var data = await _dbContext.Set() - .Where(x => x.Status == "PENDING" || x.Status == "PROCESSING") + .Where(x => x.Status == "PENDING") .ToListAsync(); return data; @@ -125,15 +125,14 @@ namespace BMA.EHR.Application.Repositories.Leaves.TimeAttendants public async Task ProcessTaskAsync(Guid id, CancellationToken cancellationToken = default) { - + Console.WriteLine($"กำลังประมวลผลงานที่มี RootDnaId: {id}"); } public async Task ProcessPendingJobsAsync() { - - - var pendingJobs = await GetPendingOrProcessingJobsAsync(); + var pendingJobs = await GetPendingJobsAsync(); + Console.WriteLine($"พบงานที่ค้างอยู่ในสถานะ PENDING จำนวน {pendingJobs.Count} งาน"); foreach (var job in pendingJobs) { diff --git a/BMA.EHR.Insignia/Program.cs b/BMA.EHR.Insignia/Program.cs index a4dd716a..88c1c2ac 100644 --- a/BMA.EHR.Insignia/Program.cs +++ b/BMA.EHR.Insignia/Program.cs @@ -131,7 +131,7 @@ var builder = WebApplication.CreateBuilder(args); { options.ServerName = "Insignia-Server"; // ← ระบุชื่อ server options.WorkerCount = 5; // ← - options.Queues = new[] { "insignia" }; // ← worker จะรันเฉพาะ queue "insignia" + options.Queues = new[] { "insignia","default" }; // ← worker จะรันเฉพาะ queue "insignia" }); diff --git a/BMA.EHR.Leave/Program.cs b/BMA.EHR.Leave/Program.cs index 19959f37..c4774831 100644 --- a/BMA.EHR.Leave/Program.cs +++ b/BMA.EHR.Leave/Program.cs @@ -119,7 +119,7 @@ builder.Services.AddHealthChecks(); builder.Services.AddRabbitMqConnectionPooling(builder.Configuration); // Add Hangfire services. -var defaultConnection = builder.Configuration.GetConnectionString("DefaultConnection"); +var hangfireConnection = builder.Configuration.GetConnectionString("defaultConnection"); builder.Services.AddHangfire(configuration => configuration .SetDataCompatibilityLevel(CompatibilityLevel.Version_170) @@ -127,19 +127,24 @@ builder.Services.AddHangfire(configuration => configuration .UseRecommendedSerializerSettings() .UseStorage( new MySqlStorage( - defaultConnection, + hangfireConnection, 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" + InvisibilityTimeout = TimeSpan.FromHours(3), + TablesPrefix = "Hangfire_Leave" }))); -builder.Services.AddHangfireServer(); +builder.Services.AddHangfireServer(options => + { + options.ServerName = "Leave-Server"; // ← ระบุชื่อ server + options.WorkerCount = 5; // ← + options.Queues = new[] { "leave","default" }; // ← worker จะรันเฉพาะ queue "leave" + }); var app = builder.Build(); @@ -191,7 +196,12 @@ 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 * * *", bangkokTimeZone); + 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/appsettings.json b/BMA.EHR.Leave/appsettings.json index f8562d6e..e55f7ac0 100644 --- a/BMA.EHR.Leave/appsettings.json +++ b/BMA.EHR.Leave/appsettings.json @@ -19,9 +19,9 @@ // "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;" - "DefaultConnection": "server=192.168.1.63;user=root;password=12345678;port=3306;database=hrms;Convert Zero Datetime=True;Allow User Variables=true;Pooling=True;", - "ExamConnection": "server=192.168.1.63;user=root;password=12345678;port=3306;database=hrms_exam;Convert Zero Datetime=True;Allow User Variables=true;Pooling=True;", - "LeaveConnection": "server=192.168.1.63;user=root;password=12345678;port=3306;database=hrms_leave;Convert Zero Datetime=True;Allow User Variables=true;Pooling=True;" + "DefaultConnection": "Server=192.168.1.63;User ID=root;Password=12345678;Port=3306;Database=hrms;Allow User Variables=True;Convert Zero Datetime=True;Pooling=True;", + "ExamConnection": "Server=192.168.1.63;User ID=root;Password=12345678;Port=3306;Database=hrms_exam;Allow User Variables=True;Convert Zero Datetime=True;Pooling=True;", + "LeaveConnection": "Server=192.168.1.63;User ID=root;Password=12345678;Port=3306;Database=hrms_leave;Allow User Variables=True;Convert Zero Datetime=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;", From 759a51ab58648665d580dfd130ec62353291a490 Mon Sep 17 00:00:00 2001 From: Suphonchai Phoonsawat Date: Mon, 30 Mar 2026 15:53:33 +0700 Subject: [PATCH 148/183] Enhance LeaveProcessJobStatusRepository with detailed processing logic and add new methods in UserProfileRepository for fetching officer and employee profiles by RootDnaId --- .../LeaveProcessJobStatusRepository.cs | 268 +++++++++++++++++- .../Repositories/UserProfileRepository.cs | 69 +++++ BMA.EHR.Insignia/appsettings.json | 17 +- 3 files changed, 345 insertions(+), 9 deletions(-) diff --git a/BMA.EHR.Application/Repositories/Leaves/TimeAttendants/LeaveProcessJobStatusRepository.cs b/BMA.EHR.Application/Repositories/Leaves/TimeAttendants/LeaveProcessJobStatusRepository.cs index 9543820f..b57183d2 100644 --- a/BMA.EHR.Application/Repositories/Leaves/TimeAttendants/LeaveProcessJobStatusRepository.cs +++ b/BMA.EHR.Application/Repositories/Leaves/TimeAttendants/LeaveProcessJobStatusRepository.cs @@ -2,7 +2,13 @@ using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using System.Text.Json; +using System.IO; using BMA.EHR.Application.Common.Interfaces; +using BMA.EHR.Application.Repositories.Leaves.LeaveRequests; +using BMA.EHR.Application.Repositories.MetaData; +using BMA.EHR.Application.Responses.Profiles; +using BMA.EHR.Domain.Extensions; using BMA.EHR.Domain.Models.Leave.TimeAttendants; using Microsoft.AspNetCore.Http; using Microsoft.EntityFrameworkCore; @@ -14,15 +20,35 @@ namespace BMA.EHR.Application.Repositories.Leaves.TimeAttendants #region " Fields " private readonly ILeaveDbContext _dbContext; + private readonly IHttpContextAccessor _httpContextAccessor; + private readonly UserProfileRepository _userProfileRepository; + private readonly HolidayRepository _holidayRepository; + private readonly DutyTimeRepository _dutyTimeRepository; + private readonly UserDutyTimeRepository _userDutyTimeRepository; + private readonly ProcessUserTimeStampRepository _processUserTimeStampRepository; + private readonly LeaveRequestRepository _leaveRequestRepository; #endregion #region " Constructor and Destructor " public LeaveProcessJobStatusRepository(ILeaveDbContext dbContext, - IHttpContextAccessor httpContextAccessor) : base(dbContext, httpContextAccessor) + IHttpContextAccessor httpContextAccessor, + UserProfileRepository userProfileRepository, + HolidayRepository holidayRepository, + DutyTimeRepository dutyTimeRepository, + UserDutyTimeRepository userDutyTimeRepository, + ProcessUserTimeStampRepository processUserTimeStampRepository, + LeaveRequestRepository leaveRequestRepository) : base(dbContext, httpContextAccessor) { _dbContext = dbContext; + _httpContextAccessor = httpContextAccessor; + _userProfileRepository = userProfileRepository; + _holidayRepository = holidayRepository; + _leaveRequestRepository = leaveRequestRepository; + _dutyTimeRepository = dutyTimeRepository; + _userDutyTimeRepository = userDutyTimeRepository; + _processUserTimeStampRepository = processUserTimeStampRepository; } #endregion @@ -123,9 +149,214 @@ namespace BMA.EHR.Application.Repositories.Leaves.TimeAttendants return job!; } - public async Task ProcessTaskAsync(Guid id, CancellationToken cancellationToken = default) + public async Task ProcessTaskAsync(Guid rootDnaId, DateTime? startDate, DateTime? endDate) { - Console.WriteLine($"กำลังประมวลผลงานที่มี RootDnaId: {id}"); + + var profiles = new List(); + var dateStart = startDate?.Date ?? DateTime.Now.Date; + var dateEnd = endDate?.Date ?? DateTime.Now.Date; + + var holidays = await _holidayRepository.GetHolidayAsync(dateStart, dateEnd); + var weekend = _holidayRepository.GetWeekEnd(dateStart, dateEnd); + var excludeDates = holidays.Union(weekend).ToList(); + + var dateList = new List(); + for (DateTime i = dateStart; i <= dateEnd; i = i.AddDays(1)) + { + if (holidays.Contains(i)) + { + var d = await _holidayRepository.GetHolidayAsync(i); + dateList.Add(new LoopDate + { + date = i, + isHoliday = true, + isWeekEnd = false, + dateRemark = d + }); + } + else if (weekend.Contains(i)) + { + dateList.Add(new LoopDate + { + date = i, + isHoliday = true, + isWeekEnd = false, + dateRemark = "วันหยุด" + }); + } + else + { + dateList.Add(new LoopDate + { + date = i, + isHoliday = false, + isWeekEnd = false, + dateRemark = "" + }); + } + } + + var defaultRound = await _dutyTimeRepository.GetDefaultAsync(); + if (defaultRound == null) + { + throw new Exception("ไม่พบรอบการลงเวลา Default"); + } + + var employees = new List(); + + foreach (var dd in dateList.Where(x => !x.isHoliday && !x.isWeekEnd)) + { + profiles = await _userProfileRepository.GetAllOfficerByRootDnaId(rootDnaId.ToString(),dd.date); + foreach (var p in profiles) + { + var count = 1; + var keycloakUserId = p.Keycloak ?? Guid.Empty; + + var timeStamps = await _processUserTimeStampRepository.GetTimestampByDateAsync(keycloakUserId, dd.date); + + var fullName = $"{p.Prefix}{p.FirstName} {p.LastName}"; + + var effectiveDate = await _userDutyTimeRepository.GetLastEffectRound(p.Id, dd.date); + var roundId = effectiveDate != null ? effectiveDate.DutyTimeId : Guid.Empty; + var userRound = await _dutyTimeRepository.GetByIdAsync(roundId); + + var duty = userRound ?? defaultRound; + + // check วันลาของแต่ละคน + var leaveReq = await _leaveRequestRepository.GetLeavePeriodAsync(keycloakUserId, dd.date); + var remarkStr = string.Empty; + + if (leaveReq != null) + { + switch (leaveReq.Type.Code.ToUpper()) + { + case "LV-001": + case "LV-002": + case "LV-005": + remarkStr += leaveReq.Type.Name; + var leaveRange = leaveReq.LeaveRange == null ? "" : leaveReq.LeaveRange.ToUpper(); + if (leaveRange == "MORNING") + remarkStr += "ครึ่งวันเช้า"; + else if (leaveRange == "AFTERNOON") + remarkStr += "ครึ่งวันบ่าย"; + + + // var leaveRangeEnd = leaveReq.LeaveRangeEnd == null ? "" : leaveReq.LeaveRangeEnd.ToUpper(); + // if (leaveRangeEnd == "MORNING") + // remarkStr += "ครึ่งวันเช้า"; + // else if (leaveRangeEnd == "AFTERNOON") + // remarkStr += "ครึ่งวันบ่าย"; + + var leaveRangeEnd = leaveReq.LeaveRangeEnd == null ? "" : leaveReq.LeaveRangeEnd.ToUpper(); + if (leaveRange != leaveRangeEnd) + { + if (leaveRangeEnd == "MORNING") + remarkStr += " - ครึ่งวันเช้า"; + else if (leaveRangeEnd == "AFTERNOON") + remarkStr += " - ครึ่งวันบ่าย"; + } + break; + default: + remarkStr += leaveReq.Type.Name; + break; + } + } + else + { + if (timeStamps == null) + { + if (dd.date <= DateTime.Now.Date) + { + remarkStr = "ขาดราชการ"; + if (dd.isHoliday == true) + { + remarkStr = $"วันหยุด ({dd.dateRemark})"; + } + else if (dd.isWeekEnd) + { + remarkStr = dd.dateRemark; + } + } + else remarkStr = ""; + } + else + { + // check status ของการลงเวลา + if (timeStamps.CheckOut != null) + { + if (timeStamps.CheckOutStatus == "ABSENT") + remarkStr = "ขาดราชการ" + (!timeStamps.IsLocationCheckOut ? $" (นอกสถานที่:{timeStamps.CheckOutLocationName})".Trim() : ""); + else if (timeStamps.CheckInStatus == "ABSENT") + remarkStr = "ขาดราชการ" + (!timeStamps.IsLocationCheckIn ? $" (นอกสถานที่:{timeStamps.CheckInLocationName})".Trim() : ""); + else if (timeStamps.CheckInStatus == "LATE") + { + remarkStr = "สาย" + (!timeStamps.IsLocationCheckIn ? $" (นอกสถานที่:{timeStamps.CheckInLocationName})".Trim() : ""); + //lateTotal += 1; + } + else + remarkStr = !timeStamps.IsLocationCheckIn ? $" นอกสถานที่:{timeStamps.CheckInLocationName}".Trim() : ""; + } + else + { + if (timeStamps.CheckInStatus == "ABSENT") + remarkStr = "ขาดราชการ" + (!timeStamps.IsLocationCheckIn ? $" (นอกสถานที่:{timeStamps.CheckInLocationName})".Trim() : ""); + else if (timeStamps.CheckInStatus == "LATE") + { + remarkStr = "สาย" + (!timeStamps.IsLocationCheckIn ? $" (นอกสถานที่:{timeStamps.CheckInLocationName})".Trim() : ""); + //lateTotal += 1; + } + else + remarkStr = !timeStamps.IsLocationCheckIn ? $" นอกสถานที่:{timeStamps.CheckInLocationName}".Trim() : ""; + } + } + } + + var emp = new DateResultReport + { + no = count, + fullName = fullName, + dutyTimeName = $"{duty.StartTimeMorning} - {duty.EndTimeAfternoon} น.", + checkInLocation = timeStamps == null ? "" : timeStamps.CheckInPOI, + checkInTime = timeStamps == null ? "" : $"{timeStamps.CheckIn.ToString("HH:mm")} น.", + checkOutLocation = timeStamps == null ? "" : timeStamps.CheckOutPOI ?? "", + checkOutTime = timeStamps == null ? "" : + timeStamps.CheckOut != null ? + $"{timeStamps.CheckOut.Value.ToString("HH:mm")} น." : + "", + remark = remarkStr, + 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 == null ? dd.date.Date : timeStamps?.CheckIn, + checkOutTimeRaw = timeStamps == null ? dd.date.Date : timeStamps?.CheckOut != null ? timeStamps?.CheckOut : null, + }; + + employees.Add(emp); + count++; + } + + // Write employees to JSON file + var fileName = $"employees_{DateTime.Now:yyyyMMdd_HHmmss}.txt"; + var filePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Exports", fileName); + + // Ensure directory exists + var directory = Path.GetDirectoryName(filePath); + if (!string.IsNullOrEmpty(directory) && !Directory.Exists(directory)) + { + Directory.CreateDirectory(directory); + } + + var jsonOptions = new JsonSerializerOptions + { + WriteIndented = true, + Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping + }; + + var jsonContent = JsonSerializer.Serialize(employees, jsonOptions); + await File.WriteAllTextAsync(filePath, jsonContent); + } } public async Task ProcessPendingJobsAsync() @@ -142,7 +373,7 @@ namespace BMA.EHR.Application.Repositories.Leaves.TimeAttendants await UpdateToProcessingAsync(job.Id); // ทำงานที่ต้องการที่นี่ (เช่น เรียก API, ประมวลผลข้อมูล ฯลฯ) - await ProcessTaskAsync(job.RootDnaId); + await ProcessTaskAsync(job.RootDnaId,job.StartDate, job.EndDate); // อัปเดตสถานะเป็น Completed await UpdateToCompletedAsync(job.Id); @@ -157,4 +388,33 @@ namespace BMA.EHR.Application.Repositories.Leaves.TimeAttendants #endregion } + + class LoopDate + { + public DateTime date { get; set; } + + public bool isHoliday { get; set; } + + public bool isWeekEnd { get; set; } + + public string dateRemark { get; set; } + + } + + 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; } + } + } \ No newline at end of file diff --git a/BMA.EHR.Application/Repositories/UserProfileRepository.cs b/BMA.EHR.Application/Repositories/UserProfileRepository.cs index ba1f70c3..4ae8bcbe 100644 --- a/BMA.EHR.Application/Repositories/UserProfileRepository.cs +++ b/BMA.EHR.Application/Repositories/UserProfileRepository.cs @@ -764,6 +764,75 @@ namespace BMA.EHR.Application.Repositories } } + public async Task> GetAllOfficerByRootDnaId(string? rootDnaId, DateTime date) + { + try + { + var apiPath = $"{_configuration["API"]}/org/unauthorize/officer-list"; + var apiKey = _configuration["API_KEY"]; + var body = new + { + reqNode = 0, + reqNodeId = rootDnaId, + date = date + }; + //Console.WriteLine(body); + + var profiles = new List(); + + var apiResult = await PostExternalAPIAsync(apiPath, "", body, apiKey); + if (apiResult != null) + { + var raw = JsonConvert.DeserializeObject(apiResult); + if (raw != null) + return raw.Result; + else + return new List(); + } + else + return new List(); + } + catch + { + throw; + } + } + + public async Task> GetAllEmployeeByRootDnaId(string? rootDnaId, DateTime date) + { + try + { + var apiPath = $"{_configuration["API"]}/org/unauthorize/employee-list"; + var apiKey = _configuration["API_KEY"]; + var body = new + { + reqNode = 0, + reqNodeId = rootDnaId, + startDate = date, + endDate = date + }; + //Console.WriteLine(body); + + var profiles = new List(); + + var apiResult = await PostExternalAPIAsync(apiPath, "", body, apiKey); + if (apiResult != null) + { + var raw = JsonConvert.DeserializeObject(apiResult); + if (raw != null) + return raw.Result; + else + return new List(); + } + else + return new List(); + } + catch + { + throw; + } + } + public async Task> GetProfileByAdminRolev4(string? accessToken, int? node, string? nodeId, string role, string? revisionId, int? reqNode, string? reqNodeId, DateTime? startDate, DateTime? endDate) { try diff --git a/BMA.EHR.Insignia/appsettings.json b/BMA.EHR.Insignia/appsettings.json index 4f3d3faf..4e160482 100644 --- a/BMA.EHR.Insignia/appsettings.json +++ b/BMA.EHR.Insignia/appsettings.json @@ -31,10 +31,11 @@ //"DisciplineConnection": "server=hrms.chin.in.th;user=root;password=ey2qVVyyqGYw8CyA7h8X72559r2Ad84K;port=53636;database=hrms_discipline;Convert Zero Datetime=True;Allow User Variables=true;Pooling=True;" }, "Jwt": { - //"Key": "j7C9RO_p4nRtuwCH4z9Db_A_6We42tkD_p4lZtDrezc", - //"Issuer": "https://hrms-id.chin.in.th/realms/hrms" - "Key": "HP-FnQMUj9msHMSD3T9HtdEnphAKoCJLEl85CIqROFI", - "Issuer": "https://id.frappet.synology.me/realms/hrms" + //"Key": "HP-FnQMUj9msHMSD3T9HtdEnphAKoCJLEl85CIqROFI", + "Key": "j7C9RO_p4nRtuwCH4z9Db_A_6We42tkD_p4lZtDrezc", + "Issuer": "https://hrmsbkk-id.case-collection.com/realms/hrms" + //"Key": "xY2VR-EFvvNPsMs39u8ooVBWQL6mPwrNJOh3koJFTgU", + //"Issuer": "https://hrms-id.bangkok.go.th/realms/hrms" }, "EPPlus": { "ExcelPackage": { @@ -55,11 +56,17 @@ "Node": { "API": "https://bma-ehr.frappet.synology.me/api/v1/probation" }, - "API": "https://bma-ehr.frappet.synology.me/api/v1", "RabbitMQ": { "URL": "localhost", "UserName": "frappet", "Password": "FPTadmin2357" }, + "Domain": "https://hrmsbkk.case-collection.com", + "APIPROBATION": "https://hrmsbkk.case-collection.com/api/v1/probation", + "API": "https://hrmsbkk.case-collection.com/api/v1", + "APIV2": "https://hrmsbkk.case-collection.com/api/v2", + "VITE_URL_MGT": "https://hrmsbkk-mgt.case-collection.com", + //"API": "https://bma-ehr.frappet.synology.me/api/v1", + //"API": "https://bma-hrms.bangkok.go.th/api/v1", "API_KEY": "fKRL16yyEgbyTEJdsMw2h64tGSCmkW685PRtM3CygzX1JOSdptT9UJtpgWwKM8FybRTJups3GTFwj27ZRvlPdIkv3XgCoVJaD5LmR06ozuEPvCCRSdp2WFthg08V5xHc56fTPfZLpr1VmXrhd6dvYhHIqKkQUJR02Rlkss11cLRWEQOssEFVA4xdu2J5DIRO1EM5m7wRRvEwcDB4mYRXD9HH52SMq6iYqUWEWsMwLdbk7QW9yYESUEuzMW5gWrb6vIeWZxJV5bTz1PcWUyR7eO9Fyw1F5DiQYc9JgzTC1mW7cv31fEtTtrfbJYKIb5EbWilqIEUKC6A0UKBDDek35ML0006cqRVm0pvdOH6jeq7VQyYrhdXe59dBEyhYGUIfozoVBvW7Up4QBuOMjyPjSqJPlMBKwaseptfrblxQV1AOOivSBpf1ZcQyOZ8JktRtKUDSuXsmG0lsXwFlI3JCeSHdpVdgZWFYcJPegqfrB6KotR02t9AVkpLs1ZWrixwz" } From 2cd7798dd91391c2b9b9fe637d430021b0134d27 Mon Sep 17 00:00:00 2001 From: Suphonchai Phoonsawat Date: Tue, 31 Mar 2026 09:46:44 +0700 Subject: [PATCH 149/183] Add admin endpoints for processing leave tasks, including retrieval, deletion, and updates --- .../LeaveProcessJobStatusRepository.cs | 84 +++++++---- BMA.EHR.Leave/Controllers/LeaveController.cs | 133 ++++++++++++++++++ 2 files changed, 189 insertions(+), 28 deletions(-) diff --git a/BMA.EHR.Application/Repositories/Leaves/TimeAttendants/LeaveProcessJobStatusRepository.cs b/BMA.EHR.Application/Repositories/Leaves/TimeAttendants/LeaveProcessJobStatusRepository.cs index b57183d2..ded9e370 100644 --- a/BMA.EHR.Application/Repositories/Leaves/TimeAttendants/LeaveProcessJobStatusRepository.cs +++ b/BMA.EHR.Application/Repositories/Leaves/TimeAttendants/LeaveProcessJobStatusRepository.cs @@ -80,6 +80,19 @@ namespace BMA.EHR.Application.Repositories.Leaves.TimeAttendants return data; } + /// + /// ดึงข้อมูล Job Status จาก UserId + /// + public async Task> GetByUserIdAsync(Guid userId) + { + var data = await _dbContext.Set() + .Where(x => x.CreatedUserId == userId.ToString("D")) + .OrderByDescending(x => x.CreatedDate) + .ToListAsync(); + + return data; + } + /// /// ดึงข้อมูล Job Status ที่ยัง pending หรือ processing /// @@ -225,6 +238,9 @@ namespace BMA.EHR.Application.Repositories.Leaves.TimeAttendants // check วันลาของแต่ละคน var leaveReq = await _leaveRequestRepository.GetLeavePeriodAsync(keycloakUserId, dd.date); var remarkStr = string.Empty; + var status = string.Empty; + var stampType = string.Empty; + var stampAmount = 0.0; if (leaveReq != null) { @@ -260,6 +276,9 @@ namespace BMA.EHR.Application.Repositories.Leaves.TimeAttendants remarkStr += leaveReq.Type.Name; break; } + status = "LEAVE"; + stampType = leaveReq.LeaveRange ?? ""; + stampAmount = leaveReq.LeaveRange == "MORNING" || leaveReq.LeaveRangeEnd == "MORNING" ? 0.5 : 1; } else { @@ -268,13 +287,18 @@ namespace BMA.EHR.Application.Repositories.Leaves.TimeAttendants if (dd.date <= DateTime.Now.Date) { remarkStr = "ขาดราชการ"; + status = "ABSENT"; + stampType = "FULL_DAY"; + stampAmount = 1; if (dd.isHoliday == true) { remarkStr = $"วันหยุด ({dd.dateRemark})"; + status = "HOLIDAY"; } else if (dd.isWeekEnd) { remarkStr = dd.dateRemark; + status = "WEEKEND"; } } else remarkStr = ""; @@ -285,12 +309,25 @@ namespace BMA.EHR.Application.Repositories.Leaves.TimeAttendants if (timeStamps.CheckOut != null) { if (timeStamps.CheckOutStatus == "ABSENT") + { remarkStr = "ขาดราชการ" + (!timeStamps.IsLocationCheckOut ? $" (นอกสถานที่:{timeStamps.CheckOutLocationName})".Trim() : ""); + status = "ABSENT"; + stampType = "FULL_DAY"; + stampAmount = 1; + } else if (timeStamps.CheckInStatus == "ABSENT") + { remarkStr = "ขาดราชการ" + (!timeStamps.IsLocationCheckIn ? $" (นอกสถานที่:{timeStamps.CheckInLocationName})".Trim() : ""); + status = "ABSENT"; + stampType = "FULL_DAY"; + stampAmount = 1; + } else if (timeStamps.CheckInStatus == "LATE") { remarkStr = "สาย" + (!timeStamps.IsLocationCheckIn ? $" (นอกสถานที่:{timeStamps.CheckInLocationName})".Trim() : ""); + status = "LATE"; + stampType = "FULL_DAY"; + stampAmount = 1; //lateTotal += 1; } else @@ -299,9 +336,17 @@ namespace BMA.EHR.Application.Repositories.Leaves.TimeAttendants else { if (timeStamps.CheckInStatus == "ABSENT") + { + status = "ABSENT"; + stampType = "FULL_DAY"; + stampAmount = 1; remarkStr = "ขาดราชการ" + (!timeStamps.IsLocationCheckIn ? $" (นอกสถานที่:{timeStamps.CheckInLocationName})".Trim() : ""); + } else if (timeStamps.CheckInStatus == "LATE") { + status = "LATE"; + stampType = "FULL_DAY"; + stampAmount = 1; remarkStr = "สาย" + (!timeStamps.IsLocationCheckIn ? $" (นอกสถานที่:{timeStamps.CheckInLocationName})".Trim() : ""); //lateTotal += 1; } @@ -313,24 +358,13 @@ namespace BMA.EHR.Application.Repositories.Leaves.TimeAttendants var emp = new DateResultReport { - no = count, - fullName = fullName, - dutyTimeName = $"{duty.StartTimeMorning} - {duty.EndTimeAfternoon} น.", - checkInLocation = timeStamps == null ? "" : timeStamps.CheckInPOI, - checkInTime = timeStamps == null ? "" : $"{timeStamps.CheckIn.ToString("HH:mm")} น.", - checkOutLocation = timeStamps == null ? "" : timeStamps.CheckOutPOI ?? "", - checkOutTime = timeStamps == null ? "" : - timeStamps.CheckOut != null ? - $"{timeStamps.CheckOut.Value.ToString("HH:mm")} น." : - "", + profileId = p.Id.ToString(), + stampDate = dd.date, + stampType = stampType, + stampAmount = stampAmount, remark = remarkStr, - 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 == null ? dd.date.Date : timeStamps?.CheckIn, - checkOutTimeRaw = timeStamps == null ? dd.date.Date : timeStamps?.CheckOut != null ? timeStamps?.CheckOut : null, + status = status + }; employees.Add(emp); @@ -403,18 +437,12 @@ namespace BMA.EHR.Application.Repositories.Leaves.TimeAttendants 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? profileId { get; set; } + public DateTime stampDate { get; set; } + public string stampType { get; set; } + public double stampAmount { 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; } + public string status { get; set; } } } \ No newline at end of file diff --git a/BMA.EHR.Leave/Controllers/LeaveController.cs b/BMA.EHR.Leave/Controllers/LeaveController.cs index 930c4043..799f3558 100644 --- a/BMA.EHR.Leave/Controllers/LeaveController.cs +++ b/BMA.EHR.Leave/Controllers/LeaveController.cs @@ -4200,6 +4200,139 @@ namespace BMA.EHR.Leave.Service.Controllers return Success(); } + /// + /// แสดงรายการ Task สำหรับ Process ข้อมูลวันลาและขาดราชการ (ADMIN) + /// + /// + /// + /// เมื่อทำรายการสำเร็จ + /// ไม่ได้ Login เข้าระบบ + /// เมื่อเกิดข้อผิดพลาดในการทำงาน + [HttpGet("admin/leave-task/process")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status401Unauthorized)] + [ProducesResponseType(StatusCodes.Status500InternalServerError)] + public async Task> GetProcessTaskAsync() + { + var userId = UserId == null ? Guid.Empty : Guid.Parse(UserId); + + var tasks = await _leaveProcessJobStatusRepository.GetByUserIdAsync(userId); + + var result = tasks.Select(t => new + { + t.Id, + t.CreatedFullName, + t.CreatedAt, + t.Status, + t.StartDate, + t.EndDate, + t.ProcessingDate, + t.CompletedDate, + t.ErrorMessage + }); + + return Success(result); + } + + + /// + /// แสดงรายการ Task สำหรับ Process ข้อมูลวันลาและขาดราชการ (ADMIN) + /// + /// + /// + /// เมื่อทำรายการสำเร็จ + /// ไม่ได้ Login เข้าระบบ + /// เมื่อเกิดข้อผิดพลาดในการทำงาน + [HttpGet("admin/leave-task/process/{id:guid}")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status401Unauthorized)] + [ProducesResponseType(StatusCodes.Status500InternalServerError)] + public async Task> GetProcessTaskByIdAsync(Guid id) + { + var task = await _leaveProcessJobStatusRepository.GetByTaskIdAsync(id); + + if (task == null) + { + return Error(GlobalMessages.DataNotFound, StatusCodes.Status404NotFound); + } + + var result = new + { + task.Id, + task.CreatedFullName, + task.CreatedAt, + task.Status, + task.StartDate, + task.EndDate, + task.ProcessingDate, + task.CompletedDate, + task.ErrorMessage + }; + + return Success(result); + } + + /// + /// ลบ Task สำหรับ Process ข้อมูลวันลาและขาดราชการ (ADMIN) + /// + /// + /// + /// เมื่อทำรายการสำเร็จ + /// ไม่ได้ Login เข้าระบบ + /// เมื่อเกิดข้อผิดพลาดในการทำงาน + [HttpDelete("admin/leave-task/process/{id:guid}")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status401Unauthorized)] + [ProducesResponseType(StatusCodes.Status500InternalServerError)] + public async Task> DeleteProcessTaskByIdAsync(Guid id) + { + var task = await _leaveProcessJobStatusRepository.GetByTaskIdAsync(id); + + if (task == null) + { + return Error(GlobalMessages.DataNotFound, StatusCodes.Status404NotFound); + } + + await _leaveProcessJobStatusRepository.DeleteAsync(task); + + return Success(); + } + + /// + /// อัปเดต Task สำหรับ Process ข้อมูลวันลาและขาดราชการ (ADMIN) + /// + /// + /// + /// เมื่อทำรายการสำเร็จ + /// ไม่ได้ Login เข้าระบบ + /// เมื่อเกิดข้อผิดพลาดในการทำงาน + [HttpPut("admin/leave-task/process/{id:guid}")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status401Unauthorized)] + [ProducesResponseType(StatusCodes.Status500InternalServerError)] + public async Task> UpdateProcessTaskByIdAsync(Guid id,[FromBody] CreateLeaveProcessJobDto req) + { + var task = await _leaveProcessJobStatusRepository.GetByTaskIdAsync(id); + + if (task == null) + { + return Error(GlobalMessages.DataNotFound, StatusCodes.Status404NotFound); + } + + if(task.Status != "PENDING") + { + return Error("ไม่สามารถแก้ไขได้เนื่องจาก Task อยู่ในสถานะกำลังดำเนินการหรือดำเนินการเสร็จสิ้นแล้ว"); + } + + task.StartDate = req.StartDate; + task.EndDate = req.EndDate; + + await _leaveProcessJobStatusRepository.UpdateAsync(task); + + return Success(); + } + + #endregion #endregion From 82c31a0f576d9260253e87bbc14832d744ff7838 Mon Sep 17 00:00:00 2001 From: Suphonchai Phoonsawat Date: Tue, 31 Mar 2026 10:18:06 +0700 Subject: [PATCH 150/183] Refactor GenericRepository and GenericLeaveRepository to expose PostExternalAPIAsync method and enhance LeaveProcessJobStatusRepository with API integration for processing employee records --- .../Repositories/GenericRepository.cs | 2 +- .../Leaves/GenericLeaveRepository.cs | 35 ++++++++++++ .../LeaveProcessJobStatusRepository.cs | 54 ++++++++++++------- 3 files changed, 71 insertions(+), 20 deletions(-) diff --git a/BMA.EHR.Application/Repositories/GenericRepository.cs b/BMA.EHR.Application/Repositories/GenericRepository.cs index 805b88ba..d825ccaf 100644 --- a/BMA.EHR.Application/Repositories/GenericRepository.cs +++ b/BMA.EHR.Application/Repositories/GenericRepository.cs @@ -115,7 +115,7 @@ namespace BMA.EHR.Application.Repositories } - protected async Task PostExternalAPIAsync(string apiPath, string accessToken, object? body, string apiKey, CancellationToken cancellationToken = default) + public async Task PostExternalAPIAsync(string apiPath, string accessToken, object? body, string apiKey, CancellationToken cancellationToken = default) { try { diff --git a/BMA.EHR.Application/Repositories/Leaves/GenericLeaveRepository.cs b/BMA.EHR.Application/Repositories/Leaves/GenericLeaveRepository.cs index 93ae9cec..2e550d71 100644 --- a/BMA.EHR.Application/Repositories/Leaves/GenericLeaveRepository.cs +++ b/BMA.EHR.Application/Repositories/Leaves/GenericLeaveRepository.cs @@ -2,8 +2,11 @@ using BMA.EHR.Domain.Models.Base; using Microsoft.AspNetCore.Http; using Microsoft.EntityFrameworkCore; +using Newtonsoft.Json; using System.IO.Pipes; +using System.Net.Http.Headers; using System.Security.Claims; +using System.Text; namespace BMA.EHR.Application.Repositories.Leaves { @@ -43,6 +46,38 @@ namespace BMA.EHR.Application.Repositories.Leaves #region " Methods " + public async Task PostExternalAPIAsync(string apiPath, string accessToken, object? body, string apiKey, CancellationToken cancellationToken = default) + { + try + { + // กำหนด timeout เป็น 30 นาที + using var timeoutCts = new CancellationTokenSource(TimeSpan.FromMinutes(30)); + using var combinedCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, timeoutCts.Token); + var json = JsonConvert.SerializeObject(body); + var stringContent = new StringContent(json, Encoding.UTF8, "application/json"); + //stringContent.Headers.ContentType = new MediaTypeHeaderValue("application/json"); + + using (var client = new HttpClient()) + { + client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken.Replace("Bearer ", "")); + client.DefaultRequestHeaders.Add("api-key", apiKey); + var _res = await client.PostAsync(apiPath, stringContent, combinedCts.Token); + if (_res.IsSuccessStatusCode) + { + var _result = await _res.Content.ReadAsStringAsync(); + + return _result; + } + return string.Empty; + } + } + catch + { + throw; + } + } + + public virtual async Task> GetAllAsync() { return await _dbSet.ToListAsync(); diff --git a/BMA.EHR.Application/Repositories/Leaves/TimeAttendants/LeaveProcessJobStatusRepository.cs b/BMA.EHR.Application/Repositories/Leaves/TimeAttendants/LeaveProcessJobStatusRepository.cs index ded9e370..4eba7b2a 100644 --- a/BMA.EHR.Application/Repositories/Leaves/TimeAttendants/LeaveProcessJobStatusRepository.cs +++ b/BMA.EHR.Application/Repositories/Leaves/TimeAttendants/LeaveProcessJobStatusRepository.cs @@ -12,6 +12,7 @@ using BMA.EHR.Domain.Extensions; using BMA.EHR.Domain.Models.Leave.TimeAttendants; using Microsoft.AspNetCore.Http; using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Configuration; namespace BMA.EHR.Application.Repositories.Leaves.TimeAttendants { @@ -27,6 +28,7 @@ namespace BMA.EHR.Application.Repositories.Leaves.TimeAttendants private readonly UserDutyTimeRepository _userDutyTimeRepository; private readonly ProcessUserTimeStampRepository _processUserTimeStampRepository; private readonly LeaveRequestRepository _leaveRequestRepository; + private readonly IConfiguration _configuration; #endregion @@ -39,12 +41,14 @@ namespace BMA.EHR.Application.Repositories.Leaves.TimeAttendants DutyTimeRepository dutyTimeRepository, UserDutyTimeRepository userDutyTimeRepository, ProcessUserTimeStampRepository processUserTimeStampRepository, - LeaveRequestRepository leaveRequestRepository) : base(dbContext, httpContextAccessor) + LeaveRequestRepository leaveRequestRepository, + IConfiguration configuration) : base(dbContext, httpContextAccessor) { _dbContext = dbContext; _httpContextAccessor = httpContextAccessor; _userProfileRepository = userProfileRepository; _holidayRepository = holidayRepository; + _configuration = configuration; _leaveRequestRepository = leaveRequestRepository; _dutyTimeRepository = dutyTimeRepository; _userDutyTimeRepository = userDutyTimeRepository; @@ -364,38 +368,50 @@ namespace BMA.EHR.Application.Repositories.Leaves.TimeAttendants stampAmount = stampAmount, remark = remarkStr, status = status - }; employees.Add(emp); count++; } - // Write employees to JSON file - var fileName = $"employees_{DateTime.Now:yyyyMMdd_HHmmss}.txt"; - var filePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Exports", fileName); + // // Write employees to JSON file + // var fileName = $"employees_{DateTime.Now:yyyyMMdd_HHmmss}.txt"; + // var filePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Exports", fileName); - // Ensure directory exists - var directory = Path.GetDirectoryName(filePath); - if (!string.IsNullOrEmpty(directory) && !Directory.Exists(directory)) - { - Directory.CreateDirectory(directory); - } + // // Ensure directory exists + // var directory = Path.GetDirectoryName(filePath); + // if (!string.IsNullOrEmpty(directory) && !Directory.Exists(directory)) + // { + // Directory.CreateDirectory(directory); + // } - var jsonOptions = new JsonSerializerOptions - { - WriteIndented = true, - Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping - }; + // var jsonOptions = new JsonSerializerOptions + // { + // WriteIndented = true, + // Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping + // }; - var jsonContent = JsonSerializer.Serialize(employees, jsonOptions); - await File.WriteAllTextAsync(filePath, jsonContent); + // var jsonContent = JsonSerializer.Serialize(employees, jsonOptions); + // await File.WriteAllTextAsync(filePath, jsonContent); } + + //call api + var apiPath = $"{_configuration["API"]}/org/profile/absent-late/batch"; + var apiKey = _configuration["API_KEY"]; + var body = new + { + records = employees + }; + + var apiResult = await PostExternalAPIAsync(apiPath, AccessToken ?? "", body, apiKey); + if(apiResult == "") + { + throw new Exception($"เรียก API {apiPath} ไม่สำเร็จ"); + } } public async Task ProcessPendingJobsAsync() { - var pendingJobs = await GetPendingJobsAsync(); Console.WriteLine($"พบงานที่ค้างอยู่ในสถานะ PENDING จำนวน {pendingJobs.Count} งาน"); From d85bab11b2cf17bd38464b5e480685203f6b21e5 Mon Sep 17 00:00:00 2001 From: Suphonchai Phoonsawat Date: Tue, 31 Mar 2026 10:20:30 +0700 Subject: [PATCH 151/183] Add ProcessEmpTaskAsync method to handle employee task processing and integrate with external API for attendance reporting --- .../LeaveProcessJobStatusRepository.cs | 246 ++++++++++++++++++ 1 file changed, 246 insertions(+) diff --git a/BMA.EHR.Application/Repositories/Leaves/TimeAttendants/LeaveProcessJobStatusRepository.cs b/BMA.EHR.Application/Repositories/Leaves/TimeAttendants/LeaveProcessJobStatusRepository.cs index 4eba7b2a..4a2be392 100644 --- a/BMA.EHR.Application/Repositories/Leaves/TimeAttendants/LeaveProcessJobStatusRepository.cs +++ b/BMA.EHR.Application/Repositories/Leaves/TimeAttendants/LeaveProcessJobStatusRepository.cs @@ -410,6 +410,251 @@ namespace BMA.EHR.Application.Repositories.Leaves.TimeAttendants } } + public async Task ProcessEmpTaskAsync(Guid rootDnaId, DateTime? startDate, DateTime? endDate) + { + + var profiles = new List(); + var dateStart = startDate?.Date ?? DateTime.Now.Date; + var dateEnd = endDate?.Date ?? DateTime.Now.Date; + + var holidays = await _holidayRepository.GetHolidayAsync(dateStart, dateEnd); + var weekend = _holidayRepository.GetWeekEnd(dateStart, dateEnd); + var excludeDates = holidays.Union(weekend).ToList(); + + var dateList = new List(); + for (DateTime i = dateStart; i <= dateEnd; i = i.AddDays(1)) + { + if (holidays.Contains(i)) + { + var d = await _holidayRepository.GetHolidayAsync(i); + dateList.Add(new LoopDate + { + date = i, + isHoliday = true, + isWeekEnd = false, + dateRemark = d + }); + } + else if (weekend.Contains(i)) + { + dateList.Add(new LoopDate + { + date = i, + isHoliday = true, + isWeekEnd = false, + dateRemark = "วันหยุด" + }); + } + else + { + dateList.Add(new LoopDate + { + date = i, + isHoliday = false, + isWeekEnd = false, + dateRemark = "" + }); + } + } + + var defaultRound = await _dutyTimeRepository.GetDefaultAsync(); + if (defaultRound == null) + { + throw new Exception("ไม่พบรอบการลงเวลา Default"); + } + + var employees = new List(); + + foreach (var dd in dateList.Where(x => !x.isHoliday && !x.isWeekEnd)) + { + profiles = await _userProfileRepository.GetAllEmployeeByRootDnaId(rootDnaId.ToString(),dd.date); + foreach (var p in profiles) + { + var count = 1; + var keycloakUserId = p.Keycloak ?? Guid.Empty; + + var timeStamps = await _processUserTimeStampRepository.GetTimestampByDateAsync(keycloakUserId, dd.date); + + var fullName = $"{p.Prefix}{p.FirstName} {p.LastName}"; + + var effectiveDate = await _userDutyTimeRepository.GetLastEffectRound(p.Id, dd.date); + var roundId = effectiveDate != null ? effectiveDate.DutyTimeId : Guid.Empty; + var userRound = await _dutyTimeRepository.GetByIdAsync(roundId); + + var duty = userRound ?? defaultRound; + + // check วันลาของแต่ละคน + var leaveReq = await _leaveRequestRepository.GetLeavePeriodAsync(keycloakUserId, dd.date); + var remarkStr = string.Empty; + var status = string.Empty; + var stampType = string.Empty; + var stampAmount = 0.0; + + if (leaveReq != null) + { + switch (leaveReq.Type.Code.ToUpper()) + { + case "LV-001": + case "LV-002": + case "LV-005": + remarkStr += leaveReq.Type.Name; + var leaveRange = leaveReq.LeaveRange == null ? "" : leaveReq.LeaveRange.ToUpper(); + if (leaveRange == "MORNING") + remarkStr += "ครึ่งวันเช้า"; + else if (leaveRange == "AFTERNOON") + remarkStr += "ครึ่งวันบ่าย"; + + + // var leaveRangeEnd = leaveReq.LeaveRangeEnd == null ? "" : leaveReq.LeaveRangeEnd.ToUpper(); + // if (leaveRangeEnd == "MORNING") + // remarkStr += "ครึ่งวันเช้า"; + // else if (leaveRangeEnd == "AFTERNOON") + // remarkStr += "ครึ่งวันบ่าย"; + + var leaveRangeEnd = leaveReq.LeaveRangeEnd == null ? "" : leaveReq.LeaveRangeEnd.ToUpper(); + if (leaveRange != leaveRangeEnd) + { + if (leaveRangeEnd == "MORNING") + remarkStr += " - ครึ่งวันเช้า"; + else if (leaveRangeEnd == "AFTERNOON") + remarkStr += " - ครึ่งวันบ่าย"; + } + break; + default: + remarkStr += leaveReq.Type.Name; + break; + } + status = "LEAVE"; + stampType = leaveReq.LeaveRange ?? ""; + stampAmount = leaveReq.LeaveRange == "MORNING" || leaveReq.LeaveRangeEnd == "MORNING" ? 0.5 : 1; + } + else + { + if (timeStamps == null) + { + if (dd.date <= DateTime.Now.Date) + { + remarkStr = "ขาดราชการ"; + status = "ABSENT"; + stampType = "FULL_DAY"; + stampAmount = 1; + if (dd.isHoliday == true) + { + remarkStr = $"วันหยุด ({dd.dateRemark})"; + status = "HOLIDAY"; + } + else if (dd.isWeekEnd) + { + remarkStr = dd.dateRemark; + status = "WEEKEND"; + } + } + else remarkStr = ""; + } + else + { + // check status ของการลงเวลา + if (timeStamps.CheckOut != null) + { + if (timeStamps.CheckOutStatus == "ABSENT") + { + remarkStr = "ขาดราชการ" + (!timeStamps.IsLocationCheckOut ? $" (นอกสถานที่:{timeStamps.CheckOutLocationName})".Trim() : ""); + status = "ABSENT"; + stampType = "FULL_DAY"; + stampAmount = 1; + } + else if (timeStamps.CheckInStatus == "ABSENT") + { + remarkStr = "ขาดราชการ" + (!timeStamps.IsLocationCheckIn ? $" (นอกสถานที่:{timeStamps.CheckInLocationName})".Trim() : ""); + status = "ABSENT"; + stampType = "FULL_DAY"; + stampAmount = 1; + } + else if (timeStamps.CheckInStatus == "LATE") + { + remarkStr = "สาย" + (!timeStamps.IsLocationCheckIn ? $" (นอกสถานที่:{timeStamps.CheckInLocationName})".Trim() : ""); + status = "LATE"; + stampType = "FULL_DAY"; + stampAmount = 1; + //lateTotal += 1; + } + else + remarkStr = !timeStamps.IsLocationCheckIn ? $" นอกสถานที่:{timeStamps.CheckInLocationName}".Trim() : ""; + } + else + { + if (timeStamps.CheckInStatus == "ABSENT") + { + status = "ABSENT"; + stampType = "FULL_DAY"; + stampAmount = 1; + remarkStr = "ขาดราชการ" + (!timeStamps.IsLocationCheckIn ? $" (นอกสถานที่:{timeStamps.CheckInLocationName})".Trim() : ""); + } + else if (timeStamps.CheckInStatus == "LATE") + { + status = "LATE"; + stampType = "FULL_DAY"; + stampAmount = 1; + remarkStr = "สาย" + (!timeStamps.IsLocationCheckIn ? $" (นอกสถานที่:{timeStamps.CheckInLocationName})".Trim() : ""); + //lateTotal += 1; + } + else + remarkStr = !timeStamps.IsLocationCheckIn ? $" นอกสถานที่:{timeStamps.CheckInLocationName}".Trim() : ""; + } + } + } + + var emp = new DateResultReport + { + profileId = p.Id.ToString(), + stampDate = dd.date, + stampType = stampType, + stampAmount = stampAmount, + remark = remarkStr, + status = status + }; + + employees.Add(emp); + count++; + } + + // // Write employees to JSON file + // var fileName = $"employees_{DateTime.Now:yyyyMMdd_HHmmss}.txt"; + // var filePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Exports", fileName); + + // // Ensure directory exists + // var directory = Path.GetDirectoryName(filePath); + // if (!string.IsNullOrEmpty(directory) && !Directory.Exists(directory)) + // { + // Directory.CreateDirectory(directory); + // } + + // var jsonOptions = new JsonSerializerOptions + // { + // WriteIndented = true, + // Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping + // }; + + // var jsonContent = JsonSerializer.Serialize(employees, jsonOptions); + // await File.WriteAllTextAsync(filePath, jsonContent); + } + + //call api + var apiPath = $"{_configuration["API"]}/org/profile-employee/absent-late/batch"; + var apiKey = _configuration["API_KEY"]; + var body = new + { + records = employees + }; + + var apiResult = await PostExternalAPIAsync(apiPath, AccessToken ?? "", body, apiKey); + if(apiResult == "") + { + throw new Exception($"เรียก API {apiPath} ไม่สำเร็จ"); + } + } + + public async Task ProcessPendingJobsAsync() { var pendingJobs = await GetPendingJobsAsync(); @@ -424,6 +669,7 @@ namespace BMA.EHR.Application.Repositories.Leaves.TimeAttendants // ทำงานที่ต้องการที่นี่ (เช่น เรียก API, ประมวลผลข้อมูล ฯลฯ) await ProcessTaskAsync(job.RootDnaId,job.StartDate, job.EndDate); + await ProcessEmpTaskAsync(job.RootDnaId,job.StartDate, job.EndDate); // อัปเดตสถานะเป็น Completed await UpdateToCompletedAsync(job.Id); From a50153f32cadc6b0a3dca7ab423e391d62ee81db Mon Sep 17 00:00:00 2001 From: Suphonchai Phoonsawat Date: Tue, 31 Mar 2026 11:26:28 +0700 Subject: [PATCH 152/183] Refactor LeaveProcessJobStatusRepository to enhance leave remark generation logic and update file export functionality with environment path handling --- .../LeaveProcessJobStatusRepository.cs | 255 ++++++++++++------ 1 file changed, 170 insertions(+), 85 deletions(-) diff --git a/BMA.EHR.Application/Repositories/Leaves/TimeAttendants/LeaveProcessJobStatusRepository.cs b/BMA.EHR.Application/Repositories/Leaves/TimeAttendants/LeaveProcessJobStatusRepository.cs index 4a2be392..3962b53e 100644 --- a/BMA.EHR.Application/Repositories/Leaves/TimeAttendants/LeaveProcessJobStatusRepository.cs +++ b/BMA.EHR.Application/Repositories/Leaves/TimeAttendants/LeaveProcessJobStatusRepository.cs @@ -10,6 +10,7 @@ using BMA.EHR.Application.Repositories.MetaData; using BMA.EHR.Application.Responses.Profiles; using BMA.EHR.Domain.Extensions; using BMA.EHR.Domain.Models.Leave.TimeAttendants; +using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; @@ -29,6 +30,7 @@ namespace BMA.EHR.Application.Repositories.Leaves.TimeAttendants private readonly ProcessUserTimeStampRepository _processUserTimeStampRepository; private readonly LeaveRequestRepository _leaveRequestRepository; private readonly IConfiguration _configuration; + private readonly IWebHostEnvironment _env; #endregion @@ -42,7 +44,8 @@ namespace BMA.EHR.Application.Repositories.Leaves.TimeAttendants UserDutyTimeRepository userDutyTimeRepository, ProcessUserTimeStampRepository processUserTimeStampRepository, LeaveRequestRepository leaveRequestRepository, - IConfiguration configuration) : base(dbContext, httpContextAccessor) + IConfiguration configuration, + IWebHostEnvironment env) : base(dbContext, httpContextAccessor) { _dbContext = dbContext; _httpContextAccessor = httpContextAccessor; @@ -53,6 +56,7 @@ namespace BMA.EHR.Application.Repositories.Leaves.TimeAttendants _dutyTimeRepository = dutyTimeRepository; _userDutyTimeRepository = userDutyTimeRepository; _processUserTimeStampRepository = processUserTimeStampRepository; + _env = env; } #endregion @@ -255,34 +259,75 @@ namespace BMA.EHR.Application.Repositories.Leaves.TimeAttendants case "LV-005": remarkStr += leaveReq.Type.Name; var leaveRange = leaveReq.LeaveRange == null ? "" : leaveReq.LeaveRange.ToUpper(); - if (leaveRange == "MORNING") - remarkStr += "ครึ่งวันเช้า"; - else if (leaveRange == "AFTERNOON") - remarkStr += "ครึ่งวันบ่าย"; - - - // var leaveRangeEnd = leaveReq.LeaveRangeEnd == null ? "" : leaveReq.LeaveRangeEnd.ToUpper(); - // if (leaveRangeEnd == "MORNING") - // remarkStr += "ครึ่งวันเช้า"; - // else if (leaveRangeEnd == "AFTERNOON") - // remarkStr += "ครึ่งวันบ่าย"; - - var leaveRangeEnd = leaveReq.LeaveRangeEnd == null ? "" : leaveReq.LeaveRangeEnd.ToUpper(); - if (leaveRange != leaveRangeEnd) + + if(leaveReq.LeaveStartDate.Date == leaveReq.LeaveEndDate.Date) { - if (leaveRangeEnd == "MORNING") - remarkStr += " - ครึ่งวันเช้า"; - else if (leaveRangeEnd == "AFTERNOON") - remarkStr += " - ครึ่งวันบ่าย"; + if (leaveRange == "MORNING") + remarkStr += "ครึ่งวันเช้า"; + else if (leaveRange == "AFTERNOON") + remarkStr += "ครึ่งวันบ่าย"; + + + // var leaveRangeEnd = leaveReq.LeaveRangeEnd == null ? "" : leaveReq.LeaveRangeEnd.ToUpper(); + // if (leaveRangeEnd == "MORNING") + // remarkStr += "ครึ่งวันเช้า"; + // else if (leaveRangeEnd == "AFTERNOON") + // remarkStr += "ครึ่งวันบ่าย"; + + var leaveRangeEnd = leaveReq.LeaveRangeEnd == null ? "" : leaveReq.LeaveRangeEnd.ToUpper(); + if (leaveRange != leaveRangeEnd) + { + if (leaveRangeEnd == "MORNING") + remarkStr += " - ครึ่งวันเช้า"; + else if (leaveRangeEnd == "AFTERNOON") + remarkStr += " - ครึ่งวันบ่าย"; + } } + else + { + if(dd.date == leaveReq.LeaveStartDate.Date) + { + if (leaveRange == "MORNING") + remarkStr += "ครึ่งวันเช้า"; + else if (leaveRange == "AFTERNOON") + remarkStr += "ครึ่งวันบ่าย"; + } + else if(dd.date == leaveReq.LeaveEndDate.Date) + { + var leaveRangeEnd = leaveReq.LeaveRangeEnd == null ? "" : leaveReq.LeaveRangeEnd.ToUpper(); + if (leaveRangeEnd == "MORNING") + remarkStr += "ครึ่งวันเช้า"; + else if (leaveRangeEnd == "AFTERNOON") + remarkStr += "ครึ่งวันบ่าย"; + else + remarkStr += "เต็มวัน"; + } + else + { + remarkStr += "เต็มวัน"; + } + } + + break; default: remarkStr += leaveReq.Type.Name; break; } status = "LEAVE"; - stampType = leaveReq.LeaveRange ?? ""; - stampAmount = leaveReq.LeaveRange == "MORNING" || leaveReq.LeaveRangeEnd == "MORNING" ? 0.5 : 1; + if(leaveReq.LeaveStartDate.Date == dd.date) + { + stampType = leaveReq.LeaveRange ?? ""; + stampAmount = leaveReq.LeaveRange != "ALL" ? 0.5 : 1; + } + else if(leaveReq.LeaveEndDate.Date == dd.date) + { + stampAmount = leaveReq.LeaveRangeEnd != "ALL" ? 0.5 : 1; + stampType = leaveReq.LeaveRangeEnd ?? ""; + } + else + stampAmount = leaveReq.LeaveRange != "ALL" || leaveReq.LeaveRangeEnd != "ALL" ? 0.5 : 1; + if(stampType == "ALL") stampType = "FULL_DAY"; } else { @@ -374,25 +419,25 @@ namespace BMA.EHR.Application.Repositories.Leaves.TimeAttendants count++; } - // // Write employees to JSON file - // var fileName = $"employees_{DateTime.Now:yyyyMMdd_HHmmss}.txt"; - // var filePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Exports", fileName); + // Write employees to JSON file + var fileName = $"employees_{DateTime.Now:yyyyMMdd_HHmmss}.txt"; + var filePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Exports", fileName); - // // Ensure directory exists - // var directory = Path.GetDirectoryName(filePath); - // if (!string.IsNullOrEmpty(directory) && !Directory.Exists(directory)) - // { - // Directory.CreateDirectory(directory); - // } + // Ensure directory exists + var directory = Path.GetDirectoryName(filePath); + if (!string.IsNullOrEmpty(directory) && !Directory.Exists(directory)) + { + Directory.CreateDirectory(directory); + } - // var jsonOptions = new JsonSerializerOptions - // { - // WriteIndented = true, - // Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping - // }; + var jsonOptions = new JsonSerializerOptions + { + WriteIndented = true, + Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping + }; - // var jsonContent = JsonSerializer.Serialize(employees, jsonOptions); - // await File.WriteAllTextAsync(filePath, jsonContent); + var jsonContent = JsonSerializer.Serialize(employees, jsonOptions); + await File.WriteAllTextAsync(filePath, jsonContent); } //call api @@ -499,34 +544,72 @@ namespace BMA.EHR.Application.Repositories.Leaves.TimeAttendants case "LV-005": remarkStr += leaveReq.Type.Name; var leaveRange = leaveReq.LeaveRange == null ? "" : leaveReq.LeaveRange.ToUpper(); - if (leaveRange == "MORNING") - remarkStr += "ครึ่งวันเช้า"; - else if (leaveRange == "AFTERNOON") - remarkStr += "ครึ่งวันบ่าย"; - - - // var leaveRangeEnd = leaveReq.LeaveRangeEnd == null ? "" : leaveReq.LeaveRangeEnd.ToUpper(); - // if (leaveRangeEnd == "MORNING") - // remarkStr += "ครึ่งวันเช้า"; - // else if (leaveRangeEnd == "AFTERNOON") - // remarkStr += "ครึ่งวันบ่าย"; - - var leaveRangeEnd = leaveReq.LeaveRangeEnd == null ? "" : leaveReq.LeaveRangeEnd.ToUpper(); - if (leaveRange != leaveRangeEnd) + + if(leaveReq.LeaveStartDate.Date == leaveReq.LeaveEndDate.Date) { - if (leaveRangeEnd == "MORNING") - remarkStr += " - ครึ่งวันเช้า"; - else if (leaveRangeEnd == "AFTERNOON") - remarkStr += " - ครึ่งวันบ่าย"; + if (leaveRange == "MORNING") + remarkStr += "ครึ่งวันเช้า"; + else if (leaveRange == "AFTERNOON") + remarkStr += "ครึ่งวันบ่าย"; + + + // var leaveRangeEnd = leaveReq.LeaveRangeEnd == null ? "" : leaveReq.LeaveRangeEnd.ToUpper(); + // if (leaveRangeEnd == "MORNING") + // remarkStr += "ครึ่งวันเช้า"; + // else if (leaveRangeEnd == "AFTERNOON") + // remarkStr += "ครึ่งวันบ่าย"; + + var leaveRangeEnd = leaveReq.LeaveRangeEnd == null ? "" : leaveReq.LeaveRangeEnd.ToUpper(); + if (leaveRange != leaveRangeEnd) + { + if (leaveRangeEnd == "MORNING") + remarkStr += " - ครึ่งวันเช้า"; + else if (leaveRangeEnd == "AFTERNOON") + remarkStr += " - ครึ่งวันบ่าย"; + } } + else + { + if(dd.date == leaveReq.LeaveStartDate.Date) + { + if (leaveRange == "MORNING") + remarkStr += "ครึ่งวันเช้า"; + else if (leaveRange == "AFTERNOON") + remarkStr += "ครึ่งวันบ่าย"; + } + else if(dd.date == leaveReq.LeaveEndDate.Date) + { + var leaveRangeEnd = leaveReq.LeaveRangeEnd == null ? "" : leaveReq.LeaveRangeEnd.ToUpper(); + if (leaveRangeEnd == "MORNING") + remarkStr += "ครึ่งวันเช้า"; + else if (leaveRangeEnd == "AFTERNOON") + remarkStr += "ครึ่งวันบ่าย"; + } + else + { + remarkStr += "เต็มวัน"; + } + } break; default: remarkStr += leaveReq.Type.Name; break; } status = "LEAVE"; - stampType = leaveReq.LeaveRange ?? ""; - stampAmount = leaveReq.LeaveRange == "MORNING" || leaveReq.LeaveRangeEnd == "MORNING" ? 0.5 : 1; + if(leaveReq.LeaveStartDate.Date == dd.date) + { + stampType = leaveReq.LeaveRange ?? ""; + stampAmount = leaveReq.LeaveRange != "ALL" ? 0.5 : 1; + } + else if(leaveReq.LeaveEndDate.Date == dd.date) + { + stampAmount = leaveReq.LeaveRangeEnd != "ALL" ? 0.5 : 1; + stampType = leaveReq.LeaveRangeEnd ?? ""; + } + else + stampAmount = leaveReq.LeaveRange != "ALL" || leaveReq.LeaveRangeEnd != "ALL" ? 0.5 : 1; + if(stampType == "ALL") stampType = "FULL_DAY"; + //stampAmount = leaveReq.LeaveRange != "ALL" || leaveReq.LeaveRangeEnd != "ALL" ? 0.5 : 1; } else { @@ -618,40 +701,42 @@ namespace BMA.EHR.Application.Repositories.Leaves.TimeAttendants count++; } - // // Write employees to JSON file - // var fileName = $"employees_{DateTime.Now:yyyyMMdd_HHmmss}.txt"; - // var filePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Exports", fileName); + // Write employees to JSON file + var fileName = $"employees_{DateTime.Now:yyyyMMdd_HHmmss}.txt"; + var filePath = Path.Combine(_env.ContentRootPath, "Exports", fileName); - // // Ensure directory exists - // var directory = Path.GetDirectoryName(filePath); - // if (!string.IsNullOrEmpty(directory) && !Directory.Exists(directory)) - // { - // Directory.CreateDirectory(directory); - // } + // Ensure directory exists + var directory = Path.GetDirectoryName(filePath); + if (!string.IsNullOrEmpty(directory) && !Directory.Exists(directory)) + { + Directory.CreateDirectory(directory); + } - // var jsonOptions = new JsonSerializerOptions - // { - // WriteIndented = true, - // Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping - // }; + var jsonOptions = new JsonSerializerOptions + { + WriteIndented = true, + Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping + }; - // var jsonContent = JsonSerializer.Serialize(employees, jsonOptions); - // await File.WriteAllTextAsync(filePath, jsonContent); + var jsonContent = JsonSerializer.Serialize(employees, jsonOptions); + Console.WriteLine($"Writing file to: {filePath}"); + await File.WriteAllTextAsync(filePath, jsonContent); + Console.WriteLine($"File written successfully: {fileName}"); } //call api - var apiPath = $"{_configuration["API"]}/org/profile-employee/absent-late/batch"; - var apiKey = _configuration["API_KEY"]; - var body = new - { - records = employees - }; + // var apiPath = $"{_configuration["API"]}/org/profile-employee/absent-late/batch"; + // var apiKey = _configuration["API_KEY"]; + // var body = new + // { + // records = employees + // }; - var apiResult = await PostExternalAPIAsync(apiPath, AccessToken ?? "", body, apiKey); - if(apiResult == "") - { - throw new Exception($"เรียก API {apiPath} ไม่สำเร็จ"); - } + // var apiResult = await PostExternalAPIAsync(apiPath, AccessToken ?? "", body, apiKey); + // if(apiResult == "") + // { + // throw new Exception($"เรียก API {apiPath} ไม่สำเร็จ"); + // } } @@ -669,7 +754,7 @@ namespace BMA.EHR.Application.Repositories.Leaves.TimeAttendants // ทำงานที่ต้องการที่นี่ (เช่น เรียก API, ประมวลผลข้อมูล ฯลฯ) await ProcessTaskAsync(job.RootDnaId,job.StartDate, job.EndDate); - await ProcessEmpTaskAsync(job.RootDnaId,job.StartDate, job.EndDate); + //await ProcessEmpTaskAsync(job.RootDnaId,job.StartDate, job.EndDate); // อัปเดตสถานะเป็น Completed await UpdateToCompletedAsync(job.Id); From 932d5e75c795c1eef9bbf165dfbb1e51cfd997f8 Mon Sep 17 00:00:00 2001 From: Suphonchai Phoonsawat Date: Tue, 31 Mar 2026 11:28:12 +0700 Subject: [PATCH 153/183] Refactor LeaveProcessJobStatusRepository to update API endpoint paths and comment out JSON file writing logic --- .../LeaveProcessJobStatusRepository.cs | 90 +++++++++---------- 1 file changed, 45 insertions(+), 45 deletions(-) diff --git a/BMA.EHR.Application/Repositories/Leaves/TimeAttendants/LeaveProcessJobStatusRepository.cs b/BMA.EHR.Application/Repositories/Leaves/TimeAttendants/LeaveProcessJobStatusRepository.cs index 3962b53e..1d45e49c 100644 --- a/BMA.EHR.Application/Repositories/Leaves/TimeAttendants/LeaveProcessJobStatusRepository.cs +++ b/BMA.EHR.Application/Repositories/Leaves/TimeAttendants/LeaveProcessJobStatusRepository.cs @@ -420,28 +420,28 @@ namespace BMA.EHR.Application.Repositories.Leaves.TimeAttendants } // Write employees to JSON file - var fileName = $"employees_{DateTime.Now:yyyyMMdd_HHmmss}.txt"; - var filePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Exports", fileName); + // var fileName = $"employees_{DateTime.Now:yyyyMMdd_HHmmss}.txt"; + // var filePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Exports", fileName); - // Ensure directory exists - var directory = Path.GetDirectoryName(filePath); - if (!string.IsNullOrEmpty(directory) && !Directory.Exists(directory)) - { - Directory.CreateDirectory(directory); - } + // // Ensure directory exists + // var directory = Path.GetDirectoryName(filePath); + // if (!string.IsNullOrEmpty(directory) && !Directory.Exists(directory)) + // { + // Directory.CreateDirectory(directory); + // } - var jsonOptions = new JsonSerializerOptions - { - WriteIndented = true, - Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping - }; + // var jsonOptions = new JsonSerializerOptions + // { + // WriteIndented = true, + // Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping + // }; - var jsonContent = JsonSerializer.Serialize(employees, jsonOptions); - await File.WriteAllTextAsync(filePath, jsonContent); + // var jsonContent = JsonSerializer.Serialize(employees, jsonOptions); + // await File.WriteAllTextAsync(filePath, jsonContent); } //call api - var apiPath = $"{_configuration["API"]}/org/profile/absent-late/batch"; + var apiPath = $"{_configuration["API"]}/org/unauthorize/profile/absent-late/batch"; var apiKey = _configuration["API_KEY"]; var body = new { @@ -702,41 +702,41 @@ namespace BMA.EHR.Application.Repositories.Leaves.TimeAttendants } // Write employees to JSON file - var fileName = $"employees_{DateTime.Now:yyyyMMdd_HHmmss}.txt"; - var filePath = Path.Combine(_env.ContentRootPath, "Exports", fileName); + // var fileName = $"employees_{DateTime.Now:yyyyMMdd_HHmmss}.txt"; + // var filePath = Path.Combine(_env.ContentRootPath, "Exports", fileName); - // Ensure directory exists - var directory = Path.GetDirectoryName(filePath); - if (!string.IsNullOrEmpty(directory) && !Directory.Exists(directory)) - { - Directory.CreateDirectory(directory); - } + // // Ensure directory exists + // var directory = Path.GetDirectoryName(filePath); + // if (!string.IsNullOrEmpty(directory) && !Directory.Exists(directory)) + // { + // Directory.CreateDirectory(directory); + // } - var jsonOptions = new JsonSerializerOptions - { - WriteIndented = true, - Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping - }; + // var jsonOptions = new JsonSerializerOptions + // { + // WriteIndented = true, + // Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping + // }; - var jsonContent = JsonSerializer.Serialize(employees, jsonOptions); - Console.WriteLine($"Writing file to: {filePath}"); - await File.WriteAllTextAsync(filePath, jsonContent); - Console.WriteLine($"File written successfully: {fileName}"); + // var jsonContent = JsonSerializer.Serialize(employees, jsonOptions); + // Console.WriteLine($"Writing file to: {filePath}"); + // await File.WriteAllTextAsync(filePath, jsonContent); + // Console.WriteLine($"File written successfully: {fileName}"); } - //call api - // var apiPath = $"{_configuration["API"]}/org/profile-employee/absent-late/batch"; - // var apiKey = _configuration["API_KEY"]; - // var body = new - // { - // records = employees - // }; + // call api + var apiPath = $"{_configuration["API"]}/org/unauthorize/profile-employee/absent-late/batch"; + var apiKey = _configuration["API_KEY"]; + var body = new + { + records = employees + }; - // var apiResult = await PostExternalAPIAsync(apiPath, AccessToken ?? "", body, apiKey); - // if(apiResult == "") - // { - // throw new Exception($"เรียก API {apiPath} ไม่สำเร็จ"); - // } + var apiResult = await PostExternalAPIAsync(apiPath, AccessToken ?? "", body, apiKey); + if(apiResult == "") + { + throw new Exception($"เรียก API {apiPath} ไม่สำเร็จ"); + } } From 47c0cfc62a9af2db2b022b14e62f128609f55875 Mon Sep 17 00:00:00 2001 From: Suphonchai Phoonsawat Date: Tue, 31 Mar 2026 11:32:17 +0700 Subject: [PATCH 154/183] Refactor leave remark generation logic in LeaveReportController to handle single and multi-day leave requests more accurately --- .../Controllers/LeaveReportController.cs | 146 ++++++++++++++---- 1 file changed, 120 insertions(+), 26 deletions(-) diff --git a/BMA.EHR.Leave/Controllers/LeaveReportController.cs b/BMA.EHR.Leave/Controllers/LeaveReportController.cs index e96fc0a0..a0d0d4b1 100644 --- a/BMA.EHR.Leave/Controllers/LeaveReportController.cs +++ b/BMA.EHR.Leave/Controllers/LeaveReportController.cs @@ -2091,20 +2091,68 @@ namespace BMA.EHR.Leave.Service.Controllers case "LV-005": remarkStr += leaveReq.Type.Name; var leaveRange = leaveReq.LeaveRange == null ? "" : leaveReq.LeaveRange.ToUpper(); - if (leaveRange == "MORNING") - remarkStr += "ครึ่งวันเช้า"; - else if (leaveRange == "AFTERNOON") - remarkStr += "ครึ่งวันบ่าย"; - - var leaveRangeEnd = leaveReq.LeaveRangeEnd == null ? "" : leaveReq.LeaveRangeEnd.ToUpper(); - if (leaveRange != leaveRangeEnd) + if(leaveReq.LeaveStartDate.Date == leaveReq.LeaveEndDate.Date) { - if (leaveRangeEnd == "MORNING") - remarkStr += " - ครึ่งวันเช้า"; - else if (leaveRangeEnd == "AFTERNOON") - remarkStr += " - ครึ่งวันบ่าย"; + if (leaveRange == "MORNING") + remarkStr += "ครึ่งวันเช้า"; + else if (leaveRange == "AFTERNOON") + remarkStr += "ครึ่งวันบ่าย"; + + + // var leaveRangeEnd = leaveReq.LeaveRangeEnd == null ? "" : leaveReq.LeaveRangeEnd.ToUpper(); + // if (leaveRangeEnd == "MORNING") + // remarkStr += "ครึ่งวันเช้า"; + // else if (leaveRangeEnd == "AFTERNOON") + // remarkStr += "ครึ่งวันบ่าย"; + + var leaveRangeEnd = leaveReq.LeaveRangeEnd == null ? "" : leaveReq.LeaveRangeEnd.ToUpper(); + if (leaveRange != leaveRangeEnd) + { + if (leaveRangeEnd == "MORNING") + remarkStr += " - ครึ่งวันเช้า"; + else if (leaveRangeEnd == "AFTERNOON") + remarkStr += " - ครึ่งวันบ่าย"; + } } + else + { + if(dd.date == leaveReq.LeaveStartDate.Date) + { + if (leaveRange == "MORNING") + remarkStr += "ครึ่งวันเช้า"; + else if (leaveRange == "AFTERNOON") + remarkStr += "ครึ่งวันบ่าย"; + } + else if(dd.date == leaveReq.LeaveEndDate.Date) + { + var leaveRangeEnd = leaveReq.LeaveRangeEnd == null ? "" : leaveReq.LeaveRangeEnd.ToUpper(); + if (leaveRangeEnd == "MORNING") + remarkStr += "ครึ่งวันเช้า"; + else if (leaveRangeEnd == "AFTERNOON") + remarkStr += "ครึ่งวันบ่าย"; + } + else + { + remarkStr += "เต็มวัน"; + } + } + + + // if (leaveRange == "MORNING") + // remarkStr += "ครึ่งวันเช้า"; + // else if (leaveRange == "AFTERNOON") + // remarkStr += "ครึ่งวันบ่าย"; + + + // var leaveRangeEnd = leaveReq.LeaveRangeEnd == null ? "" : leaveReq.LeaveRangeEnd.ToUpper(); + // if (leaveRange != leaveRangeEnd) + // { + // if (leaveRangeEnd == "MORNING") + // remarkStr += " - ครึ่งวันเช้า"; + // else if (leaveRangeEnd == "AFTERNOON") + // remarkStr += " - ครึ่งวันบ่าย"; + // } break; default: remarkStr += leaveReq.Type.Name; @@ -2421,26 +2469,72 @@ namespace BMA.EHR.Leave.Service.Controllers case "LV-005": remarkStr += leaveReq.Type.Name; var leaveRange = leaveReq.LeaveRange == null ? "" : leaveReq.LeaveRange.ToUpper(); - if (leaveRange == "MORNING") - remarkStr += "ครึ่งวันเช้า"; - else if (leaveRange == "AFTERNOON") - remarkStr += "ครึ่งวันบ่าย"; + if(leaveReq.LeaveStartDate.Date == leaveReq.LeaveEndDate.Date) + { + if (leaveRange == "MORNING") + remarkStr += "ครึ่งวันเช้า"; + else if (leaveRange == "AFTERNOON") + remarkStr += "ครึ่งวันบ่าย"; - // var leaveRangeEnd = leaveReq.LeaveRangeEnd == null ? "" : leaveReq.LeaveRangeEnd.ToUpper(); - // if (leaveRangeEnd == "MORNING") + // var leaveRangeEnd = leaveReq.LeaveRangeEnd == null ? "" : leaveReq.LeaveRangeEnd.ToUpper(); + // if (leaveRangeEnd == "MORNING") + // remarkStr += "ครึ่งวันเช้า"; + // else if (leaveRangeEnd == "AFTERNOON") + // remarkStr += "ครึ่งวันบ่าย"; + + var leaveRangeEnd = leaveReq.LeaveRangeEnd == null ? "" : leaveReq.LeaveRangeEnd.ToUpper(); + if (leaveRange != leaveRangeEnd) + { + if (leaveRangeEnd == "MORNING") + remarkStr += " - ครึ่งวันเช้า"; + else if (leaveRangeEnd == "AFTERNOON") + remarkStr += " - ครึ่งวันบ่าย"; + } + } + else + { + if(dd.date == leaveReq.LeaveStartDate.Date) + { + if (leaveRange == "MORNING") + remarkStr += "ครึ่งวันเช้า"; + else if (leaveRange == "AFTERNOON") + remarkStr += "ครึ่งวันบ่าย"; + } + else if(dd.date == leaveReq.LeaveEndDate.Date) + { + var leaveRangeEnd = leaveReq.LeaveRangeEnd == null ? "" : leaveReq.LeaveRangeEnd.ToUpper(); + if (leaveRangeEnd == "MORNING") + remarkStr += "ครึ่งวันเช้า"; + else if (leaveRangeEnd == "AFTERNOON") + remarkStr += "ครึ่งวันบ่าย"; + } + else + { + remarkStr += "เต็มวัน"; + } + } + + // if (leaveRange == "MORNING") // remarkStr += "ครึ่งวันเช้า"; - // else if (leaveRangeEnd == "AFTERNOON") + // else if (leaveRange == "AFTERNOON") // remarkStr += "ครึ่งวันบ่าย"; - var leaveRangeEnd = leaveReq.LeaveRangeEnd == null ? "" : leaveReq.LeaveRangeEnd.ToUpper(); - if (leaveRange != leaveRangeEnd) - { - if (leaveRangeEnd == "MORNING") - remarkStr += " - ครึ่งวันเช้า"; - else if (leaveRangeEnd == "AFTERNOON") - remarkStr += " - ครึ่งวันบ่าย"; - } + + // // var leaveRangeEnd = leaveReq.LeaveRangeEnd == null ? "" : leaveReq.LeaveRangeEnd.ToUpper(); + // // if (leaveRangeEnd == "MORNING") + // // remarkStr += "ครึ่งวันเช้า"; + // // else if (leaveRangeEnd == "AFTERNOON") + // // remarkStr += "ครึ่งวันบ่าย"; + + // var leaveRangeEnd = leaveReq.LeaveRangeEnd == null ? "" : leaveReq.LeaveRangeEnd.ToUpper(); + // if (leaveRange != leaveRangeEnd) + // { + // if (leaveRangeEnd == "MORNING") + // remarkStr += " - ครึ่งวันเช้า"; + // else if (leaveRangeEnd == "AFTERNOON") + // remarkStr += " - ครึ่งวันบ่าย"; + // } break; default: remarkStr += leaveReq.Type.Name; From 8fa105606bb41862f1c378a4d5fec51fe98c3271 Mon Sep 17 00:00:00 2001 From: Suphonchai Phoonsawat Date: Tue, 31 Mar 2026 11:45:48 +0700 Subject: [PATCH 155/183] Refactor LeaveProcessJobStatusRepository to filter employee records by status and ensure proper task processing --- .../TimeAttendants/LeaveProcessJobStatusRepository.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/BMA.EHR.Application/Repositories/Leaves/TimeAttendants/LeaveProcessJobStatusRepository.cs b/BMA.EHR.Application/Repositories/Leaves/TimeAttendants/LeaveProcessJobStatusRepository.cs index 1d45e49c..5e9944ad 100644 --- a/BMA.EHR.Application/Repositories/Leaves/TimeAttendants/LeaveProcessJobStatusRepository.cs +++ b/BMA.EHR.Application/Repositories/Leaves/TimeAttendants/LeaveProcessJobStatusRepository.cs @@ -445,7 +445,7 @@ namespace BMA.EHR.Application.Repositories.Leaves.TimeAttendants var apiKey = _configuration["API_KEY"]; var body = new { - records = employees + records = employees.Where(x => x.status == "ABSENT" || x.status == "LATE").ToList() }; var apiResult = await PostExternalAPIAsync(apiPath, AccessToken ?? "", body, apiKey); @@ -729,7 +729,7 @@ namespace BMA.EHR.Application.Repositories.Leaves.TimeAttendants var apiKey = _configuration["API_KEY"]; var body = new { - records = employees + records = employees.Where(x => x.status == "ABSENT" || x.status == "LATE").ToList() }; var apiResult = await PostExternalAPIAsync(apiPath, AccessToken ?? "", body, apiKey); @@ -754,7 +754,7 @@ namespace BMA.EHR.Application.Repositories.Leaves.TimeAttendants // ทำงานที่ต้องการที่นี่ (เช่น เรียก API, ประมวลผลข้อมูล ฯลฯ) await ProcessTaskAsync(job.RootDnaId,job.StartDate, job.EndDate); - //await ProcessEmpTaskAsync(job.RootDnaId,job.StartDate, job.EndDate); + await ProcessEmpTaskAsync(job.RootDnaId,job.StartDate, job.EndDate); // อัปเดตสถานะเป็น Completed await UpdateToCompletedAsync(job.Id); From bf6ea555fcfd846102b02d19d7c5d63f53f7b4e3 Mon Sep 17 00:00:00 2001 From: harid Date: Tue, 31 Mar 2026 14:23:51 +0700 Subject: [PATCH 156/183] =?UTF-8?q?fix=20=E0=B8=A3=E0=B8=B2=E0=B8=A2?= =?UTF-8?q?=E0=B8=87=E0=B8=B2=E0=B8=99=E0=B8=A1=E0=B8=B2=E0=B8=AA=E0=B8=B2?= =?UTF-8?q?=E0=B8=A2=20=E0=B8=82=E0=B9=89=E0=B8=AD=E0=B8=A1=E0=B8=B9?= =?UTF-8?q?=E0=B8=A5=E0=B9=84=E0=B8=A1=E0=B9=88=E0=B9=81=E0=B8=AA=E0=B8=94?= =?UTF-8?q?=E0=B8=87=20#2395?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../TimeAttendants/ProcessUserTimeStampRepository.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/BMA.EHR.Application/Repositories/Leaves/TimeAttendants/ProcessUserTimeStampRepository.cs b/BMA.EHR.Application/Repositories/Leaves/TimeAttendants/ProcessUserTimeStampRepository.cs index e1a25780..77aba421 100644 --- a/BMA.EHR.Application/Repositories/Leaves/TimeAttendants/ProcessUserTimeStampRepository.cs +++ b/BMA.EHR.Application/Repositories/Leaves/TimeAttendants/ProcessUserTimeStampRepository.cs @@ -191,11 +191,11 @@ namespace BMA.EHR.Application.Repositories.Leaves.TimeAttendants if (role == "ROOT" || role == "OWNER" || role == "CHILD" || role == "BROTHER" || role == "PARENT") { data = data.Where(x => - nodeByReq == 4 ? x.Child4Id == Guid.Parse(nodeIdByReq) : - nodeByReq == 3 ? x.Child3Id == Guid.Parse(nodeIdByReq) : - nodeByReq == 2 ? x.Child2Id == Guid.Parse(nodeIdByReq) : - nodeByReq == 1 ? x.Child1Id == Guid.Parse(nodeIdByReq) : - nodeByReq == 0 ? x.RootId == Guid.Parse(nodeIdByReq) : true + nodeByReq == 4 ? x.Child4DnaId == Guid.Parse(nodeIdByReq) : + nodeByReq == 3 ? x.Child3DnaId == Guid.Parse(nodeIdByReq) : + nodeByReq == 2 ? x.Child2DnaId == Guid.Parse(nodeIdByReq) : + nodeByReq == 1 ? x.Child1DnaId == Guid.Parse(nodeIdByReq) : + nodeByReq == 0 ? x.RootDnaId == Guid.Parse(nodeIdByReq) : true ).ToList(); } return data; From 1cf780ecd0d9c47edf4f5cfd52e8f0c3ee0679db Mon Sep 17 00:00:00 2001 From: Suphonchai Phoonsawat Date: Wed, 1 Apr 2026 12:24:00 +0700 Subject: [PATCH 157/183] Refactor LeaveController to streamline profile retrieval and duty time handling --- BMA.EHR.Leave/Controllers/LeaveController.cs | 9 ++++---- BMA.EHR.Leave/appsettings.json | 23 +++++++++++++------- 2 files changed, 19 insertions(+), 13 deletions(-) diff --git a/BMA.EHR.Leave/Controllers/LeaveController.cs b/BMA.EHR.Leave/Controllers/LeaveController.cs index 799f3558..0a423d60 100644 --- a/BMA.EHR.Leave/Controllers/LeaveController.cs +++ b/BMA.EHR.Leave/Controllers/LeaveController.cs @@ -3277,16 +3277,15 @@ namespace BMA.EHR.Leave.Service.Controllers foreach (var data in rawDataPaged) { var profile = await _userProfileRepository.GetProfileByKeycloakIdNewAsync(data.KeycloakUserId, AccessToken); - if (profile == null) + UserDutyTime? effectiveDate = null; + if (profile != null) { - return Error(GlobalMessages.DataNotFound, StatusCodes.Status404NotFound); + effectiveDate = await _userDutyTimeRepository.GetLastEffectRound(profile.Id); + //return Error($"{data.Id} PF{data.FirstName} {data.LastName} : {GlobalMessages.DataNotFound}", StatusCodes.Status404NotFound); } //var userRound = await _dutyTimeRepository.GetByIdAsync(profile.DutyTimeId ?? Guid.Empty); - - var effectiveDate = await _userDutyTimeRepository.GetLastEffectRound(profile.Id); var roundId = effectiveDate != null ? effectiveDate.DutyTimeId : Guid.Empty; var userRound = await _dutyTimeRepository.GetByIdAsync(roundId); - var checkInData = await _userTimeStampRepository.GetTimestampByDateAsync(data.KeycloakUserId, data.CheckDate); var duty = userRound ?? getDefaultRound; diff --git a/BMA.EHR.Leave/appsettings.json b/BMA.EHR.Leave/appsettings.json index e55f7ac0..ee356491 100644 --- a/BMA.EHR.Leave/appsettings.json +++ b/BMA.EHR.Leave/appsettings.json @@ -23,16 +23,15 @@ "ExamConnection": "Server=192.168.1.63;User ID=root;Password=12345678;Port=3306;Database=hrms_exam;Allow User Variables=True;Convert Zero Datetime=True;Pooling=True;", "LeaveConnection": "Server=192.168.1.63;User ID=root;Password=12345678;Port=3306;Database=hrms_leave;Allow User Variables=True;Convert Zero Datetime=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;", - //"LeaveConnection": "server=172.27.17.68;user=user;password=cDldaqkwESWvuZ37Gr0n;port=3306;database=hrms_leave;Convert Zero Datetime=True;Allow User Variables=true;Pooling=True;" + // "DefaultConnection": "server=172.27.17.68;user=root;password=ey2qVVyyqGYw8CyA7h8X72559r2Ad84K;port=3306;database=hrms;Convert Zero Datetime=True;Allow User Variables=true;Pooling=True;Connection Timeout=180;", + // "ExamConnection": "server=172.27.17.68;user=root;password=ey2qVVyyqGYw8CyA7h8X72559r2Ad84K;port=3306;database=hrms_exam;Convert Zero Datetime=True;Allow User Variables=true;Pooling=True;Connection Timeout=180;", + // "LeaveConnection": "server=172.27.17.68;user=root;password=ey2qVVyyqGYw8CyA7h8X72559r2Ad84K;port=3306;database=hrms_leave;Convert Zero Datetime=True;Allow User Variables=true;Pooling=True;Connection Timeout=180;" }, "Jwt": { - //"Key": "HP-FnQMUj9msHMSD3T9HtdEnphAKoCJLEl85CIqROFI", "Key": "j7C9RO_p4nRtuwCH4z9Db_A_6We42tkD_p4lZtDrezc", "Issuer": "https://hrmsbkk-id.case-collection.com/realms/hrms" - //"Key": "xY2VR-EFvvNPsMs39u8ooVBWQL6mPwrNJOh3koJFTgU", - //"Issuer": "https://hrms-id.bangkok.go.th/realms/hrms" + // "Key": "xY2VR-EFvvNPsMs39u8ooVBWQL6mPwrNJOh3koJFTgU", + // "Issuer": "https://hrms-id.bangkok.go.th/realms/hrms" }, "EPPlus": { "ExcelPackage": { @@ -55,6 +54,11 @@ "Password": "12345678", "Queue": "hrms-checkin-queue-dev", "URL": "http://192.168.1.63:9122/api/queues/%2F/" + // "Host": "172.27.17.68", + // "User": "admin", + // "Password": "admin123456", + // "Queue": "hrms-checkin-queue", + // "URL": "http://172.27.17.68:9122/api/queues/%2F/" }, "Mail": { "Server": "mail.bangkok.go.th", @@ -68,7 +72,10 @@ "API": "https://hrmsbkk.case-collection.com/api/v1", "APIV2": "https://hrmsbkk.case-collection.com/api/v2", "VITE_URL_MGT": "https://hrmsbkk-mgt.case-collection.com", - //"API": "https://bma-ehr.frappet.synology.me/api/v1", - //"API": "https://bma-hrms.bangkok.go.th/api/v1", + // "Domain": "https://hrms-exam.bangkok.go.th", + // "APIPROBATION": "https://hrms.bangkok.go.th/api/v1/probation", + // "API": "https://hrms.bangkok.go.th/api/v1", + // "APIV2": "https://hrms.bangkok.go.th/api/v2", + // "VITE_URL_MGT": "https://hrms-mgt.bangkok.go.th", "API_KEY": "fKRL16yyEgbyTEJdsMw2h64tGSCmkW685PRtM3CygzX1JOSdptT9UJtpgWwKM8FybRTJups3GTFwj27ZRvlPdIkv3XgCoVJaD5LmR06ozuEPvCCRSdp2WFthg08V5xHc56fTPfZLpr1VmXrhd6dvYhHIqKkQUJR02Rlkss11cLRWEQOssEFVA4xdu2J5DIRO1EM5m7wRRvEwcDB4mYRXD9HH52SMq6iYqUWEWsMwLdbk7QW9yYESUEuzMW5gWrb6vIeWZxJV5bTz1PcWUyR7eO9Fyw1F5DiQYc9JgzTC1mW7cv31fEtTtrfbJYKIb5EbWilqIEUKC6A0UKBDDek35ML0006cqRVm0pvdOH6jeq7VQyYrhdXe59dBEyhYGUIfozoVBvW7Up4QBuOMjyPjSqJPlMBKwaseptfrblxQV1AOOivSBpf1ZcQyOZ8JktRtKUDSuXsmG0lsXwFlI3JCeSHdpVdgZWFYcJPegqfrB6KotR02t9AVkpLs1ZWrixwz" } From 8ea572d46ce7c42a74b98b3fc4c8462bdc1969e2 Mon Sep 17 00:00:00 2001 From: Suphonchai Phoonsawat Date: Wed, 1 Apr 2026 12:30:42 +0700 Subject: [PATCH 158/183] Refactor LeaveReportController to improve duty time retrieval and handle default round logic --- .../Controllers/LeaveReportController.cs | 37 ++++++++++++++----- 1 file changed, 27 insertions(+), 10 deletions(-) diff --git a/BMA.EHR.Leave/Controllers/LeaveReportController.cs b/BMA.EHR.Leave/Controllers/LeaveReportController.cs index a0d0d4b1..9c91f891 100644 --- a/BMA.EHR.Leave/Controllers/LeaveReportController.cs +++ b/BMA.EHR.Leave/Controllers/LeaveReportController.cs @@ -7,6 +7,7 @@ using BMA.EHR.Application.Responses.Profiles; using BMA.EHR.Domain.Common; using BMA.EHR.Domain.Extensions; using BMA.EHR.Domain.Models.Leave.Requests; +using BMA.EHR.Domain.Models.Leave.TimeAttendants; using BMA.EHR.Domain.Shared; using BMA.EHR.Leave.Service.DTOs.Reports; using Microsoft.AspNetCore.Authorization; @@ -2855,25 +2856,41 @@ namespace BMA.EHR.Leave.Service.Controllers { nodeId = profileAdmin?.RootDnaId; } + var getDefaultRound = await _dutyTimeRepository.GetDefaultAsync(); + if (getDefaultRound == null) + { + return Error("ไม่พบรอบลงเวลา Default", StatusCodes.Status404NotFound); + } var userTimeStamps = await _processUserTimeStampRepository.GetTimestampByDateLateAsync(type.Trim().ToUpper(), role, nodeId, profileAdmin.Node, req.nodeId, req.node, req.StartDate, req.EndDate); foreach (var p in userTimeStamps) { var fullName = $"{p.Prefix}{p.FirstName} {p.LastName}"; - var effectiveDate = await _userDutyTimeRepository.GetLastEffectRound(p.Id); + UserDutyTime? effectiveDate = null; + + effectiveDate = await _userDutyTimeRepository.GetLastEffectRound(p.Id); + //return Error($"{data.Id} PF{data.FirstName} {data.LastName} : {GlobalMessages.DataNotFound}", StatusCodes.Status404NotFound); + + //var userRound = await _dutyTimeRepository.GetByIdAsync(profile.DutyTimeId ?? Guid.Empty); var roundId = effectiveDate != null ? effectiveDate.DutyTimeId : Guid.Empty; var userRound = await _dutyTimeRepository.GetByIdAsync(roundId); - var duty = userRound; - if (duty == null) - { - duty = await _dutyTimeRepository.GetDefaultAsync(); - if (duty == null) - { - return Error("ไม่พบรอบการลงเวลา Default", StatusCodes.Status404NotFound); - } - } + var duty = userRound ?? getDefaultRound; + + // var effectiveDate = await _userDutyTimeRepository.GetLastEffectRound(p.Id); + // var roundId = effectiveDate != null ? effectiveDate.DutyTimeId : Guid.Empty; + // var userRound = await _dutyTimeRepository.GetByIdAsync(roundId); + + // var duty = userRound; + // if (duty == null) + // { + // duty = await _dutyTimeRepository.GetDefaultAsync(); + // if (duty == null) + // { + // return Error("ไม่พบรอบการลงเวลา Default", StatusCodes.Status404NotFound); + // } + // } DateTime? checkIn = p.CheckIn; DateTime? checkOut = p.CheckOut ?? null; var emp = new From ea694bfda2a3b066c00bc40b0958e1ce8caee1e8 Mon Sep 17 00:00:00 2001 From: Suphonchai Phoonsawat Date: Thu, 2 Apr 2026 09:51:45 +0700 Subject: [PATCH 159/183] Fix effective date retrieval in LeaveReportController by using ProfileId instead of Id #2400 --- BMA.EHR.Leave/Controllers/LeaveReportController.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BMA.EHR.Leave/Controllers/LeaveReportController.cs b/BMA.EHR.Leave/Controllers/LeaveReportController.cs index 9c91f891..7e4b7b56 100644 --- a/BMA.EHR.Leave/Controllers/LeaveReportController.cs +++ b/BMA.EHR.Leave/Controllers/LeaveReportController.cs @@ -2869,7 +2869,7 @@ namespace BMA.EHR.Leave.Service.Controllers UserDutyTime? effectiveDate = null; - effectiveDate = await _userDutyTimeRepository.GetLastEffectRound(p.Id); + effectiveDate = await _userDutyTimeRepository.GetLastEffectRound(p.ProfileId ?? Guid.Empty); //return Error($"{data.Id} PF{data.FirstName} {data.LastName} : {GlobalMessages.DataNotFound}", StatusCodes.Status404NotFound); //var userRound = await _dutyTimeRepository.GetByIdAsync(profile.DutyTimeId ?? Guid.Empty); From 69b89dfc90d035a7e04004c8297003d03e6271f2 Mon Sep 17 00:00:00 2001 From: Suphonchai Phoonsawat Date: Thu, 2 Apr 2026 11:00:14 +0700 Subject: [PATCH 160/183] Add GetOcByNodeId method to UserProfileRepository and update InsigniaManageController to use it #2389 --- .../Repositories/UserProfileRepository.cs | 32 +++++++++++++++++++ .../Controllers/InsigniaManageController.cs | 9 +++++- 2 files changed, 40 insertions(+), 1 deletion(-) diff --git a/BMA.EHR.Application/Repositories/UserProfileRepository.cs b/BMA.EHR.Application/Repositories/UserProfileRepository.cs index 4ae8bcbe..4f081f77 100644 --- a/BMA.EHR.Application/Repositories/UserProfileRepository.cs +++ b/BMA.EHR.Application/Repositories/UserProfileRepository.cs @@ -1259,6 +1259,38 @@ namespace BMA.EHR.Application.Repositories } } + public GetOrganizationResponseDTO? GetOcByNodeId(Guid ocId, int level, string? accessToken) + { + try + { + var apiPath = $"{_configuration["API"]}/org/find/all"; + var apiKey = _configuration["API_KEY"]; + var body = new + { + nodeId = ocId, + node = level + + }; + + var apiResult = PostExternalAPIAsync(apiPath, accessToken ?? "", body, apiKey).Result; + if (apiResult != null) + { + var raw = JsonConvert.DeserializeObject(apiResult); + if (raw != null && raw.Result != null) + { + return raw.Result; + } + } + + return null; + } + catch + { + throw; + } + } + + public GetOrganizationResponseDTO? GetOc(Guid ocId, int level, string? accessToken) { try diff --git a/BMA.EHR.Insignia/Controllers/InsigniaManageController.cs b/BMA.EHR.Insignia/Controllers/InsigniaManageController.cs index 7d3c9ae4..6c0b809f 100644 --- a/BMA.EHR.Insignia/Controllers/InsigniaManageController.cs +++ b/BMA.EHR.Insignia/Controllers/InsigniaManageController.cs @@ -331,7 +331,7 @@ namespace BMA.EHR.Insignia.Service.Controllers if (req.Total + total > insigniaManage.Total) return Error(GlobalMessages.InsigniaManageOrgLimit); - var ocData = _userProfileRepository.GetOc(req.OrganizationOrganizationId, 0, AccessToken); + var ocData = _userProfileRepository.GetOcByNodeId(req.OrganizationOrganizationId, 0, AccessToken); var root = ocData?.Root ?? null; var rootDnaId = ocData?.RootDnaId ?? null; await _context.InsigniaManageOrganiations.AddAsync( @@ -407,6 +407,10 @@ namespace BMA.EHR.Insignia.Service.Controllers if (uppdated == null) return Error(GlobalMessages.InsigniaManageNotFound); + var ocData = _userProfileRepository.GetOcByNodeId(uppdated.OrganizationId, 0, AccessToken); + var root = ocData?.Root ?? null; + var rootDnaId = ocData?.RootDnaId ?? null; + var insigniaManage = await _context.InsigniaManages.AsQueryable() .Include(x => x.InsigniaManageOrganiations) .FirstOrDefaultAsync(x => x.Id == uppdated.InsigniaManage.Id); @@ -416,6 +420,9 @@ namespace BMA.EHR.Insignia.Service.Controllers if (req.Total + total > insigniaManage.Total) return Error(GlobalMessages.InsigniaManageOrgLimit); + uppdated.Organization = root; + uppdated.RootDnaId = rootDnaId; + uppdated.Total = req.Total; uppdated.LastUpdateFullName = FullName ?? "System Administrator"; uppdated.LastUpdateUserId = UserId ?? ""; From a4a5d1320352f4994de2cf19fea6c8bd5e107424 Mon Sep 17 00:00:00 2001 From: Suphonchai Phoonsawat Date: Thu, 2 Apr 2026 11:09:36 +0700 Subject: [PATCH 161/183] Update InsigniaRequestController to use GetOcByNodeId and include RootDnaId in insigniaNoteProfile #2390 --- BMA.EHR.Insignia/Controllers/InsigniaRequestController.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/BMA.EHR.Insignia/Controllers/InsigniaRequestController.cs b/BMA.EHR.Insignia/Controllers/InsigniaRequestController.cs index 005f9882..81ffe1f4 100644 --- a/BMA.EHR.Insignia/Controllers/InsigniaRequestController.cs +++ b/BMA.EHR.Insignia/Controllers/InsigniaRequestController.cs @@ -3146,7 +3146,10 @@ namespace BMA.EHR.Insignia.Service.Controllers var doc = await _documentService.UploadFileAsync(file, file.FileName); insigniaNoteProfile.DocReceiveInsignia = doc; } - var root = _userProfileRepository.GetOc(req.OrgId, 0, AccessToken)?.Root ?? null; + + var orgData = _userProfileRepository.GetOcByNodeId(req.OrgId,0, AccessToken); + var root = orgData?.Root ?? null; + var rootDnaId = orgData?.RootDnaId ?? null; if (req.OrgId != Guid.Parse("00000000-0000-0000-0000-000000000000")) { if (root == null) @@ -3157,6 +3160,7 @@ namespace BMA.EHR.Insignia.Service.Controllers root = "สำนักนายกรัฐมนตรี"; } insigniaNoteProfile.OrgReceiveInsignia = root; + insigniaNoteProfile.RootDnaId = rootDnaId; insigniaNoteProfile.OrgReceiveInsigniaId = req.OrgId; insigniaNoteProfile.DateReceiveInsignia = req.Date; insigniaNoteProfile.LastUpdateFullName = FullName ?? "System Administrator"; From 6b8eddcbc0a92943221c411ca610d31f29993e28 Mon Sep 17 00:00:00 2001 From: Suphonchai Phoonsawat Date: Thu, 2 Apr 2026 11:36:59 +0700 Subject: [PATCH 162/183] Add Insignia launch configuration and improve null checks in InsigniaRequestController #2388 --- .vscode/launch.json | 25 +++++++++++++++++++ .../Controllers/InsigniaRequestController.cs | 2 ++ 2 files changed, 27 insertions(+) diff --git a/.vscode/launch.json b/.vscode/launch.json index 205e817c..6be3e592 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -26,6 +26,31 @@ "/Views": "${workspaceFolder}/Views" } }, + { + // Use IntelliSense to find out which attributes exist for C# debugging + // Use hover for the description of the existing attributes + // For further information visit https://github.com/dotnet/vscode-csharp/blob/main/debugger-launchjson.md. + "name": ".NET Core Launch (web) - Insignia", + "type": "coreclr", + "request": "launch", + "preLaunchTask": "build", + // If you have changed target frameworks, make sure to update the program path. + "program": "${workspaceFolder}/BMA.EHR.Insignia/bin/Debug/net7.0/BMA.EHR.Insignia.dll", + "args": [], + "cwd": "${workspaceFolder}/BMA.EHR.Insignia", + "stopAtEntry": false, + // Enable launching a web browser when ASP.NET Core starts. For more information: https://aka.ms/VSCode-CS-LaunchJson-WebBrowser + "serverReadyAction": { + "action": "openExternally", + "pattern": "\\bNow listening on:\\s+(https?://\\S+)" + }, + "env": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "sourceFileMap": { + "/Views": "${workspaceFolder}/Views" + } + }, { "name": ".NET Core Attach", "type": "coreclr", diff --git a/BMA.EHR.Insignia/Controllers/InsigniaRequestController.cs b/BMA.EHR.Insignia/Controllers/InsigniaRequestController.cs index 81ffe1f4..0aa8a218 100644 --- a/BMA.EHR.Insignia/Controllers/InsigniaRequestController.cs +++ b/BMA.EHR.Insignia/Controllers/InsigniaRequestController.cs @@ -2641,6 +2641,8 @@ namespace BMA.EHR.Insignia.Service.Controllers { if (item.CitizanId == null) continue; var _profile = await _userProfileRepository.GetOfficerProfileByCitizenId(item.CitizanId, AccessToken); + if (_profile == null) + continue; var profile = insigniaNote.InsigniaNoteProfiles.FirstOrDefault(x => x.ProfileId == _profile.Id); if (profile == null) { From 06956284d7b14ebeb88f2a6a82b1f3a2c28e6195 Mon Sep 17 00:00:00 2001 From: adisak Date: Thu, 2 Apr 2026 17:50:18 +0700 Subject: [PATCH 163/183] #2381 --- .../Controllers/RetirementResignController.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BMA.EHR.Retirement.Service/Controllers/RetirementResignController.cs b/BMA.EHR.Retirement.Service/Controllers/RetirementResignController.cs index 7b8d27b1..147f1adb 100644 --- a/BMA.EHR.Retirement.Service/Controllers/RetirementResignController.cs +++ b/BMA.EHR.Retirement.Service/Controllers/RetirementResignController.cs @@ -165,7 +165,7 @@ namespace BMA.EHR.Retirement.Service.Controllers p.ApproveReason, p.RejectReason, p.CancelReason, - p.Status, + status = p.RetirementResignCancels.FirstOrDefault() == null ? p.Status : p.RetirementResignCancels.FirstOrDefault().Status, statusCancel = p.RetirementResignCancels.FirstOrDefault() == null ? null : p.RetirementResignCancels.FirstOrDefault().Status, p.IsActive, }) From bceb4d3096016ef72133b79bf6b23c8222071267 Mon Sep 17 00:00:00 2001 From: Adisak Date: Fri, 3 Apr 2026 11:45:52 +0700 Subject: [PATCH 164/183] update emp status resign --- .../Controllers/RetirementResignEmployeeController.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/BMA.EHR.Retirement.Service/Controllers/RetirementResignEmployeeController.cs b/BMA.EHR.Retirement.Service/Controllers/RetirementResignEmployeeController.cs index 9b176503..5ad00538 100644 --- a/BMA.EHR.Retirement.Service/Controllers/RetirementResignEmployeeController.cs +++ b/BMA.EHR.Retirement.Service/Controllers/RetirementResignEmployeeController.cs @@ -102,8 +102,8 @@ namespace BMA.EHR.Retirement.Service.Controllers p.Remark, p.ApproveReason, p.RejectReason, - p.CancelReason, - p.Status, + p.CancelReason, + status = p.RetirementResignEmployeeCancels.FirstOrDefault() == null ? p.Status : p.RetirementResignEmployeeCancels.FirstOrDefault().Status, statusCancel = p.RetirementResignEmployeeCancels.FirstOrDefault() == null ? null : p.RetirementResignEmployeeCancels.FirstOrDefault().Status, p.IsActive, }) From cef41506a81ceaf07c4bd7886eda651bb3d95a65 Mon Sep 17 00:00:00 2001 From: Suphonchai Phoonsawat Date: Fri, 3 Apr 2026 12:09:00 +0700 Subject: [PATCH 165/183] Change Amount property to nullable int in GetProfileLeaveByKeycloakDto #2411 --- .../Responses/Leaves/GetProfileLeaveByKeycloakDto.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BMA.EHR.Application/Responses/Leaves/GetProfileLeaveByKeycloakDto.cs b/BMA.EHR.Application/Responses/Leaves/GetProfileLeaveByKeycloakDto.cs index c6c79bfd..9d13d834 100644 --- a/BMA.EHR.Application/Responses/Leaves/GetProfileLeaveByKeycloakDto.cs +++ b/BMA.EHR.Application/Responses/Leaves/GetProfileLeaveByKeycloakDto.cs @@ -13,7 +13,7 @@ public string Age { get; set; } = string.Empty; public DateTime DateAppoint { get; set; } public DateTime DateCurrent { get; set; } - public int Amount { get; set; } + public int? Amount { get; set; } = 0; public string? TelephoneNumber { get; set; } = string.Empty; public string Position { get; set; } = string.Empty; public string PosLevel { get; set; } = string.Empty; From bf92f6933e47e5083ac78dc7ce64ab0afd460903 Mon Sep 17 00:00:00 2001 From: Suphonchai Phoonsawat Date: Fri, 3 Apr 2026 12:17:39 +0700 Subject: [PATCH 166/183] Refactor user profile retrieval method in InsigniaRequestController #2390 --- BMA.EHR.Insignia/Controllers/InsigniaRequestController.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BMA.EHR.Insignia/Controllers/InsigniaRequestController.cs b/BMA.EHR.Insignia/Controllers/InsigniaRequestController.cs index 0aa8a218..415a0de2 100644 --- a/BMA.EHR.Insignia/Controllers/InsigniaRequestController.cs +++ b/BMA.EHR.Insignia/Controllers/InsigniaRequestController.cs @@ -3098,7 +3098,7 @@ namespace BMA.EHR.Insignia.Service.Controllers var doc = await _documentService.UploadFileAsync(file, file.FileName); insigniaNoteProfile.DocReturnInsignia = doc; } - var root = _userProfileRepository.GetOc(req.OrgId, 0, AccessToken)?.Root ?? null; + var root = _userProfileRepository.GetOcByNodeId(req.OrgId, 0, AccessToken)?.Root ?? null; if (req.OrgId != Guid.Parse("00000000-0000-0000-0000-000000000000")) { if (root == null) From 8950073485bbf0ad36b6b4c7bdf757c9bb6285ad Mon Sep 17 00:00:00 2001 From: harid Date: Fri, 3 Apr 2026 16:03:01 +0700 Subject: [PATCH 167/183] =?UTF-8?q?=E0=B8=AA=E0=B9=88=E0=B8=87=E0=B8=A3?= =?UTF-8?q?=E0=B8=B2=E0=B8=A2=E0=B8=8A=E0=B8=B7=E0=B9=88=E0=B8=AD=E0=B9=84?= =?UTF-8?q?=E0=B8=9B=E0=B8=AD=E0=B8=AD=E0=B8=81=E0=B8=84=E0=B8=B3=E0=B8=AA?= =?UTF-8?q?=E0=B8=B1=E0=B9=88=E0=B8=87=20C-PM-25,=20C-PM-26=20=E0=B9=83?= =?UTF-8?q?=E0=B8=AB=E0=B9=89=E0=B8=9B=E0=B8=B1=E0=B9=8A=E0=B8=A1=20comman?= =?UTF-8?q?dCode=20#2377?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Controllers/DisciplineResultController.cs | 40 ++++++++++++++++--- .../Requests/ReportPersonRequest.cs | 8 ++++ 2 files changed, 42 insertions(+), 6 deletions(-) diff --git a/BMA.EHR.Discipline.Service/Controllers/DisciplineResultController.cs b/BMA.EHR.Discipline.Service/Controllers/DisciplineResultController.cs index e5be6189..85388209 100644 --- a/BMA.EHR.Discipline.Service/Controllers/DisciplineResultController.cs +++ b/BMA.EHR.Discipline.Service/Controllers/DisciplineResultController.cs @@ -1242,14 +1242,22 @@ namespace BMA.EHR.DisciplineResult.Service.Controllers /// ไม่ได้ Login เข้าระบบ /// เมื่อเกิดข้อผิดพลาดในการทำงาน [HttpPost("command25/report")] - public async Task> PostReportCommand25([FromBody] ReportPersonRequest req) + public async Task> PostReportCommand25([FromBody] ReportPersonAndCommandRequest req) { try { var data = await _context.DisciplineReport_Profiles .Where(x => req.refIds.Contains(x.Id.ToString())) .ToListAsync(); - data.ForEach(profile => profile.Status = req.status.Trim().ToUpper()); + // data.ForEach(profile => profile.Status = req.status.Trim().ToUpper()); + data.ForEach(profile => + { + profile.Status = !string.IsNullOrEmpty(req.status) + ? req.status.Trim().ToUpper() : null; + profile.CommandTypeId = !string.IsNullOrEmpty(req.commandTypeId) && Guid.TryParse(req.commandTypeId, out var cmdTypeId) + ? cmdTypeId : null; + profile.CommandCode = req.commandCode ?? null; + }); await _context.SaveChangesAsync(); return Success(); } @@ -1276,7 +1284,13 @@ namespace BMA.EHR.DisciplineResult.Service.Controllers .Where(x => req.refIds.Contains(x.Id.ToString())) // .Where(x => x.Status.ToUpper() == "REPORT") .ToListAsync(); - data.ForEach(profile => profile.Status = "PENDING"); + // data.ForEach(profile => profile.Status = "PENDING"); + data.ForEach(profile => + { + profile.Status = "PENDING"; + profile.CommandTypeId = null; + profile.CommandCode = null; + }); await _context.SaveChangesAsync(); return Success(); } @@ -1429,14 +1443,22 @@ namespace BMA.EHR.DisciplineResult.Service.Controllers /// ไม่ได้ Login เข้าระบบ /// เมื่อเกิดข้อผิดพลาดในการทำงาน [HttpPost("command26/report")] - public async Task> PostReportCommand26([FromBody] ReportPersonRequest req) + public async Task> PostReportCommand26([FromBody] ReportPersonAndCommandRequest req) { try { var data = await _context.DisciplineReport_Profiles .Where(x => req.refIds.Contains(x.Id.ToString())) .ToListAsync(); - data.ForEach(profile => profile.Status = req.status.Trim().ToUpper()); + // data.ForEach(profile => profile.Status = req.status.Trim().ToUpper()); + data.ForEach(profile => + { + profile.Status = !string.IsNullOrEmpty(req.status) + ? req.status.Trim().ToUpper() : null; + profile.CommandTypeId = !string.IsNullOrEmpty(req.commandTypeId) && Guid.TryParse(req.commandTypeId, out var cmdTypeId) + ? cmdTypeId : null; + profile.CommandCode = req.commandCode ?? null; + }); await _context.SaveChangesAsync(); return Success(); } @@ -1463,7 +1485,13 @@ namespace BMA.EHR.DisciplineResult.Service.Controllers .Where(x => req.refIds.Contains(x.Id.ToString())) // .Where(x => x.Status.ToUpper() == "REPORT") .ToListAsync(); - data.ForEach(profile => profile.Status = "PENDING"); + // data.ForEach(profile => profile.Status = "PENDING"); + data.ForEach(profile => + { + profile.Status = "PENDING"; + profile.CommandTypeId = null; + profile.CommandCode = null; + }); await _context.SaveChangesAsync(); return Success(); } diff --git a/BMA.EHR.Discipline.Service/Requests/ReportPersonRequest.cs b/BMA.EHR.Discipline.Service/Requests/ReportPersonRequest.cs index 1bd61c56..f7a16efc 100644 --- a/BMA.EHR.Discipline.Service/Requests/ReportPersonRequest.cs +++ b/BMA.EHR.Discipline.Service/Requests/ReportPersonRequest.cs @@ -8,4 +8,12 @@ namespace BMA.EHR.Discipline.Service.Requests public string[] refIds { get; set; } public string? status { get; set; } } + + public class ReportPersonAndCommandRequest + { + public string[] refIds { get; set; } + public string? status { get; set; } + public string? commandTypeId { get; set; } + public string? commandCode { get; set; } + } } From 057b51390e9291565d5b2a112a9fca0126cdeade Mon Sep 17 00:00:00 2001 From: Suphonchai Phoonsawat Date: Thu, 9 Apr 2026 12:07:11 +0700 Subject: [PATCH 168/183] add some code --- .../Leaves/LeaveRequests/LeaveRequestRepository.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/BMA.EHR.Application/Repositories/Leaves/LeaveRequests/LeaveRequestRepository.cs b/BMA.EHR.Application/Repositories/Leaves/LeaveRequests/LeaveRequestRepository.cs index f465191e..8416af4e 100644 --- a/BMA.EHR.Application/Repositories/Leaves/LeaveRequests/LeaveRequestRepository.cs +++ b/BMA.EHR.Application/Repositories/Leaves/LeaveRequests/LeaveRequestRepository.cs @@ -676,6 +676,9 @@ namespace BMA.EHR.Application.Repositories.Leaves.LeaveRequests thisYear = thisYear + 1; } await _leaveBeginningRepository.UpdateLeaveUsageAsync(thisYear, data.Type.Id, data.KeycloakUserId, -1 * data.LeaveTotal); + // update leave count ลดลง 1 ครั้ง + await _leaveBeginningRepository.UpdateLeaveCountAsync(thisYear, data.Type.Id, data.KeycloakUserId, -1); + var _baseAPI = _configuration["API"]; var apiUrlSalary = $"{_baseAPI}/org/profile/leave/cancel/{data.Id}"; From 678329b5dfed9e81580db093992763e45f53ec27 Mon Sep 17 00:00:00 2001 From: adisak Date: Thu, 16 Apr 2026 11:31:03 +0700 Subject: [PATCH 169/183] #2392 fix departmentName null --- .../Repositories/Reports/InsigniaReportRepository.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BMA.EHR.Application/Repositories/Reports/InsigniaReportRepository.cs b/BMA.EHR.Application/Repositories/Reports/InsigniaReportRepository.cs index c094738e..a1dd7936 100644 --- a/BMA.EHR.Application/Repositories/Reports/InsigniaReportRepository.cs +++ b/BMA.EHR.Application/Repositories/Reports/InsigniaReportRepository.cs @@ -893,7 +893,7 @@ namespace BMA.EHR.Application.Repositories.Reports select new { RowNo = 1, - DepartmentName = _userProfileRepository.GetOc(g.Key.OcId, 0, AccessToken).Root, //_organizationCommonRepository.GetOrganizationNameFullPath(g.Key.OcId, false, false), + DepartmentName = _userProfileRepository.GetOc(g.Key.OcId, 0, AccessToken)?.Root ?? "-", //_organizationCommonRepository.GetOrganizationNameFullPath(g.Key.OcId, false, false), G1Male = g.Sum(x => x.Gendor == "ชาย" && x.RequestInsigniaName == "เหรียญจักรพรรดิมาลา" ? 1 : 0), G1Female = g.Sum(x => x.Gendor == "หญิง" && x.RequestInsigniaName == "เหรียญจักรพรรดิมาลา" ? 1 : 0), G2Male = g.Sum(x => x.Gendor == "ชาย" ? 1 : 0), From c34fe35506aefe49341f265a5f5254093b9f21d7 Mon Sep 17 00:00:00 2001 From: harid Date: Thu, 16 Apr 2026 11:57:47 +0700 Subject: [PATCH 170/183] =?UTF-8?q?API=20=E0=B8=A2=E0=B8=81=E0=B9=80?= =?UTF-8?q?=E0=B8=A5=E0=B8=B4=E0=B8=81=E0=B8=81=E0=B8=B2=E0=B8=A3=E0=B8=AA?= =?UTF-8?q?=E0=B9=88=E0=B8=87=E0=B8=95=E0=B8=B1=E0=B8=A7=E0=B8=9A=E0=B8=A3?= =?UTF-8?q?=E0=B8=A3=E0=B8=88=E0=B8=B8=E0=B8=9C=E0=B8=B9=E0=B9=89=E0=B8=AA?= =?UTF-8?q?=E0=B8=AD=E0=B8=9A=E0=B8=9C=E0=B9=88=E0=B8=B2=E0=B8=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Controllers/PlacementController.cs | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/BMA.EHR.Placement.Service/Controllers/PlacementController.cs b/BMA.EHR.Placement.Service/Controllers/PlacementController.cs index 2a1e5399..e8970095 100644 --- a/BMA.EHR.Placement.Service/Controllers/PlacementController.cs +++ b/BMA.EHR.Placement.Service/Controllers/PlacementController.cs @@ -903,6 +903,49 @@ namespace BMA.EHR.Placement.Service.Controllers return Success(); } + /// + /// API สำหรับยกเลิกการส่งตัว + /// + /// + /// + /// ค่าตัวแปรที่ส่งมาไม่ถูกต้อง + /// ไม่ได้ Login เข้าระบบ + /// เมื่อเกิดข้อผิดพลาดในการทำงาน + [HttpPost("update/draft-status")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesResponseType(StatusCodes.Status401Unauthorized)] + [ProducesResponseType(StatusCodes.Status500InternalServerError)] + public async Task> PersonUpdateDraftStatus([FromBody] PersonUpdateStatusRequest req) + { + var getPermission = await _permission.GetPermissionAPIAsync("UPDATE", "SYS_PLACEMENT_PASS"); + var jsonData = JsonConvert.DeserializeObject(getPermission); + if (jsonData["status"]?.ToString() != "200") + return Error(jsonData["message"]?.ToString(), StatusCodes.Status403Forbidden); + + string role = jsonData["result"]?.ToString(); + if (role != "OWNER") + return Error(jsonData["message"]?.ToString(), StatusCodes.Status403Forbidden); + + var person = await _context.PlacementProfiles + .FirstOrDefaultAsync(x => x.Id == req.PersonalId); + if (person == null) + return Error(GlobalMessages.DataNotFound, 404); + + if (person.PlacementStatus == "REPORT") + return Error("ไม่สามารถยกเลิกการส่งตัวได้ เนื่องจากส่งไปออกคำสั่งแล้ว"); + + if (person.PlacementStatus == "DONE") + return Error("ไม่สามารถยกเลิกการส่งตัวได้ เนื่องจากบรรจุไปแล้ว"); + + person.Draft = false; + person.LastUpdateFullName = FullName ?? "System Administrator"; + person.LastUpdateUserId = UserId ?? ""; + person.LastUpdatedAt = DateTime.Now; + await _context.SaveChangesAsync(); + return Success(); + } + [HttpGet("pass/deferment/{personalId:length(36)}")] public async Task> GetPersonDeferment(Guid personalId) { From 6efeec3f1ff329465d7780b9ed6fa17bfa5961e7 Mon Sep 17 00:00:00 2001 From: Suphonchai Phoonsawat Date: Thu, 16 Apr 2026 19:05:26 +0700 Subject: [PATCH 171/183] =?UTF-8?q?=E0=B9=80=E0=B8=9E=E0=B8=B4=E0=B9=88?= =?UTF-8?q?=E0=B8=A1=20List=20=E0=B8=82=E0=B8=AD=E0=B8=87=E0=B8=81?= =?UTF-8?q?=E0=B8=B2=E0=B8=A3=E0=B9=80=E0=B8=88=E0=B9=89=E0=B8=AB=E0=B8=99?= =?UTF-8?q?=E0=B9=89=E0=B8=B2=E0=B8=97=E0=B8=B5=E0=B9=88=20=E0=B8=AA?= =?UTF-8?q?=E0=B9=88=E0=B8=87=20noti=20=E0=B8=82=E0=B8=AD=E0=B8=A2?= =?UTF-8?q?=E0=B8=81=E0=B9=80=E0=B8=A5=E0=B8=B4=E0=B8=81=E0=B8=81=E0=B8=B2?= =?UTF-8?q?=E0=B8=A3=E0=B8=A5=E0=B8=B2=20#2432?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- BMA.EHR.Domain/Shared/GlobalMessages.cs | 2 ++ .../Controllers/LeaveRequestController.cs | 28 +++++++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/BMA.EHR.Domain/Shared/GlobalMessages.cs b/BMA.EHR.Domain/Shared/GlobalMessages.cs index 8746de95..a99dc3c9 100644 --- a/BMA.EHR.Domain/Shared/GlobalMessages.cs +++ b/BMA.EHR.Domain/Shared/GlobalMessages.cs @@ -8,6 +8,8 @@ public static readonly string DataNotFound = "ไม่พบข้อมูลในระบบ"; + public static readonly string ProfileNotFound = "ไม่พบข้อมูลในระบบทะเบียนประวัติ"; + public static readonly string NotAuthorized = "กรุณาเข้าสู่ระบบก่อนใช้งาน!"; public static readonly string ForbiddenAccess = "คุณไม่ได้รับอนุญาติให้เข้าใช้งาน!"; diff --git a/BMA.EHR.Leave/Controllers/LeaveRequestController.cs b/BMA.EHR.Leave/Controllers/LeaveRequestController.cs index 7bc3b151..1252eef3 100644 --- a/BMA.EHR.Leave/Controllers/LeaveRequestController.cs +++ b/BMA.EHR.Leave/Controllers/LeaveRequestController.cs @@ -1876,6 +1876,12 @@ namespace BMA.EHR.Leave.Service.Controllers return Error(GlobalMessages.DataNotFound, StatusCodes.Status404NotFound); } + var profile = await _userProfileRepository.GetProfileByKeycloakIdAsync(data.KeycloakUserId, AccessToken); + if (profile == null) + { + return Error(GlobalMessages.ProfileNotFound, StatusCodes.Status404NotFound); + } + // change status to delete // แก้จาก DELETE เป็น DELETING ไว้ก่อน รอ approve ค่อยเปลี่ยนเป็น DELETE // data.LeaveStatus = "DELETE"; @@ -1929,6 +1935,28 @@ namespace BMA.EHR.Leave.Service.Controllers _appDbContext.Set().Add(noti1); } + // Get Officer List + var officers = await _userProfileRepository.GetOCStaffAsync(profile.Id, AccessToken); + var approverProfileIdList = approvers.Select(x => x.ProfileId).ToList(); + + if(officers != null && officers.Count > 0) + { + officers = officers.Where(x => !approverProfileIdList.Contains(x.ProfileId)).ToList(); + foreach (var officer in officers) + { + // Send Notification + var noti = new Notification + { + Body = $"คำร้องขอยกเลิกการลาของคุณ {data.FirstName} {data.LastName} รอรับการอนุมัติจากคุณ", + ReceiverUserId = officer.ProfileId, + Type = "", + Payload = $"{URL}/leave/detail/{id}", + }; + _appDbContext.Set().Add(noti); + } + await _appDbContext.SaveChangesAsync(); + } + return Success(); } From ee4e9c36999d3e601be7e27d8f877d1e48545aed Mon Sep 17 00:00:00 2001 From: Suphonchai Phoonsawat Date: Thu, 16 Apr 2026 21:30:17 +0700 Subject: [PATCH 172/183] =?UTF-8?q?=E0=B9=81=E0=B8=81=E0=B9=89=20leave/det?= =?UTF-8?q?ail=20=E0=B9=80=E0=B8=9B=E0=B9=87=E0=B8=99=20leave-reject/detai?= =?UTF-8?q?l=20=E0=B8=AA=E0=B8=B3=E0=B8=AB=E0=B8=A3=E0=B8=B1=E0=B8=9A?= =?UTF-8?q?=E0=B8=A3=E0=B8=B2=E0=B8=A2=E0=B8=81=E0=B8=B2=E0=B8=A3=E0=B8=A2?= =?UTF-8?q?=E0=B8=81=E0=B9=80=E0=B8=A5=E0=B8=B4=E0=B8=81=20#2432?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- BMA.EHR.Leave/Controllers/LeaveRequestController.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/BMA.EHR.Leave/Controllers/LeaveRequestController.cs b/BMA.EHR.Leave/Controllers/LeaveRequestController.cs index 1252eef3..09836018 100644 --- a/BMA.EHR.Leave/Controllers/LeaveRequestController.cs +++ b/BMA.EHR.Leave/Controllers/LeaveRequestController.cs @@ -1930,7 +1930,7 @@ namespace BMA.EHR.Leave.Service.Controllers Body = $"คำร้องขอยกเลิกการลาของคุณ {data.FirstName} {data.LastName} รอรับการอนุมัติจากคุณ", ReceiverUserId = approver!.ProfileId, Type = "", - Payload = $"{URL}/leave/detail/{id}", + Payload = $"{URL}/leave-reject/detail/{id}", }; _appDbContext.Set().Add(noti1); } @@ -1950,7 +1950,7 @@ namespace BMA.EHR.Leave.Service.Controllers Body = $"คำร้องขอยกเลิกการลาของคุณ {data.FirstName} {data.LastName} รอรับการอนุมัติจากคุณ", ReceiverUserId = officer.ProfileId, Type = "", - Payload = $"{URL}/leave/detail/{id}", + Payload = $"{URL}/leave-reject/detail/{id}", }; _appDbContext.Set().Add(noti); } From 5606e8b50ac126ae4054e126080b0743ad79da78 Mon Sep 17 00:00:00 2001 From: Suphonchai Phoonsawat Date: Fri, 17 Apr 2026 09:41:52 +0700 Subject: [PATCH 173/183] Change GetProfileByKeycloakIdNewAsync To GetProfileByKeycloakIdNew2Async --- .../LeaveRequests/LeaveBeginingRepository.cs | 8 ++--- .../LeaveRequests/LeaveRequestRepository.cs | 14 ++++----- BMA.EHR.Leave/Controllers/LeaveController.cs | 30 +++++++++---------- .../Controllers/LeaveRequestController.cs | 16 +++++----- 4 files changed, 34 insertions(+), 34 deletions(-) diff --git a/BMA.EHR.Application/Repositories/Leaves/LeaveRequests/LeaveBeginingRepository.cs b/BMA.EHR.Application/Repositories/Leaves/LeaveRequests/LeaveBeginingRepository.cs index 2d14db9c..a62aa291 100644 --- a/BMA.EHR.Application/Repositories/Leaves/LeaveRequests/LeaveBeginingRepository.cs +++ b/BMA.EHR.Application/Repositories/Leaves/LeaveRequests/LeaveBeginingRepository.cs @@ -80,7 +80,7 @@ namespace BMA.EHR.Application.Repositories.Leaves.LeaveRequests public async Task UpdateLeaveUsageAsync(int year, Guid typeId, Guid userId, double day) { // var pf = await _userProfileRepository.GetProfileByKeycloakIdAsync(userId, AccessToken); - var pf = await _userProfileRepository.GetProfileByKeycloakIdNewAsync(userId, AccessToken); + var pf = await _userProfileRepository.GetProfileByKeycloakIdNew2Async(userId, AccessToken); if (pf == null) { throw new Exception(GlobalMessages.DataNotFound); @@ -102,7 +102,7 @@ namespace BMA.EHR.Application.Repositories.Leaves.LeaveRequests public async Task UpdateLeaveCountAsync(int year, Guid typeId, Guid userId, int count) { // var pf = await _userProfileRepository.GetProfileByKeycloakIdAsync(userId, AccessToken); - var pf = await _userProfileRepository.GetProfileByKeycloakIdNewAsync(userId, AccessToken); + var pf = await _userProfileRepository.GetProfileByKeycloakIdNew2Async(userId, AccessToken); if (pf == null) { throw new Exception(GlobalMessages.DataNotFound); @@ -124,7 +124,7 @@ namespace BMA.EHR.Application.Repositories.Leaves.LeaveRequests public async Task GetByYearAndTypeIdForUserAsync(int year, Guid typeId, Guid userId) { // var pf = await _userProfileRepository.GetProfileByKeycloakIdAsync(userId, AccessToken); - var pf = await _userProfileRepository.GetProfileByKeycloakIdNewAsync(userId, AccessToken); + var pf = await _userProfileRepository.GetProfileByKeycloakIdNew2Async(userId, AccessToken); if (pf == null) { throw new Exception(GlobalMessages.DataNotFound); @@ -263,7 +263,7 @@ namespace BMA.EHR.Application.Repositories.Leaves.LeaveRequests public async Task GetByYearAndTypeIdForUser2Async(int year, Guid typeId, Guid userId) { // var pf = await _userProfileRepository.GetProfileByKeycloakIdAsync(userId, AccessToken); - var pf = await _userProfileRepository.GetProfileByKeycloakIdNewAsync(userId, AccessToken); + var pf = await _userProfileRepository.GetProfileByKeycloakIdNew2Async(userId, AccessToken); if (pf == null) { return null; diff --git a/BMA.EHR.Application/Repositories/Leaves/LeaveRequests/LeaveRequestRepository.cs b/BMA.EHR.Application/Repositories/Leaves/LeaveRequests/LeaveRequestRepository.cs index 8416af4e..792b57cb 100644 --- a/BMA.EHR.Application/Repositories/Leaves/LeaveRequests/LeaveRequestRepository.cs +++ b/BMA.EHR.Application/Repositories/Leaves/LeaveRequests/LeaveRequestRepository.cs @@ -254,7 +254,7 @@ namespace BMA.EHR.Application.Repositories.Leaves.LeaveRequests public async Task> GetLeaveRequestByYearAsync(int year, Guid userId) { // var profile = await _userProfileRepository.GetProfileByKeycloakIdAsync(userId, AccessToken); - var profile = await _userProfileRepository.GetProfileByKeycloakIdNewAsync(userId, AccessToken); + var profile = await _userProfileRepository.GetProfileByKeycloakIdNew2Async(userId, AccessToken); if (profile == null) { @@ -497,7 +497,7 @@ namespace BMA.EHR.Application.Repositories.Leaves.LeaveRequests public async Task GetSumLeaveByTypeForUserAsync(Guid keycloakUserId, Guid leaveTypeId, int year) { // var pf = await _userProfileRepository.GetProfileByKeycloakIdAsync(keycloakUserId, AccessToken); - var pf = await _userProfileRepository.GetProfileByKeycloakIdNewAsync(keycloakUserId, AccessToken); + var pf = await _userProfileRepository.GetProfileByKeycloakIdNew2Async(keycloakUserId, AccessToken); if (pf == null) throw new Exception(GlobalMessages.DataNotFound); @@ -651,7 +651,7 @@ namespace BMA.EHR.Application.Repositories.Leaves.LeaveRequests try { // var profile = await _userProfileRepository.GetProfileByKeycloakIdAsync(data.KeycloakUserId, AccessToken ?? ""); - var profile = await _userProfileRepository.GetProfileByKeycloakIdNewAsync(data.KeycloakUserId, AccessToken ?? ""); + var profile = await _userProfileRepository.GetProfileByKeycloakIdNew2Async(data.KeycloakUserId, AccessToken ?? ""); if (profile == null) { throw new Exception(GlobalMessages.DataNotFound); @@ -728,7 +728,7 @@ namespace BMA.EHR.Application.Repositories.Leaves.LeaveRequests } // var profile = await _userProfileRepository.GetProfileByKeycloakIdAsync(rawData.KeycloakUserId, AccessToken ?? ""); - var profile = await _userProfileRepository.GetProfileByKeycloakIdNewAsync(rawData.KeycloakUserId, AccessToken ?? ""); + var profile = await _userProfileRepository.GetProfileByKeycloakIdNew2Async(rawData.KeycloakUserId, AccessToken ?? ""); if (profile == null) { throw new Exception(GlobalMessages.DataNotFound); @@ -817,7 +817,7 @@ namespace BMA.EHR.Application.Repositories.Leaves.LeaveRequests } // var profile = await _userProfileRepository.GetProfileByKeycloakIdAsync(rawData.KeycloakUserId, AccessToken ?? ""); - var profile = await _userProfileRepository.GetProfileByKeycloakIdNewAsync(rawData.KeycloakUserId, AccessToken ?? ""); + var profile = await _userProfileRepository.GetProfileByKeycloakIdNew2Async(rawData.KeycloakUserId, AccessToken ?? ""); if (profile == null) { throw new Exception(GlobalMessages.DataNotFound); @@ -1242,7 +1242,7 @@ namespace BMA.EHR.Application.Repositories.Leaves.LeaveRequests else { // var profile = await _userProfileRepository.GetProfileByKeycloakIdAsync(rawData.KeycloakUserId, AccessToken); - var profile = await _userProfileRepository.GetProfileByKeycloakIdNewAsync(rawData.KeycloakUserId, AccessToken); + var profile = await _userProfileRepository.GetProfileByKeycloakIdNew2Async(rawData.KeycloakUserId, AccessToken); if (profile == null) { throw new Exception(GlobalMessages.DataNotFound); @@ -1412,7 +1412,7 @@ namespace BMA.EHR.Application.Repositories.Leaves.LeaveRequests else { // var profile = await _userProfileRepository.GetProfileByKeycloakIdAsync(rawData.KeycloakUserId, AccessToken); - var profile = await _userProfileRepository.GetProfileByKeycloakIdNewAsync(rawData.KeycloakUserId, AccessToken); + var profile = await _userProfileRepository.GetProfileByKeycloakIdNew2Async(rawData.KeycloakUserId, AccessToken); if (profile == null) { throw new Exception(GlobalMessages.DataNotFound); diff --git a/BMA.EHR.Leave/Controllers/LeaveController.cs b/BMA.EHR.Leave/Controllers/LeaveController.cs index 0a423d60..60841b8d 100644 --- a/BMA.EHR.Leave/Controllers/LeaveController.cs +++ b/BMA.EHR.Leave/Controllers/LeaveController.cs @@ -447,7 +447,7 @@ namespace BMA.EHR.Leave.Service.Controllers // Get user's last check-in record and profile in parallel var dataTask = _userTimeStampRepository.GetLastRecord(userId); - var profileTask = _userProfileRepository.GetProfileByKeycloakIdNewAsync(userId, AccessToken); + var profileTask = _userProfileRepository.GetProfileByKeycloakIdNew2Async(userId, AccessToken); var defaultRoundTask = _dutyTimeRepository.GetDefaultAsync(); await Task.WhenAll(dataTask, profileTask, defaultRoundTask); @@ -936,7 +936,7 @@ namespace BMA.EHR.Leave.Service.Controllers await _checkInJobStatusRepository.UpdateToProcessingAsync(taskId); } - var profile = await _userProfileRepository.GetProfileByKeycloakIdNewAsync(userId, data.Token); + var profile = await _userProfileRepository.GetProfileByKeycloakIdNew2Async(userId, data.Token); if (profile == null) { @@ -1589,7 +1589,7 @@ namespace BMA.EHR.Leave.Service.Controllers public async Task> CheckInOldAsync([FromForm] CheckTimeDto data) { var userId = UserId == null ? Guid.Empty : Guid.Parse(UserId); - var profile = await _userProfileRepository.GetProfileByKeycloakIdNewAsync(userId, AccessToken); + var profile = await _userProfileRepository.GetProfileByKeycloakIdNew2Async(userId, AccessToken); if (profile == null) return Error(GlobalMessages.DataNotFound, StatusCodes.Status404NotFound); @@ -1754,7 +1754,7 @@ namespace BMA.EHR.Leave.Service.Controllers { var userId = UserId == null ? Guid.Empty : Guid.Parse(UserId); - var profile = await _userProfileRepository.GetProfileByKeycloakIdNewAsync(userId, AccessToken); + var profile = await _userProfileRepository.GetProfileByKeycloakIdNew2Async(userId, AccessToken); if (profile == null) { return Error(GlobalMessages.DataNotFound, StatusCodes.Status404NotFound); @@ -2043,7 +2043,7 @@ namespace BMA.EHR.Leave.Service.Controllers } else { - var profile = await _userProfileRepository.GetProfileByKeycloakIdNewAsync(d.KeycloakUserId, AccessToken); + var profile = await _userProfileRepository.GetProfileByKeycloakIdNew2Async(d.KeycloakUserId, AccessToken); if (profile == null) { return Error(GlobalMessages.DataNotFound, StatusCodes.Status404NotFound); @@ -2993,7 +2993,7 @@ namespace BMA.EHR.Leave.Service.Controllers var time = DateTime.Now; var userId = UserId != null ? Guid.Parse(UserId) : Guid.Empty; - var profile = await _userProfileRepository.GetProfileByKeycloakIdNewAsync(userId, AccessToken); + var profile = await _userProfileRepository.GetProfileByKeycloakIdNew2Async(userId, AccessToken); if (profile == null) { throw new Exception(GlobalMessages.DataNotFound); @@ -3113,7 +3113,7 @@ namespace BMA.EHR.Leave.Service.Controllers } var userId = UserId != null ? Guid.Parse(UserId) : Guid.Empty; - var profile = await _userProfileRepository.GetProfileByKeycloakIdNewAsync(userId, AccessToken); + var profile = await _userProfileRepository.GetProfileByKeycloakIdNew2Async(userId, AccessToken); if (profile == null) { throw new Exception(GlobalMessages.DataNotFound); @@ -3276,7 +3276,7 @@ namespace BMA.EHR.Leave.Service.Controllers foreach (var data in rawDataPaged) { - var profile = await _userProfileRepository.GetProfileByKeycloakIdNewAsync(data.KeycloakUserId, AccessToken); + var profile = await _userProfileRepository.GetProfileByKeycloakIdNew2Async(data.KeycloakUserId, AccessToken); UserDutyTime? effectiveDate = null; if (profile != null) { @@ -3455,7 +3455,7 @@ namespace BMA.EHR.Leave.Service.Controllers // change user timestamp var processTimeStamp = await _processUserTimeStampRepository.GetTimestampByDateAsync(requestData.KeycloakUserId, requestData.CheckDate.Date); - var profile = await _userProfileRepository.GetProfileByKeycloakIdNewAsync(requestData.KeycloakUserId, AccessToken); + var profile = await _userProfileRepository.GetProfileByKeycloakIdNew2Async(requestData.KeycloakUserId, AccessToken); if (processTimeStamp == null) { @@ -3609,7 +3609,7 @@ namespace BMA.EHR.Leave.Service.Controllers requestData.Comment = req.Reason; await _additionalCheckRequestRepository.UpdateAsync(requestData); - var profile = await _userProfileRepository.GetProfileByKeycloakIdNewAsync(requestData.KeycloakUserId, AccessToken); + var profile = await _userProfileRepository.GetProfileByKeycloakIdNew2Async(requestData.KeycloakUserId, AccessToken); var recvId = new List { profile.Id }; await _notificationRepository.PushNotificationsAsync(recvId.ToArray(), "ลงเวลากรณีพิเศษ", "การขอลงเวลากรณีพิเศษของคุณไม่ได้รับการอนุมัติ", "", "", true, false); @@ -3653,7 +3653,7 @@ namespace BMA.EHR.Leave.Service.Controllers } else { - var profile = await _userProfileRepository.GetProfileByKeycloakIdNewAsync(d.KeycloakUserId, AccessToken); + var profile = await _userProfileRepository.GetProfileByKeycloakIdNew2Async(d.KeycloakUserId, AccessToken); if (profile == null) { return Error(GlobalMessages.DataNotFound, StatusCodes.Status404NotFound); @@ -3747,7 +3747,7 @@ namespace BMA.EHR.Leave.Service.Controllers foreach (var data in rawData) { - var profile = await _userProfileRepository.GetProfileByKeycloakIdNewAsync(data.KeycloakUserId, AccessToken); + var profile = await _userProfileRepository.GetProfileByKeycloakIdNew2Async(data.KeycloakUserId, AccessToken); if (profile == null) { return Error(GlobalMessages.DataNotFound, StatusCodes.Status404NotFound); @@ -4037,7 +4037,7 @@ namespace BMA.EHR.Leave.Service.Controllers //var userId = UserId == null ? Guid.Empty : Guid.Parse(UserId); // แก้เป็นมาใช้งาน KeycloakUserId แทน - var profile = await _userProfileRepository.GetProfileByKeycloakIdNewAsync(data.KeycloakUserId, AccessToken); + var profile = await _userProfileRepository.GetProfileByKeycloakIdNew2Async(data.KeycloakUserId, AccessToken); var defaultRound = await _dutyTimeRepository.GetDefaultAsync(); if (defaultRound == null) { @@ -4107,7 +4107,7 @@ namespace BMA.EHR.Leave.Service.Controllers [ProducesResponseType(StatusCodes.Status500InternalServerError)] public async Task> GetLeaveSummaryByProfileAsync(Guid id, [FromBody] GetLeaveSummaryDto req) { - var profile = await _userProfileRepository.GetProfileByKeycloakIdNewAsync(id, AccessToken); + var profile = await _userProfileRepository.GetProfileByKeycloakIdNew2Async(id, AccessToken); var thisYear = DateTime.Now.Year; var startDate = req.StartDate; @@ -4177,7 +4177,7 @@ namespace BMA.EHR.Leave.Service.Controllers { var userId = UserId == null ? Guid.Empty : Guid.Parse(UserId); - var profile = await _userProfileRepository.GetProfileByKeycloakIdNewAsync(userId, AccessToken); + var profile = await _userProfileRepository.GetProfileByKeycloakIdNew2Async(userId, AccessToken); if (profile == null) { return Error(GlobalMessages.DataNotFound, StatusCodes.Status404NotFound); diff --git a/BMA.EHR.Leave/Controllers/LeaveRequestController.cs b/BMA.EHR.Leave/Controllers/LeaveRequestController.cs index 09836018..baa6d51b 100644 --- a/BMA.EHR.Leave/Controllers/LeaveRequestController.cs +++ b/BMA.EHR.Leave/Controllers/LeaveRequestController.cs @@ -214,7 +214,7 @@ namespace BMA.EHR.Leave.Service.Controllers var thisYear = DateTime.Now.Year; // var profile = await _userProfileRepository.GetProfileByKeycloakIdAsync(userId, AccessToken); - var profile = await _userProfileRepository.GetProfileByKeycloakIdNewAsync(userId, AccessToken); + var profile = await _userProfileRepository.GetProfileByKeycloakIdNew2Async(userId, AccessToken); if (profile == null) { @@ -502,7 +502,7 @@ namespace BMA.EHR.Leave.Service.Controllers foreach (var leave in leaves) { // var profile = await _userProfileRepository.GetProfileByKeycloakIdAsync(leave.KeycloakUserId, AccessToken); - var profile = await _userProfileRepository.GetProfileByKeycloakIdNewAsync(leave.KeycloakUserId, AccessToken); + var profile = await _userProfileRepository.GetProfileByKeycloakIdNew2Async(leave.KeycloakUserId, AccessToken); if (profile != null) { leave.Prefix = profile.Prefix; @@ -563,7 +563,7 @@ namespace BMA.EHR.Leave.Service.Controllers // } // var profile = await _userProfileRepository.GetProfileByKeycloakIdAsync(userId, AccessToken); - var profile = await _userProfileRepository.GetProfileByKeycloakIdNewAsync(userId, AccessToken); + var profile = await _userProfileRepository.GetProfileByKeycloakIdNew2Async(userId, AccessToken); if (profile == null) { @@ -1026,7 +1026,7 @@ namespace BMA.EHR.Leave.Service.Controllers var userId = UserId == null ? Guid.Empty : Guid.Parse(UserId); // var profile = await _userProfileRepository.GetProfileByKeycloakIdAsync(userId, AccessToken); - var profile = await _userProfileRepository.GetProfileByKeycloakIdNewAsync(userId, AccessToken); + var profile = await _userProfileRepository.GetProfileByKeycloakIdNew2Async(userId, AccessToken); var govAge = (profile?.DateStart?.Date ?? DateTime.Now.Date).DiffDay(DateTime.Now.Date); var startDate = profile?.DateStart?.Date ?? DateTime.Now.Date; // var date1Raw = profile?.DateStart?.Date ?? DateTime.Now.Date; @@ -1577,7 +1577,7 @@ namespace BMA.EHR.Leave.Service.Controllers } // var profile = await _userProfileRepository.GetProfileByKeycloakIdAsync(rawData.KeycloakUserId, AccessToken); - var profile = await _userProfileRepository.GetProfileByKeycloakIdNewAsync(rawData.KeycloakUserId, AccessToken); + var profile = await _userProfileRepository.GetProfileByKeycloakIdNew2Async(rawData.KeycloakUserId, AccessToken); if (profile == null) { @@ -1876,7 +1876,7 @@ namespace BMA.EHR.Leave.Service.Controllers return Error(GlobalMessages.DataNotFound, StatusCodes.Status404NotFound); } - var profile = await _userProfileRepository.GetProfileByKeycloakIdAsync(data.KeycloakUserId, AccessToken); + var profile = await _userProfileRepository.GetProfileByKeycloakIdNew2Async(data.KeycloakUserId, AccessToken); if (profile == null) { return Error(GlobalMessages.ProfileNotFound, StatusCodes.Status404NotFound); @@ -2140,7 +2140,7 @@ namespace BMA.EHR.Leave.Service.Controllers } // var profile = await _userProfileRepository.GetProfileByKeycloakIdAsync(Guid.Parse(UserId!), AccessToken); - var profile = await _userProfileRepository.GetProfileByKeycloakIdNewAsync(Guid.Parse(UserId!), AccessToken); + var profile = await _userProfileRepository.GetProfileByKeycloakIdNew2Async(Guid.Parse(UserId!), AccessToken); if (profile == null) { @@ -2785,7 +2785,7 @@ namespace BMA.EHR.Leave.Service.Controllers var rejectList = await _leaveRequestRepository.GetSumRejectLeaveAsync(thisYear); var deleteList = await _leaveRequestRepository.GetSumDeleteLeaveAsync(thisYear); // var pf = await _userProfileRepository.GetProfileByKeycloakIdAsync(userId, AccessToken); - var pf = await _userProfileRepository.GetProfileByKeycloakIdNewAsync(userId, AccessToken); + var pf = await _userProfileRepository.GetProfileByKeycloakIdNew2Async(userId, AccessToken); if (pf == null) { From 34ec9bb77c2644a987d04c04bab9c119dcd82ede Mon Sep 17 00:00:00 2001 From: Suphonchai Phoonsawat Date: Fri, 17 Apr 2026 10:31:09 +0700 Subject: [PATCH 174/183] Remove Check Pending Status For Cancel Leave #2432 --- BMA.EHR.Leave/Controllers/LeaveRequestController.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BMA.EHR.Leave/Controllers/LeaveRequestController.cs b/BMA.EHR.Leave/Controllers/LeaveRequestController.cs index baa6d51b..c2954ca4 100644 --- a/BMA.EHR.Leave/Controllers/LeaveRequestController.cs +++ b/BMA.EHR.Leave/Controllers/LeaveRequestController.cs @@ -1918,7 +1918,7 @@ namespace BMA.EHR.Leave.Service.Controllers // TODO: Send notification to all users who need to approve the cancel leave request var approvers = data.Approvers - .Where(x => x.ApproveStatus!.ToUpper() == "PENDING") + //.Where(x => x.ApproveStatus!.ToUpper() == "PENDING") .OrderBy(x => x.Seq) .ToList(); From db99630e0d629eb9f3f3e3b64ebd1ff317cdfc7a Mon Sep 17 00:00:00 2001 From: harid Date: Fri, 17 Apr 2026 15:18:30 +0700 Subject: [PATCH 175/183] fix #2430 --- BMA.EHR.Placement.Service/Controllers/PlacementController.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BMA.EHR.Placement.Service/Controllers/PlacementController.cs b/BMA.EHR.Placement.Service/Controllers/PlacementController.cs index e8970095..fbb6e2de 100644 --- a/BMA.EHR.Placement.Service/Controllers/PlacementController.cs +++ b/BMA.EHR.Placement.Service/Controllers/PlacementController.cs @@ -2045,7 +2045,7 @@ namespace BMA.EHR.Placement.Service.Controllers .Where(x => req.refIds.Contains(x.Id.ToString())) // .Where(x => x.PlacementStatus.ToUpper() == "REPORT") .ToListAsync(); - placementProfiles.ForEach(profile => profile.PlacementStatus = "PREPARE-CONTAI"); + placementProfiles.ForEach(profile => profile.PlacementStatus = "PREPARE-CONTAIN"); await _context.SaveChangesAsync(); return Success(); } From 7bafbf5001d462edc59b7f215ef71ae446a1f38d Mon Sep 17 00:00:00 2001 From: Suphonchai Phoonsawat Date: Fri, 17 Apr 2026 15:35:29 +0700 Subject: [PATCH 176/183] =?UTF-8?q?=E0=B9=80=E0=B8=9E=E0=B8=B4=E0=B9=88?= =?UTF-8?q?=E0=B8=A1=E0=B8=A3=E0=B8=B2=E0=B8=A2=E0=B8=81=E0=B8=B2=E0=B8=A3?= =?UTF-8?q?=E0=B8=A3=E0=B8=B1=E0=B8=81=E0=B8=A9=E0=B8=B2=E0=B8=81=E0=B8=B2?= =?UTF-8?q?=E0=B8=A3=20#2431?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Repositories/PermissionRepository.cs | 34 +++ .../Leaves/GetPermissionWithActingDto.cs | 32 +++ BMA.EHR.Leave/Controllers/LeaveController.cs | 87 +++++- .../Controllers/LeaveRequestController.cs | 268 ++++++++++++++++-- 4 files changed, 398 insertions(+), 23 deletions(-) create mode 100644 BMA.EHR.Application/Responses/Leaves/GetPermissionWithActingDto.cs diff --git a/BMA.EHR.Application/Repositories/PermissionRepository.cs b/BMA.EHR.Application/Repositories/PermissionRepository.cs index 84191f91..a63207ec 100644 --- a/BMA.EHR.Application/Repositories/PermissionRepository.cs +++ b/BMA.EHR.Application/Repositories/PermissionRepository.cs @@ -10,6 +10,7 @@ using System.Net.Http.Headers; using Microsoft.Extensions.Configuration; using System.Security.Claims; using System.Net.Http.Json; +using BMA.EHR.Application.Responses.Leaves; namespace BMA.EHR.Application.Repositories { @@ -76,6 +77,39 @@ namespace BMA.EHR.Application.Repositories } } + public async Task GetPermissionWithActingAPIAsync(string action, string system) + { + try + { + var apiPath = $"{_configuration["API"]}/org/permission/dotnet-acting/{action}/{system}"; + + using (var client = new HttpClient()) + { + client.DefaultRequestHeaders.Authorization = + new AuthenticationHeaderValue("Bearer", AccessToken.Replace("Bearer ", "")); + client.DefaultRequestHeaders.Add("api-key", _configuration["API_KEY"]); + var req = await client.GetAsync(apiPath); + if (!req.IsSuccessStatusCode) + { + throw new Exception("Error calling permission API"); + } + var apiResult = await req.Content.ReadAsStringAsync(); + //return res; + + if (apiResult != null) + { + var raw = JsonConvert.DeserializeObject(apiResult); + return raw; + } + return null; + } + } + catch + { + throw; + } + } + public async Task GetPermissionOrgAPIAsync(string action, string system, string profileId) { try diff --git a/BMA.EHR.Application/Responses/Leaves/GetPermissionWithActingDto.cs b/BMA.EHR.Application/Responses/Leaves/GetPermissionWithActingDto.cs new file mode 100644 index 00000000..0050f7d1 --- /dev/null +++ b/BMA.EHR.Application/Responses/Leaves/GetPermissionWithActingDto.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace BMA.EHR.Application.Responses.Leaves +{ + public class GetPermissionWithActingDto + { + public string privilege {get; set;} = string.Empty; + public bool isAct {get; set;} = false; + public List posMasterActs {get; set;} = new(); + } + + public class ActingPermission + { + public string posNo {get; set;} = string.Empty; + public string privilege {get; set;} = string.Empty; + public Guid? rootDnaId {get; set;} + public Guid? child1DnaId {get; set;} + public Guid? child2DnaId {get; set;} + public Guid? child3DnaId {get; set;} + public Guid? child4DnaId {get; set;} + } + + public class GetPermissionWithActingResultDto + { + public int status {get; set;} = 0; + public string message {get; set;} = string.Empty; + public GetPermissionWithActingDto result {get; set;} = new(); + } +} \ No newline at end of file diff --git a/BMA.EHR.Leave/Controllers/LeaveController.cs b/BMA.EHR.Leave/Controllers/LeaveController.cs index 60841b8d..8ff74510 100644 --- a/BMA.EHR.Leave/Controllers/LeaveController.cs +++ b/BMA.EHR.Leave/Controllers/LeaveController.cs @@ -3,8 +3,10 @@ using BMA.EHR.Application.Repositories.Commands; using BMA.EHR.Application.Repositories.Leaves.LeaveRequests; using BMA.EHR.Application.Repositories.Leaves.TimeAttendants; using BMA.EHR.Application.Repositories.MessageQueue; +using BMA.EHR.Application.Responses.Leaves; using BMA.EHR.Application.Responses.Profiles; using BMA.EHR.Domain.Common; +using BMA.EHR.Domain.Extensions; using BMA.EHR.Domain.Models.Leave.TimeAttendants; using BMA.EHR.Domain.Models.Notifications; using BMA.EHR.Domain.Shared; @@ -3162,13 +3164,14 @@ namespace BMA.EHR.Leave.Service.Controllers [ProducesResponseType(StatusCodes.Status500InternalServerError)] public async Task> GetAdditionalCheckRequestAsync([Required] int year, [Required] int month, [Required] int page = 1, [Required] int pageSize = 10, string keyword = "", string? sortBy = "", bool? descending = false) { - var getPermission = await _permission.GetPermissionAPIAsync("LIST", "SYS_CHECKIN_SPECIAL"); - var jsonData = JsonConvert.DeserializeObject(getPermission); - if (jsonData["status"]?.ToString() != "200") + var jsonData = await _permission.GetPermissionWithActingAPIAsync("LIST", "SYS_CHECKIN_SPECIAL"); + //var jsonData = JsonConvert.DeserializeObject(getPermission); + if (jsonData!.status != 200) { - return Error(jsonData["message"]?.ToString(), StatusCodes.Status403Forbidden); + return Error(jsonData.message, StatusCodes.Status403Forbidden); } - string role = jsonData["result"]?.ToString(); + //string role = jsonData["result"]?.ToString(); + string role = jsonData.result.privilege; var nodeId = string.Empty; var profileAdmin = new GetUserOCAllDto(); profileAdmin = await _userProfileRepository.GetUserOCAll(Guid.Parse(UserId!), AccessToken); @@ -3206,6 +3209,80 @@ namespace BMA.EHR.Leave.Service.Controllers //var rawData = await _additionalCheckRequestRepository.GetAdditionalCheckRequests(year, month); var rawData = await _additionalCheckRequestRepository.GetAdditionalCheckRequestsByAdminRole(year, month, role, nodeId, profileAdmin?.Node, keyword); + // ถ้ามีการรักษาการ + if (jsonData.result.isAct) + { + var posActs = jsonData.result.posMasterActs; + foreach(var act in posActs) + { + var actRole = act.privilege; + string actNodeId = string.Empty; + int? actNode; + + if (role == "NORMAL" || role == "CHILD") + { + actNodeId = act.child4DnaId != null ? + act.child4DnaId.Value.ToString("D") : + act.child3DnaId != null ? + act.child3DnaId.Value.ToString("D") : + act.child2DnaId != null ? + act.child2DnaId.Value.ToString("D") : + act.child1DnaId != null ? + act.child1DnaId.Value.ToString("D") : + act.rootDnaId != null ? + act.rootDnaId.Value.ToString("D") : + ""; + actNode = act.child4DnaId != null ? + 4 : + act.child3DnaId != null ? + 3 : + act.child2DnaId != null ? + 2 : + act.child1DnaId != null ? + 1 : + act.rootDnaId != null ? + 0 : + null; + } + else if (role == "BROTHER") + { + actNodeId = act.child3DnaId != null ? + act.child3DnaId.Value.ToString("D") : + act.child2DnaId != null ? + act.child2DnaId.Value.ToString("D") : + act.child1DnaId != null ? + act.rootDnaId!.Value.ToString("D") : + act.rootDnaId != null ? + act.rootDnaId.Value.ToString("D") : + ""; + actNode = act.child4DnaId != null ? + 4 : + act.child3DnaId != null ? + 4 : + act.child2DnaId != null ? + 3 : + act.child1DnaId != null ? + 2 : + act.rootDnaId != null ? + 0 : + null; + } + else if (role == "ROOT" /*|| role == "PARENT"*/) + { + actNodeId = act.rootDnaId!.Value.ToString("D"); + actNode = 0; + } + + var rawDataAct = await _additionalCheckRequestRepository.GetAdditionalCheckRequestsByAdminRole(year, month, actRole, actNodeId, profileAdmin?.Node, keyword); + if (rawDataAct != null) + { + if (rawData != null) + rawData = rawData.Union(rawDataAct).ToList(); + else + rawData = rawDataAct; + } + } + } var total = rawData.Count; var getDefaultRound = await _dutyTimeRepository.GetDefaultAsync(); diff --git a/BMA.EHR.Leave/Controllers/LeaveRequestController.cs b/BMA.EHR.Leave/Controllers/LeaveRequestController.cs index c2954ca4..5720bccc 100644 --- a/BMA.EHR.Leave/Controllers/LeaveRequestController.cs +++ b/BMA.EHR.Leave/Controllers/LeaveRequestController.cs @@ -1352,14 +1352,14 @@ namespace BMA.EHR.Leave.Service.Controllers public async Task> GetLeaveRequestCalendarAdminAsync( [FromBody] GetLeaveRequestCalendarDto req) { - var getPermission = await _permission.GetPermissionAPIAsync("LIST", "SYS_LEAVE_LIST"); - var jsonData = JsonConvert.DeserializeObject(getPermission); - if (jsonData["status"]?.ToString() != "200") + var jsonData = await _permission.GetPermissionWithActingAPIAsync("LIST", "SYS_LEAVE_LIST"); + //var jsonData = JsonConvert.DeserializeObject(getPermission); + if (jsonData!.status != 200) { - return Error(jsonData["message"]?.ToString(), StatusCodes.Status403Forbidden); + return Error(jsonData.message, StatusCodes.Status403Forbidden); } - - string role = jsonData["result"]?.ToString(); + //string role = jsonData["result"]?.ToString(); + string role = jsonData.result.privilege; var nodeId = string.Empty; var profileAdmin = new GetUserOCAllDto(); profileAdmin = await _userProfileRepository.GetUserOCAll(Guid.Parse(UserId!), AccessToken); @@ -1395,6 +1395,85 @@ namespace BMA.EHR.Leave.Service.Controllers } var data = await _leaveRequestRepository.GetLeaveRequestByYearForAdminAsync(req.Year, role, nodeId, profileAdmin.Node); + + // ถ้ามีการรักษาการ + if (jsonData.result.isAct) + { + var posActs = jsonData.result.posMasterActs; + foreach(var act in posActs) + { + var actRole = act.privilege; + string actNodeId = string.Empty; + int? actNode = null; + + if (role == "NORMAL" || role == "CHILD") + { + actNodeId = act.child4DnaId != null ? + act.child4DnaId.Value.ToString("D") : + act.child3DnaId != null ? + act.child3DnaId.Value.ToString("D") : + act.child2DnaId != null ? + act.child2DnaId.Value.ToString("D") : + act.child1DnaId != null ? + act.child1DnaId.Value.ToString("D") : + act.rootDnaId != null ? + act.rootDnaId.Value.ToString("D") : + ""; + actNode = act.child4DnaId != null ? + 4 : + act.child3DnaId != null ? + 3 : + act.child2DnaId != null ? + 2 : + act.child1DnaId != null ? + 1 : + act.rootDnaId != null ? + 0 : + null; + } + else if (role == "BROTHER") + { + actNodeId = act.child3DnaId != null ? + act.child3DnaId.Value.ToString("D") : + act.child2DnaId != null ? + act.child2DnaId.Value.ToString("D") : + act.child1DnaId != null ? + act.rootDnaId!.Value.ToString("D") : + act.rootDnaId != null ? + act.rootDnaId.Value.ToString("D") : + ""; + actNode = act.child4DnaId != null ? + 4 : + act.child3DnaId != null ? + 4 : + act.child2DnaId != null ? + 3 : + act.child1DnaId != null ? + 2 : + act.rootDnaId != null ? + 0 : + null; + } + else if (role == "ROOT" /*|| role == "PARENT"*/) + { + actNodeId = act.rootDnaId!.Value.ToString("D"); + actNode = 0; + } + + var rawDataAct = await _leaveRequestRepository.GetLeaveRequestByYearForAdminAsync(req.Year, actRole, actNodeId, actNode); + if (rawDataAct != null) + { + if (data != null) + data = data.Union(rawDataAct).ToList(); + else + data = rawDataAct; + } + } + } + + + + var resultData = (from d in data //join p in profileList on d.KeycloakUserId equals p.Keycloak select new GetLeaveRequestCalendarResultDto @@ -1750,14 +1829,14 @@ namespace BMA.EHR.Leave.Service.Controllers public async Task> GetLeaveRequestForAdminAsync( [FromBody] GetLeaveRequestForAdminDto req) { - var getPermission = await _permission.GetPermissionAPIAsync("LIST", "SYS_LEAVE_LIST"); - var jsonData = JsonConvert.DeserializeObject(getPermission); - if (jsonData["status"]?.ToString() != "200") + var jsonData = await _permission.GetPermissionWithActingAPIAsync("LIST", "SYS_LEAVE_LIST"); + //var jsonData = JsonConvert.DeserializeObject(getPermission); + if (jsonData!.status != 200) { - return Error(jsonData["message"]?.ToString(), StatusCodes.Status403Forbidden); + return Error(jsonData.message, StatusCodes.Status403Forbidden); } - - string role = jsonData["result"]?.ToString(); + //string role = jsonData["result"]?.ToString(); + string role = jsonData.result.privilege; var nodeId = string.Empty; var profileAdmin = new GetUserOCAllDto(); profileAdmin = await _userProfileRepository.GetUserOCAll(Guid.Parse(UserId!), AccessToken); @@ -1794,6 +1873,83 @@ namespace BMA.EHR.Leave.Service.Controllers var rawData = await _leaveRequestRepository.GetListLeaveRequestForAdminAsync(req.Year, req.Type, req.Status, req.StartDate, req.EndDate, role, nodeId, profileAdmin?.Node); + + // ถ้ามีการรักษาการ + if (jsonData.result.isAct) + { + var posActs = jsonData.result.posMasterActs; + foreach(var act in posActs) + { + var actRole = act.privilege; + string actNodeId = string.Empty; + int? actNode = null; + + if (role == "NORMAL" || role == "CHILD") + { + actNodeId = act.child4DnaId != null ? + act.child4DnaId.Value.ToString("D") : + act.child3DnaId != null ? + act.child3DnaId.Value.ToString("D") : + act.child2DnaId != null ? + act.child2DnaId.Value.ToString("D") : + act.child1DnaId != null ? + act.child1DnaId.Value.ToString("D") : + act.rootDnaId != null ? + act.rootDnaId.Value.ToString("D") : + ""; + actNode = act.child4DnaId != null ? + 4 : + act.child3DnaId != null ? + 3 : + act.child2DnaId != null ? + 2 : + act.child1DnaId != null ? + 1 : + act.rootDnaId != null ? + 0 : + null; + } + else if (role == "BROTHER") + { + actNodeId = act.child3DnaId != null ? + act.child3DnaId.Value.ToString("D") : + act.child2DnaId != null ? + act.child2DnaId.Value.ToString("D") : + act.child1DnaId != null ? + act.rootDnaId!.Value.ToString("D") : + act.rootDnaId != null ? + act.rootDnaId.Value.ToString("D") : + ""; + actNode = act.child4DnaId != null ? + 4 : + act.child3DnaId != null ? + 4 : + act.child2DnaId != null ? + 3 : + act.child1DnaId != null ? + 2 : + act.rootDnaId != null ? + 0 : + null; + } + else if (role == "ROOT" /*|| role == "PARENT"*/) + { + actNodeId = act.rootDnaId!.Value.ToString("D"); + actNode = 0; + } + + var rawDataAct = await _leaveRequestRepository.GetListLeaveRequestForAdminAsync(req.Year, req.Type, req.Status, req.StartDate, req.EndDate, actRole, actNodeId, actNode); + if (rawDataAct != null) + { + if (rawData != null) + rawData = rawData.Union(rawDataAct).ToList(); + else + rawData = rawDataAct; + } + } + } + + var result = new List(); foreach (var item in rawData) @@ -1976,14 +2132,14 @@ namespace BMA.EHR.Leave.Service.Controllers public async Task> GetCancelLeaveRequestForAdminAsync( [FromBody] GetLeaveRequestForAdminDto req) { - var getPermission = await _permission.GetPermissionAPIAsync("LIST", "SYS_LEAVE_LIST"); - var jsonData = JsonConvert.DeserializeObject(getPermission); - if (jsonData["status"]?.ToString() != "200") + var jsonData = await _permission.GetPermissionWithActingAPIAsync("LIST", "SYS_LEAVE_LIST"); + //var jsonData = JsonConvert.DeserializeObject(getPermission); + if (jsonData!.status != 200) { - return Error(jsonData["message"]?.ToString(), StatusCodes.Status403Forbidden); + return Error(jsonData.message, StatusCodes.Status403Forbidden); } - - string role = jsonData["result"]?.ToString(); + //string role = jsonData["result"]?.ToString(); + string role = jsonData.result.privilege; var nodeId = string.Empty; var profileAdmin = new GetUserOCAllDto(); profileAdmin = await _userProfileRepository.GetUserOCAll(Guid.Parse(UserId!), AccessToken); @@ -2021,6 +2177,82 @@ namespace BMA.EHR.Leave.Service.Controllers var rawData = await _leaveRequestRepository.GetCancelLeaveRequestForAdminAsync(req.Year, req.Type, req.Status, role, nodeId, profileAdmin?.Node); + // ถ้ามีการรักษาการ + if (jsonData.result.isAct) + { + var posActs = jsonData.result.posMasterActs; + foreach(var act in posActs) + { + var actRole = act.privilege; + string actNodeId = string.Empty; + int? actNode = null; + + if (role == "NORMAL" || role == "CHILD") + { + actNodeId = act.child4DnaId != null ? + act.child4DnaId.Value.ToString("D") : + act.child3DnaId != null ? + act.child3DnaId.Value.ToString("D") : + act.child2DnaId != null ? + act.child2DnaId.Value.ToString("D") : + act.child1DnaId != null ? + act.child1DnaId.Value.ToString("D") : + act.rootDnaId != null ? + act.rootDnaId.Value.ToString("D") : + ""; + actNode = act.child4DnaId != null ? + 4 : + act.child3DnaId != null ? + 3 : + act.child2DnaId != null ? + 2 : + act.child1DnaId != null ? + 1 : + act.rootDnaId != null ? + 0 : + null; + } + else if (role == "BROTHER") + { + actNodeId = act.child3DnaId != null ? + act.child3DnaId.Value.ToString("D") : + act.child2DnaId != null ? + act.child2DnaId.Value.ToString("D") : + act.child1DnaId != null ? + act.rootDnaId!.Value.ToString("D") : + act.rootDnaId != null ? + act.rootDnaId.Value.ToString("D") : + ""; + actNode = act.child4DnaId != null ? + 4 : + act.child3DnaId != null ? + 4 : + act.child2DnaId != null ? + 3 : + act.child1DnaId != null ? + 2 : + act.rootDnaId != null ? + 0 : + null; + } + else if (role == "ROOT" /*|| role == "PARENT"*/) + { + actNodeId = act.rootDnaId!.Value.ToString("D"); + actNode = 0; + } + + var rawDataAct = await _leaveRequestRepository.GetCancelLeaveRequestForAdminAsync(req.Year, req.Type, req.Status, actRole, actNodeId, actNode); + if (rawDataAct != null) + { + if (rawData != null) + rawData = rawData.Union(rawDataAct).ToList(); + else + rawData = rawDataAct; + } + } + } + + var recCount = rawData.Count; if (req.Keyword != "") From ee2d16925a380476b13cbaf9fb28fc837acf7a53 Mon Sep 17 00:00:00 2001 From: Suphonchai Phoonsawat Date: Fri, 17 Apr 2026 19:21:30 +0700 Subject: [PATCH 177/183] =?UTF-8?q?=E0=B9=81=E0=B8=81=E0=B9=89=E0=B9=84?= =?UTF-8?q?=E0=B8=82=20=E0=B8=A3=E0=B8=B1=E0=B8=81=E0=B8=A9=E0=B8=B2?= =?UTF-8?q?=E0=B8=81=E0=B8=B2=E0=B8=A3=20=E0=B8=96=E0=B9=89=E0=B8=B2?= =?UTF-8?q?=E0=B9=84=E0=B8=A1=E0=B9=88=E0=B9=84=E0=B8=94=E0=B9=89=E0=B9=83?= =?UTF-8?q?=E0=B8=AA=E0=B9=88=20privilage=20=E0=B8=A1=E0=B8=B2=E0=B8=88?= =?UTF-8?q?=E0=B8=B0=E0=B9=83=E0=B8=AB=E0=B9=89=20default=20=3D=20"PARENT"?= =?UTF-8?q?=20#2431?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Leaves/GetPermissionWithActingDto.cs | 7 ++++- BMA.EHR.Domain/Shared/PrivilegeConverter.cs | 30 +++++++++++++++++++ 2 files changed, 36 insertions(+), 1 deletion(-) create mode 100644 BMA.EHR.Domain/Shared/PrivilegeConverter.cs diff --git a/BMA.EHR.Application/Responses/Leaves/GetPermissionWithActingDto.cs b/BMA.EHR.Application/Responses/Leaves/GetPermissionWithActingDto.cs index 0050f7d1..dc4cbdb9 100644 --- a/BMA.EHR.Application/Responses/Leaves/GetPermissionWithActingDto.cs +++ b/BMA.EHR.Application/Responses/Leaves/GetPermissionWithActingDto.cs @@ -2,6 +2,8 @@ using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using BMA.EHR.Domain.Shared; +using Newtonsoft.Json; namespace BMA.EHR.Application.Responses.Leaves { @@ -15,7 +17,10 @@ namespace BMA.EHR.Application.Responses.Leaves public class ActingPermission { public string posNo {get; set;} = string.Empty; - public string privilege {get; set;} = string.Empty; + //public string? privilege {get; set;} = "PARENT"; + [JsonConverter(typeof(PrivilegeConverter))] + public string privilege {get; set;} = "PARENT"; + public Guid? rootDnaId {get; set;} public Guid? child1DnaId {get; set;} public Guid? child2DnaId {get; set;} diff --git a/BMA.EHR.Domain/Shared/PrivilegeConverter.cs b/BMA.EHR.Domain/Shared/PrivilegeConverter.cs new file mode 100644 index 00000000..fa806658 --- /dev/null +++ b/BMA.EHR.Domain/Shared/PrivilegeConverter.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Newtonsoft.Json; + +namespace BMA.EHR.Domain.Shared +{ + public class PrivilegeConverter : JsonConverter +{ + public override bool CanConvert(Type objectType) + { + return objectType == typeof(string); + } + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + if (reader.TokenType == JsonToken.Null) + { + return "PARENT"; + } + return reader.Value; + } + + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + writer.WriteValue(value); + } +} +} \ No newline at end of file From 058027ea294044ccf02542d13dcfebdca383469c Mon Sep 17 00:00:00 2001 From: Suphonchai Phoonsawat Date: Fri, 17 Apr 2026 20:11:18 +0700 Subject: [PATCH 178/183] =?UTF-8?q?=E0=B9=80=E0=B8=9B=E0=B8=A5=E0=B8=B5?= =?UTF-8?q?=E0=B9=88=E0=B8=A2=E0=B8=99=20DEFAULT=20=E0=B8=AA=E0=B8=B4?= =?UTF-8?q?=E0=B8=97=E0=B8=98=E0=B8=B4=E0=B9=8C=E0=B9=80=E0=B8=9B=E0=B9=87?= =?UTF-8?q?=E0=B8=99=20"CHILD"=20=E0=B8=81=E0=B8=A3=E0=B8=93=E0=B8=B5?= =?UTF-8?q?=E0=B9=84=E0=B8=A1=E0=B9=88=E0=B8=95=E0=B8=B1=E0=B9=89=E0=B8=87?= =?UTF-8?q?=E0=B8=84=E0=B9=88=E0=B8=B2=E0=B8=A1=E0=B8=B2=20#2431?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Responses/Leaves/GetPermissionWithActingDto.cs | 2 +- BMA.EHR.Domain/Shared/PrivilegeConverter.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/BMA.EHR.Application/Responses/Leaves/GetPermissionWithActingDto.cs b/BMA.EHR.Application/Responses/Leaves/GetPermissionWithActingDto.cs index dc4cbdb9..083c4b20 100644 --- a/BMA.EHR.Application/Responses/Leaves/GetPermissionWithActingDto.cs +++ b/BMA.EHR.Application/Responses/Leaves/GetPermissionWithActingDto.cs @@ -19,7 +19,7 @@ namespace BMA.EHR.Application.Responses.Leaves public string posNo {get; set;} = string.Empty; //public string? privilege {get; set;} = "PARENT"; [JsonConverter(typeof(PrivilegeConverter))] - public string privilege {get; set;} = "PARENT"; + public string privilege {get; set;} = "CHILD"; public Guid? rootDnaId {get; set;} public Guid? child1DnaId {get; set;} diff --git a/BMA.EHR.Domain/Shared/PrivilegeConverter.cs b/BMA.EHR.Domain/Shared/PrivilegeConverter.cs index fa806658..4dc7fd85 100644 --- a/BMA.EHR.Domain/Shared/PrivilegeConverter.cs +++ b/BMA.EHR.Domain/Shared/PrivilegeConverter.cs @@ -17,7 +17,7 @@ namespace BMA.EHR.Domain.Shared { if (reader.TokenType == JsonToken.Null) { - return "PARENT"; + return "CHILD"; } return reader.Value; } From 1389df0225d77379cfff242e0e1df13e0092ca76 Mon Sep 17 00:00:00 2001 From: Suphonchai Phoonsawat Date: Mon, 20 Apr 2026 11:42:41 +0700 Subject: [PATCH 179/183] =?UTF-8?q?=E0=B9=81=E0=B8=81=E0=B9=89=E0=B9=84?= =?UTF-8?q?=E0=B8=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ถ้าไม่ได้ Add สิทธิ์มา จะข้ามตำแหน่งนั้นไป แก้บั้กการแสดงข้อมูล กรณี "ROOT" #2431 --- BMA.EHR.Domain/Shared/PrivilegeConverter.cs | 2 +- BMA.EHR.Leave/Controllers/LeaveController.cs | 10 +++---- .../Controllers/LeaveRequestController.cs | 30 +++++++++---------- 3 files changed, 21 insertions(+), 21 deletions(-) diff --git a/BMA.EHR.Domain/Shared/PrivilegeConverter.cs b/BMA.EHR.Domain/Shared/PrivilegeConverter.cs index 4dc7fd85..59f8168c 100644 --- a/BMA.EHR.Domain/Shared/PrivilegeConverter.cs +++ b/BMA.EHR.Domain/Shared/PrivilegeConverter.cs @@ -17,7 +17,7 @@ namespace BMA.EHR.Domain.Shared { if (reader.TokenType == JsonToken.Null) { - return "CHILD"; + return "EMPTY"; } return reader.Value; } diff --git a/BMA.EHR.Leave/Controllers/LeaveController.cs b/BMA.EHR.Leave/Controllers/LeaveController.cs index 8ff74510..1bd92859 100644 --- a/BMA.EHR.Leave/Controllers/LeaveController.cs +++ b/BMA.EHR.Leave/Controllers/LeaveController.cs @@ -3212,14 +3212,14 @@ namespace BMA.EHR.Leave.Service.Controllers // ถ้ามีการรักษาการ if (jsonData.result.isAct) { - var posActs = jsonData.result.posMasterActs; + var posActs = jsonData.result.posMasterActs.Where(x => x.privilege != "EMPTY"); foreach(var act in posActs) { var actRole = act.privilege; string actNodeId = string.Empty; int? actNode; - if (role == "NORMAL" || role == "CHILD") + if (actRole == "NORMAL" || actRole == "CHILD") { actNodeId = act.child4DnaId != null ? act.child4DnaId.Value.ToString("D") : @@ -3244,7 +3244,7 @@ namespace BMA.EHR.Leave.Service.Controllers 0 : null; } - else if (role == "BROTHER") + else if (actRole == "BROTHER") { actNodeId = act.child3DnaId != null ? act.child3DnaId.Value.ToString("D") : @@ -3267,7 +3267,7 @@ namespace BMA.EHR.Leave.Service.Controllers 0 : null; } - else if (role == "ROOT" /*|| role == "PARENT"*/) + else if (actRole == "ROOT" /*|| role == "PARENT"*/) { actNodeId = act.rootDnaId!.Value.ToString("D"); actNode = 0; @@ -3277,7 +3277,7 @@ namespace BMA.EHR.Leave.Service.Controllers if (rawDataAct != null) { if (rawData != null) - rawData = rawData.Union(rawDataAct).ToList(); + rawData = rawData.Union(rawDataAct).DistinctBy(x => x.Id).ToList(); else rawData = rawDataAct; } diff --git a/BMA.EHR.Leave/Controllers/LeaveRequestController.cs b/BMA.EHR.Leave/Controllers/LeaveRequestController.cs index 5720bccc..bbf49dc8 100644 --- a/BMA.EHR.Leave/Controllers/LeaveRequestController.cs +++ b/BMA.EHR.Leave/Controllers/LeaveRequestController.cs @@ -1399,14 +1399,14 @@ namespace BMA.EHR.Leave.Service.Controllers // ถ้ามีการรักษาการ if (jsonData.result.isAct) { - var posActs = jsonData.result.posMasterActs; + var posActs = jsonData.result.posMasterActs.Where(x => x.privilege != "EMPTY"); foreach(var act in posActs) { var actRole = act.privilege; string actNodeId = string.Empty; int? actNode = null; - if (role == "NORMAL" || role == "CHILD") + if (actRole == "NORMAL" || actRole == "CHILD") { actNodeId = act.child4DnaId != null ? act.child4DnaId.Value.ToString("D") : @@ -1431,7 +1431,7 @@ namespace BMA.EHR.Leave.Service.Controllers 0 : null; } - else if (role == "BROTHER") + else if (actRole == "BROTHER") { actNodeId = act.child3DnaId != null ? act.child3DnaId.Value.ToString("D") : @@ -1454,7 +1454,7 @@ namespace BMA.EHR.Leave.Service.Controllers 0 : null; } - else if (role == "ROOT" /*|| role == "PARENT"*/) + else if (actRole == "ROOT" /*|| role == "PARENT"*/) { actNodeId = act.rootDnaId!.Value.ToString("D"); actNode = 0; @@ -1464,7 +1464,7 @@ namespace BMA.EHR.Leave.Service.Controllers if (rawDataAct != null) { if (data != null) - data = data.Union(rawDataAct).ToList(); + data = data.Union(rawDataAct).DistinctBy(x => x.Id).ToList(); else data = rawDataAct; } @@ -1877,14 +1877,14 @@ namespace BMA.EHR.Leave.Service.Controllers // ถ้ามีการรักษาการ if (jsonData.result.isAct) { - var posActs = jsonData.result.posMasterActs; + var posActs = jsonData.result.posMasterActs.Where(x => x.privilege != "EMPTY"); foreach(var act in posActs) { var actRole = act.privilege; string actNodeId = string.Empty; int? actNode = null; - if (role == "NORMAL" || role == "CHILD") + if (actRole == "NORMAL" || actRole == "CHILD") { actNodeId = act.child4DnaId != null ? act.child4DnaId.Value.ToString("D") : @@ -1909,7 +1909,7 @@ namespace BMA.EHR.Leave.Service.Controllers 0 : null; } - else if (role == "BROTHER") + else if (actRole == "BROTHER") { actNodeId = act.child3DnaId != null ? act.child3DnaId.Value.ToString("D") : @@ -1932,7 +1932,7 @@ namespace BMA.EHR.Leave.Service.Controllers 0 : null; } - else if (role == "ROOT" /*|| role == "PARENT"*/) + else if (actRole == "ROOT" /*|| role == "PARENT"*/) { actNodeId = act.rootDnaId!.Value.ToString("D"); actNode = 0; @@ -1942,7 +1942,7 @@ namespace BMA.EHR.Leave.Service.Controllers if (rawDataAct != null) { if (rawData != null) - rawData = rawData.Union(rawDataAct).ToList(); + rawData = rawData.Union(rawDataAct).DistinctBy(x => x.Id).ToList(); else rawData = rawDataAct; } @@ -2180,14 +2180,14 @@ namespace BMA.EHR.Leave.Service.Controllers // ถ้ามีการรักษาการ if (jsonData.result.isAct) { - var posActs = jsonData.result.posMasterActs; + var posActs = jsonData.result.posMasterActs.Where(x => x.privilege != "EMPTY"); foreach(var act in posActs) { var actRole = act.privilege; string actNodeId = string.Empty; int? actNode = null; - if (role == "NORMAL" || role == "CHILD") + if (actRole == "NORMAL" || actRole == "CHILD") { actNodeId = act.child4DnaId != null ? act.child4DnaId.Value.ToString("D") : @@ -2212,7 +2212,7 @@ namespace BMA.EHR.Leave.Service.Controllers 0 : null; } - else if (role == "BROTHER") + else if (actRole == "BROTHER") { actNodeId = act.child3DnaId != null ? act.child3DnaId.Value.ToString("D") : @@ -2235,7 +2235,7 @@ namespace BMA.EHR.Leave.Service.Controllers 0 : null; } - else if (role == "ROOT" /*|| role == "PARENT"*/) + else if (actRole == "ROOT" /*|| role == "PARENT"*/) { actNodeId = act.rootDnaId!.Value.ToString("D"); actNode = 0; @@ -2245,7 +2245,7 @@ namespace BMA.EHR.Leave.Service.Controllers if (rawDataAct != null) { if (rawData != null) - rawData = rawData.Union(rawDataAct).ToList(); + rawData = rawData.Union(rawDataAct).DistinctBy(x => x.Id).ToList(); else rawData = rawDataAct; } From 2e9db2d42cc15d7e86a66621fdae696e195983e3 Mon Sep 17 00:00:00 2001 From: harid Date: Mon, 20 Apr 2026 12:37:06 +0700 Subject: [PATCH 180/183] =?UTF-8?q?=E0=B8=A3=E0=B8=B2=E0=B8=A2=E0=B8=87?= =?UTF-8?q?=E0=B8=B2=E0=B8=99=E0=B8=9B=E0=B8=A3=E0=B8=B0=E0=B8=81=E0=B8=B2?= =?UTF-8?q?=E0=B8=A8=E0=B9=80=E0=B8=81=E0=B8=A9=E0=B8=B5=E0=B8=A2=E0=B8=93?= =?UTF-8?q?=20#2262,=20#2261?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Reports/RetireReportRepository.cs | 6 +- .../BMA.EHR.Retirement.Service.csproj | 7 + .../Controllers/RetirementController.cs | 84 ++- BMA.EHR.Retirement.Service/Program.cs | 2 + .../Services/RetirementReportService.cs | 656 ++++++++++++++++++ .../Templates/retire-1.docx | Bin 0 -> 47968 bytes .../Templates/retire-2.docx | Bin 0 -> 48153 bytes .../Templates/retire-3.docx | Bin 0 -> 48665 bytes .../Templates/retire-emp-1.docx | Bin 0 -> 47953 bytes .../Templates/retire-emp-2.docx | Bin 0 -> 48591 bytes .../Templates/retire-emp-3.docx | Bin 0 -> 48594 bytes 11 files changed, 751 insertions(+), 4 deletions(-) create mode 100644 BMA.EHR.Retirement.Service/Services/RetirementReportService.cs create mode 100644 BMA.EHR.Retirement.Service/Templates/retire-1.docx create mode 100644 BMA.EHR.Retirement.Service/Templates/retire-2.docx create mode 100644 BMA.EHR.Retirement.Service/Templates/retire-3.docx create mode 100644 BMA.EHR.Retirement.Service/Templates/retire-emp-1.docx create mode 100644 BMA.EHR.Retirement.Service/Templates/retire-emp-2.docx create mode 100644 BMA.EHR.Retirement.Service/Templates/retire-emp-3.docx diff --git a/BMA.EHR.Application/Repositories/Reports/RetireReportRepository.cs b/BMA.EHR.Application/Repositories/Reports/RetireReportRepository.cs index ecb65e2f..6ad5a61a 100644 --- a/BMA.EHR.Application/Repositories/Reports/RetireReportRepository.cs +++ b/BMA.EHR.Application/Repositories/Reports/RetireReportRepository.cs @@ -192,7 +192,7 @@ namespace BMA.EHR.Application.Repositories.Reports }).ToList(); } string SignDate = retireHistorys.SignDate != null ? DateTime.Parse(retireHistorys.SignDate.ToString()).ToThaiFullDate().ToString().ToThaiNumber() : "-"; - return new { SignDate, retireHistorys.Detail, retireHistorys.Id, retireHistorys.CreatedAt, Year = retireHistorys.Year.ToThaiYear().ToString().ToThaiNumber(), retireHistorys.Round, retireHistorys.Type, retireHistorys.TypeReport, Total = retireHistorys.Total.ToString().ToThaiNumber(), profiles = mapProfiles }; + return new { SignDate, Detail = retireHistorys.Detail.ToThaiNumber(), retireHistorys.Id, retireHistorys.CreatedAt, Year = retireHistorys.Year.ToThaiYear().ToString().ToThaiNumber(), retireHistorys.Round, retireHistorys.Type, retireHistorys.TypeReport, Total = retireHistorys.Total.ToString().ToThaiNumber(), profiles = mapProfiles }; } } else @@ -312,7 +312,7 @@ namespace BMA.EHR.Application.Repositories.Reports root = (isDuplicateRoot ? "" : profile.root + "\n") + (isDuplicateHospital || !hospital.ToObject>().Contains(profile.child1) ? "" : profile.child1 + "\n") + (isDuplicatePosType ? "" : $"ตำแหน่งประเภท{profile.posTypeName}" + "\n") + - (isDuplicatePosLevel ? "" : $"ระดับ{profile.posLevelName}"), + (isDuplicatePosLevel ? "" : $"ระดับ{profile.posLevelName}").ToThaiNumber(), child = (profile.posExecutiveName == null ? "" : profile.posExecutiveName + "\n") + (profile.child4 == null ? "" : profile.child4 + "\n") + (profile.child3 == null ? "" : profile.child3 + "\n") + @@ -326,7 +326,7 @@ namespace BMA.EHR.Application.Repositories.Reports }).ToList(); } string SignDate = retire.SignDate != null ? DateTime.Parse(retire.SignDate.ToString()).ToThaiFullDate().ToString().ToThaiNumber() : "-"; - return new { SignDate, retire.Detail, retire.Id, retire.CreatedAt, Year = retire.Year.ToThaiYear().ToString().ToThaiNumber(), retire.Round, retire.Type, retire.TypeReport, Total = profile_retire.Count.ToString().ToThaiNumber(), profiles = mapProfiles }; + return new { SignDate, Detail = retire.Detail.ToThaiNumber(), retire.Id, retire.CreatedAt, Year = retire.Year.ToThaiYear().ToString().ToThaiNumber(), retire.Round, retire.Type, retire.TypeReport, Total = profile_retire.Count.ToString().ToThaiNumber(), profiles = mapProfiles }; } } #endregion diff --git a/BMA.EHR.Retirement.Service/BMA.EHR.Retirement.Service.csproj b/BMA.EHR.Retirement.Service/BMA.EHR.Retirement.Service.csproj index 255f2a75..6a820e15 100644 --- a/BMA.EHR.Retirement.Service/BMA.EHR.Retirement.Service.csproj +++ b/BMA.EHR.Retirement.Service/BMA.EHR.Retirement.Service.csproj @@ -40,10 +40,17 @@ + + + + PreserveNewest + + + diff --git a/BMA.EHR.Retirement.Service/Controllers/RetirementController.cs b/BMA.EHR.Retirement.Service/Controllers/RetirementController.cs index ad3105ba..b7a8b8df 100644 --- a/BMA.EHR.Retirement.Service/Controllers/RetirementController.cs +++ b/BMA.EHR.Retirement.Service/Controllers/RetirementController.cs @@ -7,6 +7,7 @@ using BMA.EHR.Domain.Models.Retirement; using BMA.EHR.Domain.Shared; using BMA.EHR.Infrastructure.Persistence; using BMA.EHR.Retirement.Service.Requests; +using BMA.EHR.Retirement.Service.Services; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; @@ -37,6 +38,7 @@ namespace BMA.EHR.Retirement.Service.Controllers private readonly PermissionRepository _permission; private readonly DisciplineDbContext _contextDiscipline; private readonly RetireReportRepository _service; + private readonly RetirementReportService _reportService; public RetirementController(RetirementRepository repository, NotificationRepository repositoryNoti, ApplicationDBContext context, @@ -46,7 +48,8 @@ namespace BMA.EHR.Retirement.Service.Controllers IHttpContextAccessor httpContextAccessor, PermissionRepository permission, DisciplineDbContext contextDiscipline, - RetireReportRepository service) + RetireReportRepository service, + RetirementReportService reportService) { _repository = repository; _repositoryNoti = repositoryNoti; @@ -58,6 +61,7 @@ namespace BMA.EHR.Retirement.Service.Controllers _permission = permission; _contextDiscipline = contextDiscipline; _service = service; + _reportService = reportService; } #region " Properties " @@ -2213,5 +2217,83 @@ namespace BMA.EHR.Retirement.Service.Controllers } } #endregion + + #region รายงานรายชื่อผู้เกษียณอายุราชการ ข้าราชการ & ลูกจ้างประจำ + /// + /// รายงานรายชื่อผู้เกษียณอายุราชการ ข้าราชการ & ลูกจ้างประจำ + /// + /// Id ของรอบเกษียณ + /// pdf, docx + /// + /// เมื่อทำการอ่านข้อมูลจาก Relational Database สำเร็จ + /// ไม่ได้ Login เข้าระบบ + /// เมื่อเกิดข้อผิดพลาดในการทำงาน + [HttpGet("report/{exportType}/{Id}")] + public async Task> GetReportProfileRetirement([FromRoute] Guid Id, string exportType = "pdf") + { + var retire = await _service.GetProfileRetirementdAsync(Id, token); + if (retire != null) + { + var reportfile = string.Empty; + exportType = exportType.Trim(); + + switch (retire.GetType().GetProperty("Type").GetValue(retire)) + { + case "OFFICER": + if (string.IsNullOrEmpty(retire.GetType().GetProperty("TypeReport").GetValue(retire))) + { + reportfile = $"retire-1"; + } + else if (retire.GetType().GetProperty("TypeReport").GetValue(retire) == "ADD" || retire.GetType().GetProperty("TypeReport").GetValue(retire) == "EDIT") + { + reportfile = $"retire-2"; + } + else if (retire.GetType().GetProperty("TypeReport").GetValue(retire) == "REMOVE") + { + reportfile = $"retire-3"; + } + else + { + return Error(retire.GetType().GetProperty("TypeReport").GetValue(retire)); + } + break; + case "EMPLOYEE": + if (string.IsNullOrEmpty(retire.GetType().GetProperty("TypeReport").GetValue(retire))) + { + reportfile = $"retire-emp-1"; + } + else if (retire.GetType().GetProperty("TypeReport").GetValue(retire) == "ADD" || retire.GetType().GetProperty("TypeReport").GetValue(retire) == "EDIT") + { + reportfile = $"retire-emp-2"; + } + else if (retire.GetType().GetProperty("TypeReport").GetValue(retire) == "REMOVE") + { + reportfile = $"retire-emp-3"; + } + else + { + return Error(retire.GetType().GetProperty("TypeReport").GetValue(retire)); + } + break; + default: + return Error(retire.GetType().GetProperty("Type").GetValue(retire)); + } + + var reportBytes = await _reportService.GenerateReportAsync(reportfile, retire, exportType); + + var fileName = $"reportRetirement-{DateTime.Now:yyyyMMdd-HHmmss}.{exportType}"; + var contentType = exportType.Trim().ToLower() == "pdf" + ? "application/pdf" + : "application/vnd.openxmlformats-officedocument.wordprocessingml.document"; + + return File(reportBytes, contentType, fileName); + + } + else + { + return NotFound(); + } + } + #endregion } } diff --git a/BMA.EHR.Retirement.Service/Program.cs b/BMA.EHR.Retirement.Service/Program.cs index ee807e0a..a55f9674 100644 --- a/BMA.EHR.Retirement.Service/Program.cs +++ b/BMA.EHR.Retirement.Service/Program.cs @@ -3,6 +3,7 @@ using BMA.EHR.Domain.Middlewares; using BMA.EHR.Infrastructure; using BMA.EHR.Infrastructure.Persistence; using BMA.EHR.Retirement.Service; +using BMA.EHR.Retirement.Service.Services; using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.ApiExplorer; @@ -86,6 +87,7 @@ var builder = WebApplication.CreateBuilder(args); builder.Services.AddApplication(); builder.Services.AddLeaveApplication(); builder.Services.AddPersistence(builder.Configuration); + builder.Services.AddScoped(); builder.Services.AddLeavePersistence(builder.Configuration); builder.Services.AddHttpClient(); diff --git a/BMA.EHR.Retirement.Service/Services/RetirementReportService.cs b/BMA.EHR.Retirement.Service/Services/RetirementReportService.cs new file mode 100644 index 00000000..b60fad8f --- /dev/null +++ b/BMA.EHR.Retirement.Service/Services/RetirementReportService.cs @@ -0,0 +1,656 @@ +using BMA.EHR.Application.Responses; +using DocumentFormat.OpenXml.Packaging; +using DocumentFormat.OpenXml.Wordprocessing; +using System.Diagnostics; +using System.Reflection; +using System.Runtime.InteropServices; + +namespace BMA.EHR.Retirement.Service.Services +{ + public class RetirementReportService + { + private readonly IWebHostEnvironment _environment; + private readonly ILogger _logger; + private readonly IConfiguration _configuration; + + /// + /// Initializes a new instance of the RetirementReportService class. + /// + public RetirementReportService( + IWebHostEnvironment environment, + ILogger logger, + IConfiguration configuration) + { + _environment = environment; + _logger = logger; + _configuration = configuration; + } + + #region Public Methods + + /// + /// สร้างรายงานจาก Template (.docx) + /// + public async Task GenerateReportAsync(string templateName, dynamic data, string exportType) + { + try + { + var templatePath = GetTemplatePath(templateName); + var docxBytes = await ProcessTemplateAsync(templatePath, data); + + return exportType.ToLower() == "pdf" + ? await ConvertToPdfAsync(docxBytes) + : docxBytes; + } + catch (Exception ex) + { + _logger.LogError(ex, "Error generating report"); + throw; + } + } + + #endregion + + #region Template Processing + + private string GetTemplatePath(string templateName) + { + var path = Path.Combine(_environment.ContentRootPath, "Templates", $"{templateName}.docx"); + if (!File.Exists(path)) + throw new FileNotFoundException($"Template not found: {templateName}"); + return path; + } + + private async Task ProcessTemplateAsync(string templatePath, dynamic data) + { + using var templateStream = File.OpenRead(templatePath); + using var outputStream = new MemoryStream(); + await templateStream.CopyToAsync(outputStream); + outputStream.Position = 0; + + using (var wordDoc = WordprocessingDocument.Open(outputStream, true)) + { + var mainPart = wordDoc.MainDocumentPart; + if (mainPart == null) return Array.Empty(); + + ReplacePlaceholders(mainPart, data); + wordDoc.Save(); + } + + return outputStream.ToArray(); + } + + private void ReplacePlaceholders(MainDocumentPart mainPart, dynamic data) + { + var document = mainPart.Document; + if (document == null) return; + + var processor = CreateDataProcessor(data); + processor.Process(document, new Action(FillTableRows)); + } + + #endregion + + #region Data Processing Strategy + + private IDataProcessor CreateDataProcessor(dynamic data) + { + var dataType = data.GetType(); + var isDictionary = dataType.IsGenericType && + dataType.GetGenericTypeDefinition() == typeof(Dictionary<,>); + + return isDictionary + ? new DictionaryDataProcessor(data) + : new ObjectDataProcessor(data); + } + + #endregion + + #region Table Processing + + private void FillTableRows(Document document, System.Collections.IEnumerable profiles) + { + var table = document.Descendants().FirstOrDefault(); + if (table == null) return; + + var rows = table.Elements().ToList(); + if (rows.Count == 0) return; + + var strategy = CreateTableStrategy(rows); + strategy.Process(table, rows, profiles); + } + + private static ITableStrategy CreateTableStrategy(List rows) + { + // retire-1 format: 1 row, 1 cell, 1 paragraph + if (IsSingleParagraphFormat(rows)) + return new SingleParagraphTableStrategy(); + + // retire-3 format: 2+ rows (header + template) + return new MultiRowTableStrategy(); + } + + private static bool IsSingleParagraphFormat(List rows) => + rows.Count == 1 && + rows[0].Elements().Count() == 1 && + rows[0].Elements().First().Elements().Count() == 1; + + #endregion + + #region PDF Conversion + + private async Task ConvertToPdfAsync(byte[] docxBytes) + { + var tempDocx = Path.Combine(Path.GetTempPath(), $"{Guid.NewGuid()}.docx"); + var tempPdf = Path.ChangeExtension(tempDocx, ".pdf"); + + try + { + await File.WriteAllBytesAsync(tempDocx, docxBytes); + await ConvertToPdfInternalAsync(tempDocx, tempPdf); + return await File.ReadAllBytesAsync(tempPdf); + } + finally + { + if (File.Exists(tempDocx)) File.Delete(tempDocx); + if (File.Exists(tempPdf)) File.Delete(tempPdf); + } + } + + private async Task ConvertToPdfInternalAsync(string docxPath, string pdfPath) + { + try + { + var useDocker = _configuration.GetValue("LibreOffice:UseDocker", false); + var timeout = _configuration.GetValue("LibreOffice:Timeout", 180000); + + if (useDocker) + { + await ConvertToPdfViaDockerAsync(docxPath, pdfPath, timeout); + } + else + { + // PROD: Disabled local LibreOffice conversion + // await ConvertToPdfLocallyAsync(docxPath, pdfPath, timeout); + throw new NotSupportedException("LibreOffice conversion is disabled."); + } + } + catch (Exception ex) + { + _logger.LogError(ex, "Error converting to PDF"); + throw; + } + } + + private async Task ConvertToPdfViaDockerAsync(string docxPath, string pdfPath, int timeout) + { + var container = _configuration["LibreOffice:DockerContainer"] ?? "libreoffice"; + var workingDir = _configuration["LibreOffice:WorkingDirectory"] ?? "/app/libreoffice/files"; + var dockerFilesPath = _configuration["LibreOffice:DockerFilesPath"] ?? "/files"; + var arguments = _configuration["LibreOffice:Arguments"] ?? "--headless --convert-to pdf --nologo --norestore"; + + // Ensure working directory exists + if (!Directory.Exists(workingDir)) + { + Directory.CreateDirectory(workingDir); + } + + // Copy file to shared directory + var fileName = Path.GetFileName(docxPath); + var sharedDocxPath = Path.Combine(workingDir, fileName); + var sharedPdfName = Path.ChangeExtension(fileName, ".pdf"); + var sharedPdfPath = Path.Combine(workingDir, sharedPdfName); + + await File.WriteAllBytesAsync(sharedDocxPath, await File.ReadAllBytesAsync(docxPath)); + + // Run LibreOffice inside Docker container + var dockerCmd = $"docker exec {container} libreoffice {arguments} --outdir {dockerFilesPath} {dockerFilesPath}/{fileName}"; + var psi = new ProcessStartInfo + { + FileName = "cmd.exe", + Arguments = $"/c \"{dockerCmd}\"", + UseShellExecute = false, + RedirectStandardOutput = true, + RedirectStandardError = true, + CreateNoWindow = true + }; + + using var process = Process.Start(psi); + var exited = process.WaitForExit(timeout); + + if (!exited) + { + process.Kill(entireProcessTree: true); + throw new TimeoutException($"LibreOffice Docker conversion timed out after {timeout}ms"); + } + + if (process.ExitCode != 0) + { + var error = await process.StandardError.ReadToEndAsync(); + throw new Exception($"LibreOffice Docker conversion failed: {error}"); + } + + // Copy result back + if (!File.Exists(sharedPdfPath)) + { + throw new FileNotFoundException($"PDF not generated in shared directory: {sharedPdfPath}"); + } + + await File.WriteAllBytesAsync(pdfPath, await File.ReadAllBytesAsync(sharedPdfPath)); + + // Cleanup shared files + try + { + if (File.Exists(sharedDocxPath)) File.Delete(sharedDocxPath); + if (File.Exists(sharedPdfPath)) File.Delete(sharedPdfPath); + } + catch (Exception ex) + { + _logger.LogWarning(ex, "Failed to cleanup shared files"); + } + } + + // PROD: Disabled local LibreOffice conversion + // private async Task ConvertToPdfLocallyAsync(string docxPath, string pdfPath, int timeout) + // { + // var libreOfficePath = _configuration["LibreOffice:Path"] ?? GetDefaultLibreOfficePath(); + // var arguments = _configuration["LibreOffice:Arguments"] ?? "--headless --convert-to pdf --nologo --norestore"; + // var outputDir = Path.GetDirectoryName(pdfPath); + // + // if (string.IsNullOrEmpty(outputDir)) + // { + // throw new DirectoryNotFoundException("Output directory cannot be determined"); + // } + // + // var psi = new ProcessStartInfo + // { + // FileName = libreOfficePath, + // Arguments = $"{arguments} --outdir \"{outputDir}\" \"{docxPath}\"", + // UseShellExecute = false, + // RedirectStandardOutput = true, + // RedirectStandardError = true, + // CreateNoWindow = true + // }; + // + // using var process = Process.Start(psi); + // var exited = process.WaitForExit(timeout); + // + // if (!exited) + // { + // process.Kill(entireProcessTree: true); + // throw new TimeoutException($"LibreOffice conversion timed out after {timeout}ms"); + // } + // + // if (process.ExitCode != 0) + // { + // var error = await process.StandardError.ReadToEndAsync(); + // throw new Exception($"LibreOffice conversion failed: {error}"); + // } + // } + + // PROD: Disabled local LibreOffice path detection + // private static string GetDefaultLibreOfficePath() + // { + // if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + // { + // var possiblePaths = new[] + // { + // @"C:\Program Files\LibreOffice\program\soffice.exe", + // @"C:\Program Files (x86)\LibreOffice\program\soffice.exe", + // @"C:\Program Files\LibreOffice\program\soffice.com" + // }; + // + // return possiblePaths.FirstOrDefault(File.Exists) + // ?? throw new FileNotFoundException("LibreOffice not found. Please install LibreOffice or configure the path in appsettings.json"); + // } + // + // // Linux/Docker: use default path + // return "libreoffice"; + // } + + #endregion + } + + #region Data Processor Interfaces & Implementations + + internal interface IDataProcessor + { + void Process(Document document, Action tableFiller); + } + + internal class DictionaryDataProcessor : IDataProcessor + { + private readonly dynamic _data; + + public DictionaryDataProcessor(dynamic data) + { + _data = data; + } + + public void Process(Document document, Action tableFiller) + { + var keys = _data.Keys as System.Collections.ICollection; + if (keys == null) return; + + System.Collections.IEnumerable? profiles = null; + + foreach (string key in keys) + { + if (key.Equals("profiles", StringComparison.OrdinalIgnoreCase)) + { + profiles = _data[key] as System.Collections.IEnumerable; + continue; + } + + var valueObj = _data[key]; + if (valueObj != null && typeof(System.Collections.IEnumerable).IsAssignableFrom(valueObj.GetType()) && + valueObj.GetType() != typeof(string)) + { + continue; + } + + var value = valueObj?.ToString() ?? string.Empty; + var placeholder = $"{{{{{key}}}}}"; + TextReplacer.ReplaceAll(document, placeholder, value); + } + + if (profiles != null) + { + tableFiller(document, profiles); + } + } + } + + internal class ObjectDataProcessor : IDataProcessor + { + private readonly dynamic _data; + + public ObjectDataProcessor(dynamic data) + { + _data = data; + } + + public void Process(Document document, Action tableFiller) + { + var dataType = _data.GetType(); + var allProps = dataType.GetProperties(); + var validProps = new List(); + + foreach (var p in allProps) + { + if (p.GetIndexParameters().Length == 0) + { + validProps.Add(p); + } + } + + System.Collections.IEnumerable? profiles = null; + + foreach (var prop in validProps) + { + var propType = prop.PropertyType; + bool isEnumerable = typeof(System.Collections.IEnumerable).IsAssignableFrom(propType); + bool isString = propType == typeof(string); + + if (isEnumerable && !isString) + { + if (prop.Name.Equals("profiles", StringComparison.OrdinalIgnoreCase)) + { + profiles = prop.GetValue(_data) as System.Collections.IEnumerable; + } + continue; + } + + var value = prop.GetValue(_data)?.ToString() ?? string.Empty; + var placeholder = $"{{{{{prop.Name}}}}}"; + TextReplacer.ReplaceAll(document, placeholder, value); + } + + if (profiles != null) + { + tableFiller(document, profiles); + } + } + } + + #endregion + + #region Text Replacer + + internal static class TextReplacer + { + public static void ReplaceAll(Document document, string oldValue, string newValue) + { + bool found = false; + + // Method 1: Check within single Run + foreach (var run in document.Descendants()) + { + var textElements = run.Elements().ToList(); + if (textElements.Count == 0) continue; + + var combinedText = string.Concat(textElements.Select(t => t.Text)); + if (combinedText.Contains(oldValue)) + { + found = true; + var replacedText = combinedText.Replace(oldValue, newValue); + textElements[0].Text = replacedText; + for (int i = 1; i < textElements.Count; i++) + { + textElements[i].Text = string.Empty; + } + } + } + + // Method 2: Check across all Runs in Paragraph + foreach (var para in document.Descendants()) + { + var allRuns = para.Elements().ToList(); + if (allRuns.Count == 0) continue; + + var combinedParaText = string.Concat(allRuns.SelectMany(r => r.Elements().Select(t => t.Text))); + if (combinedParaText.Contains(oldValue)) + { + found = true; + var replacedText = combinedParaText.Replace(oldValue, newValue); + + var firstRunTexts = allRuns[0].Elements().ToList(); + if (firstRunTexts.Count > 0) + { + firstRunTexts[0].Text = replacedText; + for (int i = 1; i < firstRunTexts.Count; i++) + { + firstRunTexts[i].Text = string.Empty; + } + } + + for (int i = 1; i < allRuns.Count; i++) + { + foreach (var t in allRuns[i].Elements()) + { + t.Text = string.Empty; + } + } + } + } + + // Fallback: Check individual Text elements + if (!found) + { + foreach (var text in document.Descendants()) + { + if (!string.IsNullOrEmpty(text.Text) && text.Text.Contains(oldValue)) + { + found = true; + text.Text = text.Text.Replace(oldValue, newValue); + } + } + } + } + + public static void ReplaceInRow(TableRow row, string oldValue, string newValue) + { + bool found = false; + + foreach (var cell in row.Descendants()) + { + foreach (var para in cell.Elements()) + { + found = ReplaceInParagraph(para, oldValue, newValue) || found; + } + } + + // Fallback: Check individual Text elements + if (!found) + { + foreach (var text in row.Descendants()) + { + if (!string.IsNullOrEmpty(text.Text) && text.Text.Contains(oldValue)) + { + found = true; + text.Text = text.Text.Replace(oldValue, newValue); + } + } + } + } + + public static bool ReplaceInParagraph(Paragraph paragraph, string oldValue, string newValue) + { + bool found = false; + + var allTexts = paragraph.Descendants().ToList(); + if (allTexts.Count == 0) return false; + + var combinedParaText = string.Concat(allTexts.Select(t => t.Text)); + + if (combinedParaText.Contains(oldValue)) + { + found = true; + var replacedText = combinedParaText.Replace(oldValue, newValue); + allTexts[0].Text = replacedText; + + for (int i = 1; i < allTexts.Count; i++) + { + allTexts[i].Text = string.Empty; + } + } + + // Fallback: Check individual Text elements + if (!found) + { + foreach (var text in allTexts) + { + if (!string.IsNullOrEmpty(text.Text) && text.Text.Contains(oldValue)) + { + found = true; + text.Text = text.Text.Replace(oldValue, newValue); + } + } + } + + return found; + } + } + + #endregion + + #region Table Strategy Interfaces & Implementations + + internal interface ITableStrategy + { + void Process(Table table, List rows, System.Collections.IEnumerable profiles); + } + + internal class SingleParagraphTableStrategy : ITableStrategy + { + public void Process(Table table, List rows, System.Collections.IEnumerable profiles) + { + var cell = rows[0].Elements().First(); + var templatePara = cell.Elements().First(); + + var profileList = profiles.Cast().ToList(); + + foreach (var profile in profileList) + { + var props = profile.GetType() + .GetProperties() + .Where(p => p.GetIndexParameters().Length == 0) + .ToList(); + + var newPara = (Paragraph)templatePara.CloneNode(true); + + foreach (var prop in props) + { + var value = prop.GetValue(profile)?.ToString() ?? string.Empty; + var placeholder = $"{{{{{prop.Name}}}}}"; + TextReplacer.ReplaceInParagraph(newPara, placeholder, value); + } + + cell.Append(newPara); + } + + templatePara.Remove(); + } + } + + internal class MultiRowTableStrategy : ITableStrategy + { + public void Process(Table table, List rows, System.Collections.IEnumerable profiles) + { + var templateRowIndex = rows.Count >= 2 ? 1 : 0; + var templateRow = rows[templateRowIndex]; + templateRow.Remove(); + + var profileList = profiles.Cast().ToList(); + + // Process header row if exists + if (rows.Count >= 2) + { + ProcessHeaderRow(rows[0], profileList); + } + + // Process template rows + foreach (var profile in profileList) + { + var newRow = (TableRow)templateRow.CloneNode(true); + var props = profile.GetType() + .GetProperties() + .Where(p => p.GetIndexParameters().Length == 0) + .ToList(); + + foreach (var prop in props) + { + var value = prop.GetValue(profile)?.ToString() ?? string.Empty; + var placeholder = $"{{{{{prop.Name}}}}}"; + TextReplacer.ReplaceInRow(newRow, placeholder, value); + } + + table.AppendChild(newRow); + } + } + + private static void ProcessHeaderRow(TableRow headerRow, List profileList) + { + var firstProfile = profileList.FirstOrDefault(); + if (firstProfile == null) return; + + var props = firstProfile.GetType() + .GetProperties() + .Where(p => p.GetIndexParameters().Length == 0) + .ToList(); + + foreach (var prop in props) + { + var value = prop.GetValue(firstProfile)?.ToString() ?? string.Empty; + var placeholder = $"{{{{{prop.Name}}}}}"; + + if (!string.IsNullOrWhiteSpace(value)) + { + TextReplacer.ReplaceInRow(headerRow, placeholder, value); + } + } + } + } + + #endregion +} diff --git a/BMA.EHR.Retirement.Service/Templates/retire-1.docx b/BMA.EHR.Retirement.Service/Templates/retire-1.docx new file mode 100644 index 0000000000000000000000000000000000000000..aae5858776a53e5f86285d3d811350b92746694a GIT binary patch literal 47968 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%0Dgd7?;HZmiVBvVimx^(9P&q~U$Iqp9qDLD z#O>BzCBtUL`q1(hJ4aNte3NU~ix26& z(YF3}uj}9ESBvs4oQ$?{_wn)J{#M-sgU|t%pa1cP9W@e9NCNcNZNQ=w%)*!`{S6=T`?-kp4FT{CMov3qVmhK!=C<7lBE@;zrNW#M+60{#W`tFty%E zm2K^Az(3c`&e50wFm=1w0NDRud}|s>ki-EM7XcK;{TmColSTUr{wMQ) z68S$XqBLT0yauSf8o-9}%cA-Bv4rs#B~v>)XInexKMfkc{}60Qamv+QHn!z}Up`57*@1nalRe5|{m-=pMp< zRQ_LR=)brS0o|1+5VDyA0sXoFt@e*2iR3S|jft^^0fU8&ftd*ty}hm3?-lsnq55wc zuaSR|_}#twcM`w5qy0?+B=Ij2|8!3K9savT`foU2=3nrC*{6TU|8Bqf8?TW2ANK#X zX8jKSU8wsT>|XU3_%{*ncM8AD0e@2nt@(?>zodfS@&8`${EY^T>3Sfb|5^R~4*&ND h>z{C)rhmZy`g|!!g9FCfuYQgM#194-ysf{U{y!C6g%tn* literal 0 HcmV?d00001 diff --git a/BMA.EHR.Retirement.Service/Templates/retire-2.docx b/BMA.EHR.Retirement.Service/Templates/retire-2.docx new file mode 100644 index 0000000000000000000000000000000000000000..4c9db823e9aad4bf5bd217f5d51caa9a66c33b9d GIT binary patch literal 48153 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 literal 0 HcmV?d00001 diff --git a/BMA.EHR.Retirement.Service/Templates/retire-3.docx b/BMA.EHR.Retirement.Service/Templates/retire-3.docx new file mode 100644 index 0000000000000000000000000000000000000000..4257c5f6d297b5648310aaa87240d320fd3dedad GIT binary patch literal 48665 zcmeFXWl$x})~3C2cXw#qoyOg@>BgPLosHADvvGHaZfM-y-D#k4cXzkV@SJyM;>^VP z^F@6B-pPupimJ+r6`8fJTr2OaFA7l5*Z^1nJOBWo01TPPBqTuq02^=s02TlqQdh#k z-o?z`#Zb-D(ac$&#ly~)yZ{=KE)M|tQUCvy|HeH~pFC#W$A&KT5CRUGZd{aarTf`G zQV?rKWcdsc!QCiT0XMUI_dqfPqNfH)U^yVqjCVZ9a*SKn&a2l$lO=J}6VX8728@)e z{gPc;IA!)VRTVWZDk%?0vLmk^dAv90TH%Gq_^l+X4LZ!>Mbk#m3GU}-QyZyRfV4V7 zI+wg>M%;V|>VcXn7h}UWIqMlL#2X$c=sk2*T0sGE!bIj|cJecr$uB(r$R$(rDCs2w zIaYp67cIT{8SmG;P4Z2A)J}yi8Oi;xBUOGSG1}sh@F7P>0=iPjkbC~`$g1*Gy=B{c zx%8|?zHX}d@64h>RNJ{y@!1T7a{v5TJmxXMkUzwe)V= zfry9p0i0$DU}&(tpGI$|deX|;GZRMov`gEpAwWpq_gA~0OVWwSUiO_}aSd8@NU%Od zUlBYd(eC5iidEO02a6Jt|M6z+0#!`>mOak;aRP}#^&V?-aUfF>bJUG0SUT&clPNoo zmy32hO(jM;yuWfK2>w9!{T&MM<^O=0G@h{O;=@CgKUhWjfZ5Q=%+{He<)7pKgZRHO zx&PMm@`Robh>=Avg5H9rJ5-l@@C%h$P5!LlFTug<$|+*4ELtys-vk#IA@z@pWyWS^ z6aV~h&XRWBOwzw3Oi@RP=s=i%GU(HNY4-pmhqV7>_n3FwM$X*7H-8(Yl%pRFiqyu5 zm?DIYe+*BX=*HS(ki6e3MzZ=uKlRsys;)2xsJ`%!=F6Fv&OSG9EvxGVS1Kgc8A{SR zN#KiUE>e@q$s8BkyI!l`uXdc+%n@x(f5${<%f?Eb75}{)k?^v8`uIm-SMdNSgb5)Z z0mS;^ZLWu@13rk@ClKMepwO;2+;Skw(igk=k9;6GkAF5m2LSlT0RW7TPH}f|GG#S& zFmbi}hJ4K*p)#XU)DeGqSdT&s#UTPpMOse>gb|z~n$J z<0XfYXN9y#zf9Xiw+adIz3>nPCWA|?;aytfr$s1Ud}~F~-MlHkjRfK3UM8nSr?WXP zG0KMLL$7NyC$@CsMS}-WWs`3=z0=H#mKi(^e2Y;ig$fo-Wzmun3CbbXWB3*y5YNA!K>h5MgWfcgE&qJx0KUuX=%;Ck~uEpUV?y_L6qQ$|1Bt%?LkW;th zCb)ZNfsSzgP*5vSD>YZtMu^skCX4GwsF-5Ek$Wn1btx^m7nA!a zOr|aPymLLGsR7BCsg5+qZG8wSfT27Vnt8~=vaUpoG^fKXiF!EwP!LaIy56&fgMukT zKfJ^blhvY}aUqEQnX5s)vVzFH57`mxDzVc!TkHBRyr5dl0W-Y3pXL+xi6S*Hprn4E47B*C@NF$@I7 zc}oQti$_ugh56?nnWVvZ(i;h0AfQke19vHR3(B^SP9x zD#kT4xp)%*e!?C?hlWvE3}l#kVW7qaRQ4azMdyi$g@3}%q43QWc+I<6FSNhtF+O;l z?js%Yu>`hX7z)BlK-Ia#n-OuZa4Y{Y5FOk{>zeA=@baiyc)#j3LMa(VRwzD|+TY%|X>CQdo)S?73NyQ4m*)Kcb*tl;MdmPD*$sipulntMm!`Qd z-A=)wQEgbYXJMG~EMB$!%A`vv-#ry^d(!ZFI@e%dd+KD!04C=+^D}$wpzeTk_9%;jIZ1>YgOtxlq5!9a2BUtCTp& zDSNn{?dHwa(eQb=pW$th;q--{o=`J$nq$W@C%2JKR=Xf`1x~eJ zGbsl(?A}8K%QlzO+7%6GSQAr6#&*rFJ3T!qI;>x07P#|2>DK>q>V0+N$ zZe?&4aCU%+;+#b^AsC@tYH5NLPZIIo7jhLIiihUYe}-c|$*hdT1 zS7(LilE6$Yc38K)bupQ9ydhB~hm(XEOYA{WCCoM*_a%HSVv^5fP=dZ=EQ^@CQ2Vmv7$|;8kL)F}~Bu-tSKf=ubKJEa$AcD-TF_ z#Ox5;vn{w zK5)3&WzHGs@b(HhdJv3MKoJSmahj!&{ZYIgr{3u?8~;0vH*RqXd{xL^kMmjiVd9<4ybf--nG`9zqmkIbM~*N_=ME{t%hR$4}}7r zKbcBx?v`N~G;MeJvqd2*yn-C3l_Xn#NTYTPb&}*a_?($aN?jV8tP7rBZeh!d%20{* zaRZkw4G-^-bWSsm=ITl({1+za=VD68`aAbic7>$!Q{|f`AaNd%hJ@KK@p&aW;FNB? zSzqS9E%z3>q6i+2Y**N+P^BZ;BsFblz4V_D;#b(z&S;?ae_f;X{+czzOvc!4FR6bX z&pp4YrK)+XqkDui6yd9qEXi@+xo#Z5_nrxAskx~-7B>)7n_%4qI@Fk@R=|=deQA&e z-Rl}Q#Z6ZP^`|$1n3ZQg26f4a32Wu?9w0cApC;|G^HJl8 zL>n9WC|X2)WX!8wS+7C$Vz>^ZqWu;VixS3{!%@MWvT|{1QRYDG%4s-=l`n2$J*JtM zltVLbc6}+_wMldIUB)``&4F*>FRqklqYABiSll0WxqNr{7PGPU$dE$J^zU=TZtmI5 zu5$5&b!W*n1PlrD7-*w^VS`b;dQ4JfJLk^%EXt**Q}25E5Ql`aUt`v*gny!55;^)4 zd5Fj@421q*YZ9)#1@>jdIIUNu)&6`ai;7ji35Qd@PQD%L=KCa5SM)PBIw0sgglEk} zDUnQK#xv5v(v@G*s1;$Qm2Dp8;@|-+)6>AT%ml0pIo$8DF%%>?32gJ*gXnh zS%lMM~=+I^%juvR{`ozY3gx#qU-P#dV}3EQv|=J>DpEbXmC1LkcZO&XefX z3ELx+m<$zBT=M86;BRFHfnXk00!M8josop`NxrTSlmtaj?HATW3=aH5gbXvVl(&IpWa)C-{~fE zaGqGDz_q+ys=HH>b;VL(iFiMq-3{y~!+PK!w^`Ds-I0->E`L@QFKYPe;2v*pJ5Y@) zPf*$P+kET=D3uzbZVJ=FTJ}o+%sceG?oXW&K|kRCl!Jk3hs9An2rq0=(MWP_F1u9r zC0hJS>mjhrwgp?b-fkXAhw4RqqiObZs!$(qHQuj_c9UcNJ7a~~>mkoRI&HH{D{rY! zJ5PGqY_q11QGS11o-+`?4rz|%{B^iDHj9qZic24(6Ru-B=8g+7pjb;(GZQumLQRb* z0|m;uv{b?@8{!PZKc(>q*4r!U(wuMJE&hF+vxH2WA=%Gxw+rtHPYN7`XL+~q={*yv zefPa8qHfA_tB&F5xperQ>H2~{tMeK@1D)sUWhKn6CWW73Yegpkm$ z4G09$WbijDB?Sjv3yr?fNy`pmsZ`zPBT z&&88YBEyLbHlg%iUDxVk*CNQi~t-e@b8$ZF89m%0W< zW3_l8U2uohf1elWwV)#xKp{Nbgz$2SlbsDPXm+6|*ylj2M&pb}N9Za>BA{Dd+C zl=NZgz#|l|AQ&uK0cf?3Idu+2_4(QXP`Vw^aO=xslT@iMA7`&ay>wC9B=$7m_MdwQN}^X zstok#N1{O0c=A#Sc&cej;SBqlQ5p4)c+REHB*T0jNsei-MiR_7bSQI{( z1tlu}VqBumHL@+kq4dm%hgSP7Rc0-{zORDUL{&4KyqT@hJxs`yj(cE{hP*7d;& zDR{f0tdx06EL8ykF;?=AMysvGuoB8}_$4oE;-bkD?A{5H#$DG$hf%(sxY+OFc>*V5wu2REVG-e@M!>azcPbud zI^kG?suU-PUu%Xk(y;W<>@f%s@^sO@NCMQ05`RB2R5(uJWxJEX(n|tK@WgjwX<+HK zq;NAK$0;DwHW`69obO1uRb8E5a_}KrA`xZ)c)l1bo^*kvuelKp{|eOqx2F69gV1y7 zLr>N~1^_TWYW&Mh%^e(E>>XUpoc|Hc##2_DGTG2WF9c@Yjhfw|AIJMA>-k)q7UY)U zz67&nGcL+@X<@^2{-a=rZ>q-xfv!AjTRp8;F zs(5M8H7S%Sklw=MAg9LZ;@+@pb>Dcr#az#14|$$^B4el?Y{$5lTd;1Z}y$ zn;QSj<^EywL+iLqrm*{{+HAr}*I$KSy>)(+qK0t~jo1vEQ(qEAHYcaB24b;I4&o=KOu= zCL|R^CE8|i_x3laKvJWS%kHrPb6W;jQ9CzXRDv`O?&4LPF$z1Qw*cT6U5DSr9Etz#ib4J2;GJnMvCRD)1RVoG{0;d>AYeAdV5X4^n9U7l{vv(tQD zZx&TQ*&I-cw6Khqy@H#4SB`XbY48Ou#T}oC0T!Jibupc{FdNI3t~C?K(McMzO9V;v z`#$-K%yfTo!A4GQDMoQ4hFJ_V=)Cg==WsV)7)e4+OB8`SweqO;iR&g3)Jun#PvXUs zeKU0D_EN9cELGKF(r~7)DZjYJSFQ_9<;#YBMgvG#l-+Z$a#9cBrFrP1Qh8V2T8@hicBK&Nv!$s&943n1l|7$ z0#+}o?gt1LkqswwDB6(Dlb7=yMti;y)lP{TB!q{{kUe;s97L*_()=YXtoT_UGV1%$CXr2(aa0 zc}B4RfFPm9OV9v0UkX8&0mGqzB0J_;#5h^8A0J3@CBoxKjaZ=urhBGS#VTsFd^^re&HU%_`(5R#upt#FlPT98=|8 zq=ti1EThkxB@QoHQ@OT1)TQ=;{AV3L8nD9?}Tjz%osM*b^^}Co-Sca>{YLQfD6*lH*)8ydRn<2ITStpI?8^XK` z+YLFi+%>5msx-|#9h!yK$i960R3vOZ?hG69dl!2k7H*K*dn8(l#)l*^K)D6#euLQb zg)Z|^?|$Xx+vk~8o`l{S>ghFCvVckYc|2o(L?pWW{TXh3o@`uSJ{5dp?3EgsS%MJN z$4l zv=4d%t4fF~32sK4kvp7@=YS<~NvJ7z{%e&_$3D-tB9=xH-rUU!x`*Z`bfyb3o)@!A zGU?4y)(go(o3b^s{}>VMOJCiJKR`f00suZ1Q~oO=SehA|nmMum6Ay|~*6dfvv4<{b z&q$ayiWikLerY6Hm(Klm|JEb~fFag}2?Ed_<@mz+Ijl3ROoUKuzQ^`$Wtp6xvj%$6 z{!(Iz>>x9M=^R4*WMx470G=X?Ez7@&4c-hO<=Y3m%OyW9Ks%4z21Xt8NX^iwv(p^`h>zqov5(s>h~(6zWf>9bP4nK-YQ6F ze^;`=vX6^Py5hE@ce+Sz4JUdlofVkf(b?e24!p7&>vd`h*Jt|NICo6nS+)nF`e_tW z(H_dRvnivU#-yq5imqVAmfjFI>xIgTu-@sevDL5kVrZpsY;qXzZQNl}`EvOK0%Nu< zE|P+8itJlc+`0O*pgNXQSR0+ek|;IC%=lQpIOPxXo)8StIeX?~~~V1P{oJCA)$BDnsJd zuf08=WDU_c{*)N$7qx>+1^8Dn5B7Zd7y)mo??0zGN(ix>4to6Vfbqfm>y>xiosgYlyb%pz>kX^gODtNh$Xf39 zUplnEAu%cmr?2Dq1e8T`(tPtsIat*7vJ-*uF_Ibk!3Bw55F!HEN`8Dr5N`JciMqdL6~L&!|7deIWZC^oz&H~RP>lQ>2_ zO)LR5biwTG$kJKAr0OHsXHLV+JEuSQ&<0_NAPH$j_x$c3~o--YUn=~>c`Ce&^nMtO9; zLkjr21Fh-2-x7j zCgR7S{7<0(-2F}<=dlC;{@HOp?))oxw=*-fGG?{1Gqx~eXK}Q*0DV!GK|v(^=PeXD zS;?;-=W_r60tWtLVhY$LTv_-yL0gC^i2(pLu}H7RFdx?lj7up)g)= z&D(TUOlvzbzsHj}T)QlCi{RhcKiAppV<7Im9Fb{mcD=uJyzXcRsSU=2YOZ=yrP%D7jzg^!_> zjKkWed-{C5SqZh!=m)-0xJqnS+hVrG)JycYGd-6H4FuXefT4gQe2}!$CV!wz2%Gq2J4e-|OuSDDS)vLS17U z{MK~v(4-;=WG-)3TMX7U4;Si7YF_mya6az|NTm3u@pskR8r#p&DBC=(-$y%rNnV^+ zY*J%XwuP_y?Z{KXk2fb_X>@5>nL?XfW@Opm*8>seT!ZejV_IOY$M#v8y54333rA_I z#C`R%A4N>&dCp= zHygq@Li>}`AO{aLMGh1%Ur8T6dL>kFEZffa?%3?e^57++d3|~m0({a1E{kNtGIjef zKpP{rO_Sj61jGZbx!39AdMj^Nng!D4czh>X_bxSTiU}8Z7Jk41da|^jYO&^ICpG?K z6gV#H={oRMECQZo+>Xh~E}A&xxyMowtU+;ayc_(6`LOZ+_SamE+z=x*G`U$3@&(J_ zU>=U^A2+b`2x{`>Ozgi);EDwyZD+tR09(BDKuDIKm;FSpp2*2_QaK)whP7D5mgZ>W z_9%y}1KP=1prXT(tYi$|UbhZJz&mInzD&6}&9sMO7T-HmHZz}%U?KZ8q&_#zL|vkl;BfwNo0RqowX0$9R8vdfDA z3eTx+(2FSUa0FG|8M?=TX0_Axsj8pfSVQ@4#mjN~Im}bx^Luk)afzP9sCxjI&G}@k zqO0UGS#)*1?BZ_dFbBhM0I>`4*lwl45v)z*NW%hGo~2<(1d`X=+$_ znzHEiT=to>5yqnL?56S0Bq1*Ad}YXktQqv}+B=wS*wpfMX^ZPVp1h(oj+(Cu`1AhI z_15YAqoq@doO#$w-eTK{ybh2n5KlQcf@?c~X^sx}D*KomcJsbfJUh{#cdO{w2>ZnF zxW`(2G4EU~X(XW&U8}eZBGrahzZ}isR)%U;x}S}+{0*>aCrDKJ?Vv34a>;rN6X~Xzsz@$ zAyT)ziulOq(2e0&9y!KsaL3Lbh5q{Ho%Jmo6bosX%R$RebvZ3ni=wO!Uh2Y1g*f4P zeAVrq=k4*wv5IO!hX^CLG0IeEG-ZL0;5#- zwwAq&#nb(scgPIPAeNL0p|elKRf8L4Ni0|m!XHfL;>>kcvKW=HvU(0y*O8E8uVlZJ zBlN_>^S@6)IZ!eJZTzp6ZiXp?c;#kXtwvHsKIY6pftdp9-cseY>3_mwF?e%Awsr8E z3NwSt;S3NW_rn#AUuHoABB9ankc4}AMlNuC2x==15Ib2oIz<|aO<24mLtQhM=uR%P zpc?JWGc7D-9$i0}UHhPuRgBeN6owFw$sXi~UNAcIuwtM{RU97RENW)@rXn;qsWgyJ z0o5jssNY~N@`~XhEAokv_<%NQaG z4h>n6z-?q4cYcTTh-S6@*_Xua!l1UAax{)wFVkq7xhrCauGa=X8kW*Ga^Qw})8m%! zXcUr{5pqnpR!i^2`ogW|RHwT6=8tHkVN{=)lnTzxHNu6{=~&kEH(B#Qqgy#h9jSzI zd`#Kz`w7DcQTBew&PplXR4%$)+LTnCz_v!|%PPRD(k3P6hJwL%umYo7_hN1K3G^*Em8R%H>|b{fOGw$oscwc3#=t*C&C9>D=((Nu5IS;t|te7_Sv;-X`t zoM2#XeF#at$n0XMkG@|Dw3B(D@B9>wx9x(BrVY*UJNkQ*gUU+FE0!{)C_`d_k94XN zRkV%=m3g!sXv{EZ*L$IX%xY6~gNKVvRO4ISk2M;Prdr5t$1Kff{&U=R6az+T?eLw^ zVRTUm5;^=>x$vlM_IIi>e^%K* z;9nV`){U2V*mEZ5FS{xJ%xNmrd-o7*j%(W*Cgb56V!sM})N{Zw>Iaw_`$B_n3?(aV ztH1pT*$5Pe-2>)alFF6aCcd3guKJLjJ(>eG<7<|OY_djz5mR{$eD>~az|spO0d@om z_E{Z60qZ~nZ#+hq*3aRXSqe`3-;{>eBaluLhetUA-4!jg@5;n8>F^+CY{jn!Vm~zn z4ZF)b6|XJE8sCitr`$tYh@aIb_Y3*G;i5f?iZ*|VB)j|}n5#)M&CBY;_a4#glR$$R z*JJEq9@=oCz=4@aJCc@UqIQyDEX~%(<^baygNy8hbfv#(CKj;(^a=ZGJ6z-2^}R+H zmX4$LMl<9ASldliJ#m=DlPIeD=Nuc0mm=&ELJuD}#Ah9Fvy97I*%qp-hY(~cW!Bw3 zAA*l8nkipE#RNIh6Mq7Q7l-JOZ(eW#$SThMR^EJ`gAG-np=atK>I>6+w)Raj7}#M^ zb~=f6B$uZoF)(0wOBRpYP5W%Rl|MD?P|#Ht5Qn8EzUWqu{yfL-@h5l6D|~OU{XC>A z{7D%?7H4jf-bf9E0XgCYPrvc|SiX09A&rxm9+?KV-{UXY?UY@$zf9ItraJb(?O2PF z+U~hsQ+Idfmq6tu2rVfYg5aGP7-AxVcp>0KO6t{g(>b7%frC$Dx(7Ye4L&&e@Z7SL zcxg+JzZ%8~df9=QMF}yY8|?kdU;PhU=T(KAFI?=#HRs!!6ezcS%uJeH!W1T``L_#$ zwrZ>euDJPa!I9^Ho0m^ zQ?kAOJNfn-#k~w#FLpgOolq{s+Y1Ki4H&1EPUYg=)~x-RVcjMhW(R?9F_r3HX|_~~ z1mDmpa^3fNJ1GV`yyaw?-EqjF`!ex?G$yD;3Xd;!8x2q?R1dxXOUAybwMjGo@ z9tm3J?l|sc6{d3=y$PLj}m+IY;DhhKF#d}oi>epR z(-Soyr0WN-xL(~lB}VFT)yA? zN&<{PB?f8qVPW%nV3C+p$Fq1Q#=;%umv}gY$Nm^uHdH&C9_VLBdXLn}GB&z2(LB)< zH&vVlvSjPyJ*T6{{Ejg4Cawk3Q-(qUkBULxbZ1*ZtV&19_ZqI?z?ROyYNdQL@mne7JIG&wnQUH z5NAQ0@l?h*lSE&_eHE_N^U+#m^{|kVQ$#;`mdR+KJo62fWUMkO>Ue<^R$G;agG^yk z>eDZ!5k5o6)s@a{WLv}}#x*NMF(tC%`^1)Fx?n5zTV!d*FRjY7z)K?UZNPDSDLX~* zrvhBl$+nSedkTf>Q`0mmZCA#l^=!GKLW^wmt$x)!GGSeqJlgKjU(Krfy)s|!okS&NQKqJ-k}kGyHUN3$&`-3zx+ry2xNpHw<+oc z=`mqJ3`(;njhCvTaLU65Ixpc8=yR@9x}csU$3ZKflLv47tcqura;a^YdK`TS?;n+9 z!%C?5@Ex$Y;p>FgKAS4Br{jKId`+0CqUK)@lZoZHhpY>bJ%Si3%*#__;jPn`OM+Z+ zxA^<<9seDG$gK$7H0BWuuxuePeV6t6G-k? zOS|iTckjFAV4un)?4xHn}UBpd=sy~dxULPZ2y z=wa>Bx9Fb}*Pz{3#3u8*-4TIRz;{Jmf|~0NvQA%i77!Oe8Zl(v98o#sx8P;6^Ez?&0%3oyw@BL3YJ_PZBOHIMNDC`|S4PdMgiWp~rKhPBQ&V z?`4M3iKJ^cIyq>pr4AxPwVd4hd?joz04+G=ksDUm<+d=R(3=pdV^}=984Z)Sa%q9G zmtV;#jwZ32yBD8ty<{Sz+EoT_7eln&vW~E=AB{cK5Phdg!%{A!gE#E#alDzP-nV>! zx@OF4zjUE8Mi+}4zadqCN_8^Z>I|XDI`E7{Uj6#`FoPKoWI;|fgB~$@lQA?s(tQnw zC|}j8L|q19M+S2kNutMEwMY{7_c+vO%>MnJwayFYw6|IN4L?yhTue8_IZCPey7L*r zSaER5hs6FTa)*jwbDAPZvvgM0U9sb8nY0JuDCWLsaBsJe_*+eU<6xtl&S`XC zHFeCrz&tj*bez#=H?~^>jE%;}NcQ-fbJ}1&%f76~c3Ao!n-=!4PV0cZM@oN>50xVK zZX!V&2jSZgKE}7UOP&0+D;eg{{-D%=z{lY+l2_!(@?(^gMA-@GEDIYoS^+pwJuCpH z(804sQzyiv4R2H$LHrKWB(2;%yvZtKb@ctTdAl1|BFz^0r~AHnU*_PrLrdEHnvD|v zbBw@XcKAw+C85||V!5v%l;)!ZaMx$k14YlKowXf5V5mo({bl*~i;q zdH5vzsYPExQp_%)fmZ0kFq~`YG{_Gr{&b5z!@*%tX|}R0j~O; z4&%^bNPzY^loJ>b1VtFlfIpgNN(~QT=4#qwY9t3TvEzJpAm=VU^Vk>yJrUT%UQM^H zJ9WXC&W%1Gu6zhta_I9&M_K|gLQM_`{_I+e2S+<;YDh5{iw2829 zvMHE2pPXWN4+Dr=ql0U1w}b+kiH=)&SR(LLG;ZIW<^HpLj$kjLNmeJ(C zKI|x=!B;aB-E zub59Qh;^T)#q`h@Ht~F>ncFx40jfRF=+!yS1xXnIcu`zA-N9lMB%IQh^3N|6RLTxC)XlbB^H}NL*?4StL%;fyMG-is!bSaW3K7FN zQ8a$?<4D~+{0bi?*Fl2apSnrsa>~y|v9pOYCAUTJf#;bI#acLpfM;Tna}>VUhcaQO zF)?9%ZA|TcTqv5*Q(ILHd}oqfu3Wf|mS|F%#qk?Y@TxTA#Z3*OtcBXietKSn`o_k} zE>N3zsdYIJHTT>oGPrjfP;@n~mI9jBLUXBe^%Zv>^5lB$zBGQ>OzBrb# zop5_LXdFFUxU;)wgPk05VitK@#Y&Z=NfEMW2rep5=gp~bh@^as@RY{EcZsP7+jiJ| z?OxDp-2cqk7LLTUFf7Z?0Ut>z5EUmjtVM}0OE>A^#o5K-gdRg+HZ;|3EAV^vn>CRz zmX^68bO99=b0dvtT)mgLVKMTEejrJ%TFLBqRW8JxQnp~Dj8-u)Xu}+c67_94TC~A@ zMQ|nBvMb&W;$TzbyXOOASn@oeJ^pbtp&k6Im2hklUxK=xS`FVxfOF$~UH86>*)34G0!C8;);|1u2acpV@G=6EuZ6 zE$rIN@?rx~zda)JW9&R;{E^9(EnvnEhBa@p^HvI(D78oAfmv+I8S8%`AfJ>~q+I)L zS;sv=wNPkxh0>;oMO|Ym&_$aGl;!UqL2U2X|ET2(O5Hz3QJ7=N`BR$o1j6F7(a0^+Er zfI)Oqk&5O~x+8Gm1MBwFiJr5Dx5lT6grd0)n3@)Ut1$4>04>8*XLhvuOuiM*%dmMa z)MP_!tvfYe;+=`9$#(WH*nsO65c(W{fXJqEOUq<(x(8R-&xA&Wd6;tiaYbs@e+an3 z#X`>>ZTFgxFuSS9k)9kzCn_scidj^_7}e_bTUHAPsBF^I zcJC=hM~QlXDkgg=a&iVO+hM0q?PQPpTA?4ySuR#nO$~<=n7B8-S@?IR(;c_lU@ot&eSH9 zN=#}$&@z`w+PSfujxxT49FlTcr5t9uPv0E~IC}ld2va;cNs*-o99H#p?)6#XIh zvh;&9d*euOUWR(XgJf)MGrP>H{tZd()o9t_y}d~+Cg#b2+jY;$$|>4qMXRT;q%x+& zpi?6-O5W`x6hn?&IW-hxb=2T$jjBwMa zNrtk}8(RESa@LdJbQ>j_yjMSjnSOzHv3-+I=SuJYA`o#{$>nx$BAeFs>us;I}Hkz)J+ zM7&<1KFotsJ-1t>+xDugh(`d~&Buyzj&Nn78znLb>6IHPu?#)viVvYLP;0gP*{M9y z?sqpn(f~2mfVFf!+R&^Vp?{+W^k9ikY%iY&$ujr!tqIFa7wF@Ny36ufHiQE&sKB9es5B| zKZ{twua|bvpzbQExtLZO*9;c|Rt{z<$k}GtTH7osoh=TQCod$jU21+oTef56A&1=L z1_#Q<(N`9lqxv3xSn@Mw;|iZ9j;%c8d?Pf~uy7py8vG=hgfr-dvQUc;a@(3q1Jlv1 z$_Tfw|4)WNRP-9dkFU0-aOr7rt&~cNASCN2I8%&^U3DpP*2GMpv=NZITPSbpm}x#&L+to}C1y6CPT}?3$z(pC_J(=& zZA3UM))=AWyH;v|&-JfUTSf&oOF+{SEcR#fM^e6*))FrN!J?U|eZD{{^z_S)G6&zL z@@nDi2vOPjeG8Bf<%TNz;p+)C^!R}ODem~Vh&*zWY#Jw9vFT{=n}(@6^?q;-U0T-$ zy8$L$!I@p=8>6g8KdosF@z_ z;POv5+R_Gkb`AcYSy<3GyyDX;Y(Y=!T{v3BA~+LiT*fJ=P(rAgO2^-V5p1P$%^nnW#$u2oZTHC{FWR7fMth zFahv;FfTT}Be3yyz$jcSQ?9_G#|%8(_(sU4j51Hf8MF7&rd-Y_clzkd98#T9?jd9h z4MecC<)Eax?=e_IeYNpvi_P${I9qD*$a%UkM?VNSn1!8-mFaHP64}{vpC2v<=poq} ziyH|CfQc!=;mo(j_A#;F@!6DN?#=Y?{b|s|ngWEUj$Sl+BX^w3sAzYioL6z^)hkH9Rg`z$=VF9ib)CcU5!$9XX=Y09@!jC?A??#yg&d2NCH4X3!( zHpfFhE{S-u&QL82$>NnyaTf3Q`}H&Np?)TeMoFqAJHMC<1}UKvKPAb-oGk?&r1V{J zl&vGmRZjD(To>l9APtSw#Q&geq7BC7Egv(Z|Tcg-;!GMhCchog3NH}5S8bFd^tOZpf?>gTeyObQb;KV`URE0Oxd)i#_I7dG_#3~^xt+rx)e!t+=B9X-bNex%zx%{fxlEZz<|X~E zCZC_Y-h|0jPhz19riGucaz`v8Dp@RY za@dyoKLB+=iob|ug!PVMAAK^`fqhcf84eM*PK4ue!U!zRe%XIUCIX>5v;|Skf(CfS ziSYdYxtG@+UNfQJEs<-%kp#MTR$Z3?x;L#&U^ zOFz4wDqAEYSZ!P52K_G*M-%3`C9)XgRyg%KNn3M^Io_iWA-W)#5&SFLk^p_lrZ%*R`pFn|xw8^os8SM!! z+R+D}s5h_K*6m*&Poe)B`VZTqJ!@fp=3;*IV2o^YOX4PJS%c`LZ9YLC35rf@+hy+y zV3+#qBgSJb`lhd=wa+;#Sa-&%(AM~64|WmPEQNxz@2{MAmSu3koqNgt>8#jl%q(8v9@&XNH~w95!!PHJjcj($kzH(ch;)iJWqfBLp7W)Sv%>Q zf6Vx{MJi$GSHCh!F-Q?eQrE+n980O+(N-a#UHe;*e$I-@ni!v$T?1>HCD61Tg6(J9 zYr%NTIokSWg2ATxD!(Dw!eMJk4ulDD4qK3#i94De7=vof>l!cwZCI$|Bh05`5$2nH zN6Z)_^43gTZ$NB;aqW+=u6&{^D?Y+QCNr36ZF@xMmqcURMSyUO*U|PD!G6q38@7PF zru{hAlgHpGwLL-{paYd-gq~!UwpBk8lKw%@Ymok}afZzpmo-;#jJ~I2b=pSHgt!~V zcmZP)3$(2_vN$J6(%^7$o2vhkM7>l_8mHYfy-Xff5 zT#Vss5@<%=uIGVma|RpZFs`5d3mqmGblL(A&1&g7dCgSRU zb1B%!wunKL!Wgvib0*D^Y*ikRwrA|Lr~XyR`H!YBCe@O@qemYrTx@>d#)9d04$=*w zy~qHf|K7@LfLjqTyyKu_j=SY#L4Z1D2EStyKvY12ucN7&0X39nd*;W^tc?d)SU<0+ z^GpmN?`QB?J2Q@gZXD}Dx)aV}ACtc}b!}T)+C>n(WWQD{|{MI-g)} z1Vb?HOSV1zX-$#NCWy8w25YlDYXv4PtHqfV+;N@z`4t>D*T$MjYRu8M=xt2i3xbQU zz|dV%Q7L#|gSP@ZpsRhk{$y;9=|k3% zfonAkLVFgN<2eph;3ymuxkw_-cj??Uu?=@&Q!~iw{x;D>en-L()tes=_q;k+K5{*KV&<^;bK0P=B z{nZblfAx{)uEm>(Yu39U#R3+YcPMGQ<_tj~VswHBvbt?Uk{FDIp=tztu1n0V0r)1%k^}Gj|M?F}R)j02T+6H@R z(>2>yXm8hQQgR$ghp^TP?54fgWu(t~|0UiS-|KjUHL!cZe|bE?Iqv+^AKNnzzNWJF zxY<}Hx)6^hafJ1b)?tF!!-pAeLR=J?!8QsDTWepz0~lHLx)B%D=R&!z!D)F@g84Ea zYQ{8}kPHM+s|$12wgxkE?1cf#D@0U>ytzW4sCOmrt>M@bN`0z5JPt|v4Yq^&WGo0j zvqt9g^XOW4)L*C@v0`##Oi6N*AKm|TUEtiNU7D!NUSxbKGrM3r$^90*sW*WvS=JUx zJB*&+>0xNH8b;cawQ+#XVXO+~XN$=zLSV54=Y3{DB#lZYPQ#-QIFa;}0LbpZ08f(I z*9yD^#wD>}GXu(Phk4^XNXwV0H8IKCS!X|6>aJ{1#K7zN= zeu0W(#Kee@Dqe--wf_iim5@Dz7m(Tt=6s1!)>Le(qmuZx*&8ry!wA9(b>qfV4IYAu zbG-H&R?D0uLbic>f-Qlrj)4$}d6%2d34N9cOPuU>`5$c0kh=R_H*9N5m7ONe>so&)d(`#@O;-Ul#LNZI@a28d;Ioc(W z6vm(rr8Mg+tZex3aYn09Q7T+vGAYQ zo$BQHwn<@p+KRM2cIkR8I%xxl#w-00d{k{W`w_fErEQP3up9GHak;b^B@2@2`Pz5^ zbJNdtzZEwjE`S_Vg4jF zx_}I_Jz(oTdXQoy*s=x%pRK;=giW>iQK28U_YdE@ro<5&Gu|Gg%^(Ikq`gPPt=GG( zyS6zZ=lR;&0d^$@&;>$zSy211Ms%Kmn`b7-3i>0XbJyTz#1&r0)~$otj=&oMqtS+i zr6W`N0|_ktFQ=!lI7WLjt47OU=5vb2Q)r*b23vas; zR3 z$B7Yq&2fbB*tX{l%@of2nCKcDdqiyFp1D}d?6J_JxH)mXR;pQ2U^D`rMXnixb_5*G zO@fRB!yHD7RRTN5Mkm4D?EES!1-+>L1_xh5ycH0X@z4htpx7~!ihwA!I%x;m8>qY| z;CWSFg*hr{n?%P)eje#@6zJ{PNRO9EB13-@7xv~e$FHytPh9y zd3K$21gwp*2+oeSX6=aMnXdx<^1E`7q&rL764I8nc`TEZpiPq7D(YIVN{|J$!m{4n zF(^rd+Gn&2*#r8ezlj+>vrg>KJbTDR8+Mknua?A?z2Aa&A+AA~*9sgP1s~wLg6#}J zLz~%_qrx$D20@U9#}SN$0SRcvMCju&$&rHI^c6U$R;v`|uW-J-L1s0n2`Kerp43aw zhx5R;Rj{$=*-6v@Mb}(o`il1)J13PLKu_j$-ZR>?Yqn&L3)?sU<+&>Ok;KU>pRtkW zDkKu~?;$8a7vsfoNB&L_m+IZux?D@&67ND>Fsh0QfinYJA{LE-Xv@N9e>EUsdxS9n z&O!KI4F_~5H(`9g=RKcqeQ0wIso(fW*2H)fw1;$~b0e61v<>}dTlJdM_{?!I z(@`?ifF{UB$%|maRzTuz1*|wF?$h6$u3%j=;%2e{t_A(l7YW8g^yds|%Pn9l+My@? zh>g9!Cc|!*>-+J_U$?-!5w|*<&%Dnd@oqDYo`G|na7$m>BL-r^u`%4mQ%DEFuaSKC z0^$)EfLG>Le`TagN9$IDaR!NtP^EnDp+OOf=h>O!$1PiF(xQPwkezg*gq5En!TuIc`YxTH|4G9 z2|aE`RsYnrVHb|=C6lP}wKul*ApL(?T~GEQIrr7vS3b$cy=AfM>TtJqDUdV1Z* zI!A33$(_NLI9KL&oY%L&-2#FiGO>_6(w<1RSLlboXp0bIGyie?t%TK%bq;;Sd*V2& zDv!u=99`cF?@C;-X917^C(Qzp<7{7X?hzuVvAdxgans-z=bF%-0LBrJAE_09&xu~C zqJq4Iyt5#axagM{1vXOe(w^4)y5c5ra-FvZK1*MaOcI>;oW3At@P$6;`ZYV(wb-7v zBpHatapueQ&A{9WbJXNf#X`f=Y9pYxZQq(S*6{AcU8}kk7!TuPoLNvJI0fUGbrlSi zk^#^AGaC18)1AAK3&^Ia598%}NGRv?%QfXKVKro4?4$13!0R$~dkYu?=<9qUbw}#?3Q5MgkgRmB3&9`SN#T6P3pT+9IsZtojbp6EF@CoXkW2h>KA)>IIsesK^8y4%3kDm8}>bJ(xq_U;+f)`d1@A zew>46ZEHlu@-HSJv`xon_8onnc@&JVUbA3ESJsdOmv-Y{f$lX(P$)HXU2)gFU+9Ct~Q-|3}|VSdnwAJ+Ji3 zIKf{d-&fFvHTHEhpEJfskrsRyaRCdHJiOi_OKL0(+23#2jyW+jI&2C-*a*-AWC_SB z1WUh*yIiBCSmk)ndOs50eEO(_0kAD|ViSBZOm5AdB5#mgO1u7D?B0Eq?@L?%tdKAc zcvXg<+cub2WDn>^5*q2uIzJj8N?d>e%(z_|Ay_!y)9Pq5AX)Xjss*HCj;z95e{q~l ziZDOb%lEWLxRxR_r*1eEy=zsc9f8dITW#m|zySRyFrRC#c2#D;=2SoIOkdlxLt|&f zU);EenIE*mBw<w6zoe>4#2xmE>W6W(K^Yv7hznI-c~R9Q{ydy*{TWz01Kp+6I!MqGhY>-NLoCyR5A zto9ZoAC3Y=|5Yx`(?I&npk7enmtkiMt^#(1`@XyI?(r&d5L{X!c&m5UAYY zl&lKta%~FNQZr=R&myjC;B}3BX)E-`RtCELj|4pZxvsT83FI@u+Fb|9cte{|j3`@Rud)ou%GI$+aGFu6MctQDK+PlWOMdIx%OJgYpC{TWYtBkZ8?>Dn5z zy+R-KaQqb3Q(@cBP9PO`MO=Vk^JWCboIMf4ql-8Lg8GV3^|*O}oW}amZwz^yAjm#C zG&8D1*KvD=v1?^#T;}BxAOl%pjiYPiw*dV|f%sNHl&q)<>mvA>H^F17*o5_;+qdgh z$zn409PhC^()%~tk$m|Kd8pn9iGsGQkyonpX}r@b!koqg{qz0W?u#~zZ{=sKy}PxK z!rc)U;4B<^c61Y*0P*%@+P8q~R-FuKb+ljdr%s$3f&Y(|U?sqCK4M#mo9`8Za5m-< zcr%6Z3HC~+qo6Mr0<(%^ zmwfgU#aM#2I z8QeGkClgQzL1a+X@A~TJy~3CNj60H?8_9t7dbNU#KC3W%Rpc+6`g)d!{j&`L2LWi) zcK~sByjP9(nK|eWA!ZeVO2=3v1d~RN!!hhjwaj%Xte<%ReP~;CRj@_ZHIfB=x{kJM zy3UA!{@Pf(__M`Zh`T3jgfwjpVt*Gn+I z$S4>HJkQtaMpdMbRS~f5W^U$iC&7D&+K(+`nAGS`^`H-WGR|J&G9LXHn`+nRU49&r z!f{?_$pT$+Z9Rdu@%o>{Z8qbz|5@DjUgBAi8Cx5C1eoVg^0)&$3~@8?BuxS`q?nmK z$7i-g^}P?9^LZBB66h7$I0WdZj{<>40qA)^P-mZ!5Yz-SVxap<1t;*pZ2L-E=i$9U znh63z_3_#@cI~fl{mh!FbbPM9z>bo7B#gZW=vDhNE=WK2*ENK%XTWCK?@{nD#05!` z{Q86-#L)y1uT(&!en$K}dPVy8zRl4iFh9Wq0kWEOX#vf)lB&Wum5j9KnwI_<*b)fx z&<}#rcUHkil6Q1R=Mm-x-0?o^-gIW3K3Ci@QV3|T2hj~3C4kk@3eD@Qq|wILu8x!E z(SscZ{ju#Dv`_kq^)e^pG6p&!`JVIk^BP?+Cg+hy!$T1lcx{QXXV)ulMX$okHqPM4 zfHUYgjy_f_!DoVmU{TlFUm>86f!?i?p1#Q^aAqI~tg389j|4#{Kx|J5NCIoNOl@g%{H zKqBV2*b~C_c?Uo*)`ac3sYW|KZ$aN8*u`DLny_Y)H{-KjeBsVy)SiSe)B6$du*4Mv z;;hQ72?0?_ZAM;$0o8x5w^ZQG8zcedVD6Dl2-`>214#(HBSa>6J6adkgh*5p3~SsA z=0-Z{c091Km9(gM*a{u6A8S04=p=m#E$Bz&dKt8GfW2m3w8BBbxm2xEN!u2e=pfjWZ1o3DKj15RK!gWGS#$yY* zmHeofv{Of~MOlk&B;%HS_$r8Y=@MVZA`WAEv zBC1RUjw6UUPo~aS`q3X}dw2P0@FeKlx6*Asb?V<=E`hDC2!M3xH~QXmOGq+|2N5cm z7jn?XDmafo5-KdO1TU3Cv40AEgm$RX z`?mzaD`PUY^FTz#7zO&d;os8x?5AHHZ?uE?O8C;m1q9MoVC3~# z9vdA;)&OZn{U8!IsL%%?*f>t`TY0I{_X-jKI$}$23xw;nlv#SI3uLA&$;p;;@CH8bB_Bg;%QvpC!22+O461=leZ? zjE+ZN-&f%r;5nBa7`x^5GCnx{f@%I>NbaEU>Amh`uEm!jE+oXEI*#fFaJrR_ zY<_wl4j%+zLg(lBM+t!r{wW$Odldu!m%p5n$Tj3;9!XHeE%^~hTEsOE$8VP-5CgJh zrQ8c`Lf=!x{c%Cu)T0Xi!Kbt#N$W4yn*?Uey3=}8dMCRE0+5QPhJIUQs8yV~=tq!i1BGK;qprd6=xe3T z#FnCMCD+lU#@hNJ+j1;-x!|8RO};?|%X z34sMsaJG_UCm@B#aBRZsD}vYn&A{4164kN$j}(?;DV~SmUhhFf2~<%N*C{ApbYq;X zo?RxD{B+alrD{8lM5zLyKvp^A2ppU1Ti<75%*2^Q&13o%srHx&b~@t2d&9#Lw>pR* zf}{|!R-iRWc#OcwEtq8Gl>qbo9`uURDlP5R*(x$2mw{eB63U|npI1J&==zLc5NHW9o98iEhH-7Q9+Ios98;_)JF=@iQ`#~YM-Ox zZUaJijNle|yov#j9a1NWmOGglOk~WN#1+^&{iTonoWdB)?U)MV3Vn<}bKFNH@52!{ zD*%o%_>;Iv2!b!G6z3$tny|f1=;#cNtN;~$_vqF)VF0lf^r^`t#lWK-bE|u;9@k1% z;v`$zIR#zOMZJ;wsHS70?+o;JOxCDPsvpx=?slq-l7#0HeY|$rqh9)v2Sy$Z4@F#V zDC8Yk!TWUhD0_<@Fw0#`f&%g(mCQXL1CB|%47w4h*5@%MMVWK7VT;OmUeAt%3L$u3 zb)AXrw%TWv1+{9Wu(tf)2y$m4vQnn*3GXE9H^_F$G!?Rjcc%!B|YE;2$CbyCh6wX0#<@GRMw-^km6Z+DJ_RN)J>cQKF za4jRDk85U&UfVJ*`VqvekyrJkAIDx1p^=X}@a{;^H!JXE+sjJpao#g7b1_H4cGZrM z=``j~-FDhONoJxb zqb=Zui|uQcHpp^}dYvg*b-Y#5@qu@c`EHrOf?zqOzh<%GCz536OLx7arYeQ3+v^&D%_b_#Q#hxSThC7~E^v<5IrAfulWIyd8D7uJb5 zw}MXIUqG<8{>gTaf``$x2FFq1IwlE`z{v)n+gv3xGq7s$N|nsO0dCPQq*WG<6DZ!T zwH?PC6a6^PSRNY*q^G~SN{kV)6o?9#kIw{b{l0r7jQtYer>}~rjOuqLe|w!(Yt|($ z?AO5fe8vYN?=~Z>)kK#`0A!+athdOcT<<;vanm^j2a+V@y~p=Dk1;`#gx~}@Uj`k3 zSm!Z*J7ss?8;GE91($0;$E@HS`U-V`V2}wg&YnNi<`z=|Vgv1g*e)yFmN1sd=KQ;5 zw$=;5tZ3R%@R>R3&+dj@31a~Ja1K-wceK2gIwPX`7TeaOld+KJ=&P=219ZYJXZyGQ zCgFR=Ll8F|CXzt4Dta7;-~=319gOq`)nm(=$T_nb00R(VeCFYO1|ZkER-+2Z4lxp) z@XNU8%-@!Fs&=R2Bayg%miPeuOmei#pdATwp&vHwm2T;akf_9L4T7p!AFtF0X*2Ac z{=3++w?vU7O1qDQF>|cEel~dsbvYkz-5h7OrhCHO6W0Ji3{c_mirI0U1;PIM%U^qt zCP8~eY0mEf5`?Brg=CVfl8)nBkUU8+kwihzg(sTmbD?X9cJVwq(2uncguPu`510{` zbr{67Iak?5Y7**q_?LcLfPC1VwXx>z(`IWvGgqRnk+t=;6w|=vK%49F@@ttM z^yR#Pb|&dS(2cc@`a3q&3f=2@gf*u9f!on7xZQ`ev%Bs<`)x^*4OqRA3T;~F0(C5t z@o1cT!`*9`#>~Lc$(*mSeP&sLk^rY)h0f(1?;${~d`PX3=)*NB=iFWy_#?f9Z4&(K zkLQ=&j=Dbd;cr{7o`~b2Kgo&tlDvRw%W=-<_SbFg z4A>viT50=3Da*A#<0X=OivCbMUi$*i)ynEa8`=(FBmIWBo6zQnZSNI#Puy7r*dvk& zcns%M7!(!uNvX&RCb1>?N=-7weh3Q_lmnG?#QTbm02*4 zP%AH_%)kW%uICLZUisX&l9$c<@m_&iKLP}j)-k=ttT4$SNibv-(4tmaMB;OkuFh|p z3W$!r+K}~8)e@}Bzfi&ZKF=~<)sbqyXLkc6Zr#mPJKRE^#C;<)WOY_s12oG0OWN6`rf`_$X_84uP0T9B7?041IQ{%B7mF(gcTHGuMt*^D(+f!Va%;{fLR-o zOKz7vz6bfg+-s{O?T7@}&hrYofqHx-5E7wJk{DYjtmq?OaXgX4y?5Nbh8j|*iX>2l znSVxHt6{>iAPgixbb4OHrE`%{P}0|6BA}@@kir{55E>n8kR>W{2|UK)MwW9cv;}*J zJ0lDgEq68Aa0~7XST8n3td1V5z-G>^;1jX6{dK+GJ6I!}caE3y?5Nbh8nU; zW?9}t77UL=3_7G@cHBPI!K3z-AoQsugs9n$@d@I7cQ}rt^N7GnT({};XPx#{ta53S zkucAQ6cPdQa2}YnMnI3;vZEV%Nd|;9j@C7rA4!akWk1)O@LZi!zmiPQk@JW<1aub` zgG?g#im_+z2z#&TF0NHW2q2JE5UJ3?2CmS-^aVJ*5*ML=1#Jz6F3Z*#WRN2@VYqIN zXK>Yi2s&<&7NOmRK8u_ySOarWr6313t*F%!dsQ;hCb!*URB09Ax@Wtk(K&sG=m+M~ zcPSjJ$N7A^3x4`seQ}Std*ap#li3z0=>!6j&xqA(O&}jzR~(#9W_+{qX>0rPkU#C# z#%6B%RWN1}i@AIDZ91yM^RmepDytt`*jE97}ufKA&e6*d*6E z627BO`_=>sY)iXDCE`)({g($r5R>aY zQAuwlWlgn;L-rcH5TO2!_f}Hv%G}trR&qX*Sn6HgQH2z1q-CCwU#dQ-N6wXf)+EPM zcP>Fd-cV$oN5Df6m&&AJ>-C3DsKm8G)b;hAdjD=k*ih( zfu1#xx#0vEsJ2;A2_il-7UwVSSPuj}N85OW<8>V=1}sEkr;+3E9y=i3>{0kmd-ivl z@!Kp}ay;$G*tHFQL3Zsj;(J<|J4SXHenHS^Er-D(JR`N+bvkn7dZAJp#ciu@uH0kz-9#|4N>%%0vRl_Eks*6{B`l zyq9rG2roOaN>1RpihA5cy5+BX!s}JtDe{kX<8-ej3345g85cC)SXTm&nEM+fo(U*$LS1lvm2<~dnM7?gWf{xlOZJfH% zCdK13vNKtYgh@Ns;P~~@`#06RTaI{s?V>roUAOk1?={;U=CS|3l=REG* zNm_JE{i+b~sapkGVG>Q=9p`$M07DP-8YPL`J?xcPpC06G$*dzQEM(?fwMRw&^-qjf z1-l84Ip%Cf1#DXhs*aU#*x5de%V*@Au$x^e1nDn+pZ!R9DB^-zHAdBlV@Q&KD#kHh zGjMC5Byn@=G@2(xtW_I?K%y$mKp=B1o8xfZ&HN+6fg|MYRjYA@b&t;FjX*wUf}8;Y zaHB298i`ddeP~kfbCO$luIk}&eh$g}`q<+$aZVEtb7%Nqm>M3f{38;-03^o%l>;MgNxv0 z>y_sjADp*l6G10PwudCMu^=Im$E<9Cu}vJB%&3wcQdcSv}6W^#$T_$qu@0 zqt8f4;;fgpjALBzI;x`nrdc6I9uW^qT&_`S<*nHoc#dpak$v+oJEU{dvCfyo$e>fe z7HO*x$jrrfBY3kVx5Wgf$oXc81^q={5=iVd;z9y+qmBLc%BW98J%1rhf40GlgJ^>h z>Fkp}0{mft`kXh(=TY&n#1%F3`1s^mYsU{2s!5NzRI_ zqE)gw3+=`q8@LrY*94?LNJ6JemeUO}J}0tY!sGZ10wSq@CTMI@uWcW>(u&tsc~I2r zp^7{=6Yeg8d{PTZmmA_Nsligtb`psx95hGp^_K51*;Fxg}2Iar(GBNTJF) z_vm~IW0+vS0=^V+#aaLopaiTScrxI4UjuL?2??Cr(Y)S5Od^R9@khdg7~G7reg4dE zgiKyYuD2q$-sqf!*%AoT5yNM)N_&9po9FU4x~?T!B`f?E<<<_=L(4Gf9xD%boAcAmKNGk-G1zHN@(l>9esN)Q&`zXK@r)$h*zLp@i9$8EQ zUPG!=O<1XG&Xt(`;)w*x_z*A0#hw);oSJZ{!e6P%CcZ3j1th5sB`^sn#A;-vAy7mH zikp(|Ate3LHTxjUnf~~S`0~UBIC**v)Uth6X6~?x{O(jpX8PDKw|)J(aT!Z7In=5# zBQD!lbnfj3)o2fx(|Na$+hv6{L5$S7N+zO_jg%yH?UQXGVQ$q)u!))wt2o}?bw4t` z1#tyIgL6ktPa%L1$TEdFA_wf;F(Dhw+2uW zvz{Y(&Hl5%8*};5+o9uVYkHQZmp`C@NO=xh_;lLnS43%J z^s47aLZbNn_sjpv^Sj{N5w`;8Q&p%QYosA)Yt~)Wu}Y1|1$pcENAT;fzYe$I2=lsL^vJ&o!kXQUSQ&Zk$>(o>TSh;j>dlQd6Io3F z%p+#Wgn4Mkj^w-J+Y`5ficD~f0w;ww1R*kNzZP5uaIH!Z_PpI(Fxr}5%8x!yC(c6j zHM~kN1EEdD#ogy7=n6)Im4Wx8 zHltvMlmsBJ*$?8)g5T=HtJjTO4??bs>&SsX8lLkV>Buq0Ko`f!_10&;JKCYTJ1}8z z+-jPbU6P<1eb+8oSaVHePn_^1?goxr3#V{LS`g4|O9{lRG6ee?ay!X*eVpZUKi0Og zjFHFoh&Q+Lx?6(e_IV8?58JV>{>z)zH_2No>JfR%Wx#j9lejzJwcHW0f)dDVjj3jS zy~Or*tCG~9_d1e*&bz>n#%oUcKuXB$E64c1eW;53V4VK_Iy#&5_bpc_1%^b!~oJl5Xi;#UOhhpZS&9WTqDexRqyj*X5ZU{CNL6?6x@j?ULg*>x<}tsJX2v+}=8 zp(?wj-jci{|HK$i;%=bcY27PS-5L7!vHjzE@bzDV_|dD@=x*$c>~9sexTCT8Orns` zkPqFsc4ZCy{UU$=?PW`xxb`O7o||Id8Fx)ws#j)dDj&ha@sDuqk(*Lg^4e|-%#);n zL=b;g0TpZk+?wwZuOYW8H(OS|M)LaAxsHPQ>ezb(!mr)LyA?<}#8XYs9J}Z1?Ykvz z8gWfVThd$L2pL|>%U^*!f{QI_yIus>Bo80f0K;``d_7~pN?K%X2eF20iL1O9d00=b zZ%f`Q&aTDX5tj;^)ra>6q1TWYyl(3VdYpWVO1y_GY-QIfBD$~^rG7?u+Q?U=2YLCM zR~(}%9NDsCAI7Ec(SB}yYfz8$uJ5Zb|3xwi!GC*oLzmfBd=KagQME_L4n9Ozd~Ter z?M7&O54bDht~qjs8ej?rX1jXS;d_D!(t=w&;ILQvf0NOcG9$uU#ck=^b{hMIbpR6a z=zJBP7TbIIApseiIH+Ushx)b%So>6Qj?co~5H|_Pdjcsl-xkQ2IHLwOpBayHIFfc5 z0DR_neGJgP=#XRTaH^8+$=b9d=y~0`Hr*pV=vrdPn^fq*&+*WiHNExeD#k`%z;4Fp zvYCf<9^Fokgt7Pzo3X5V97lK7*ynERN!v5`$cE@bVx=E?>pR|aob$|={!&;g+Vwm-*aq8Hnv9gnW7vx|n)t4P zYvz^t1UAik5a{?>_;ljpG!21Zd<=w>*0L<;bQY3?KMJlN>tAnREGjU8AP6jVB9Ld- zIeb4`@7z+=X6;3il=tZ1A5KqOFgKq~cvaFa2mzRt0-=Jy!7*2uO@96 z$ff>Ag#cC;16F%^>4L4i0mnRRNZqS2*9_^~t;7p~(r+`JuvfOrpFv(DYya!S+S=X| zpGsUDRkt2E5)e!{iXbwua0u@SuoT*H949%}mM{-}lr3~!m%dxhg1A-;gUG(Tp9Sd4 z?{ybxwqgCkAkRkvNpJ?R{c>H1c42M_*6&g;uwSmL+LGPJ47JiS5A)&|{Ihyzzn#)P ztYr<%6l_V^R`LG)y1b`a z{Wb|C`f(jR0*+IcB-0+?Y#eNW@;U;w4ifc5(u%ja{O<#mSia}_~M*)4#l80AV)z?e}Yr7 z|C$hVd$PA<;;d|MCQcG@Y%d^O)u)nh2G&a;%?K|U0acNt@nVt z(1M`AP%|?2DiYOAF$+Q<#^609KavUQn*p^Z5SYM#cvj#f?-mFS`dSrdL54oGyA}`@ zT^;LMa4EwLus03c)zyYXRWqnd{F<22IDH&be7mSM`GKPEJkpS`V~=1>M=V1*|=Nc)_~&}*Drgs z`v@oEkE{BgYhJ+^WMZ7^XU3T6#qr!8=l`L@S3U$NYeNSr&?sOH_9NJ{?H+-Et@CqC zLX$k*)nHTGp!b>`Y10Yb*6t2T8TBI(a6E0Npbv8s4svS721z-_y3S~)M*1Qj zfz%bftV$w(^dQ+FvA#jjzb0PR%sGgLfBo+E=*=ztX0mil1^t=(2#JlcT*HM=8uS%% zO%lB|?uxhutl1oBEF6N9`xdHIr!~iTT{Ftm^shm64GdtNJxV6tR8yGOcJ?D665L1{ zRR9G0yeF)vIiEWKh~g7atV3UAfY;pu_D?`8&mrmOYk(d(o5T+=St?!!rc+qEXu1yBnaG=VMuPz;}FPe=C8sQ);~u6 zUP!`%T4DS7XktLrKNN0ID6~^|f-cYTo#(BAO-DCsvSM|=v`YerT6Y)O2i-;9jKi0b zJ_&N+Wt8FFz?8oi{iT0)%lzW_^+KQu*Er#;cV!6N1Mo86-L~B4JMk%lQoU znsl*AuKB$|63MOfUa9N92i!Gr^ZE{_e-;QXf(%KAB4p}xEP-l(Qfd162u_093Z&4- z0A{ygRb%UIRpY$J6|Ntz1#`ZPbxJPP#uAX@7k7W#aGmhdKbM`Pifta8@LE6u^YQ*~lRjiIjzz{fs zHdfiu_(@vM=ldgpHA(=E<9o;>(5s(sI|p>mxkh8v?X%!}Z>u|P9EYUF7&$+Jtswf5 zwcQGtSXuY2E=z8SyC?4H7epr`;^6lrFU09iTatyr#4&=|iU9~HDzF)w3NpHDIjYpO z&2@0zr-yxyx+ijli1A3E+HQc zWE|!ik(j=&hc>JaFb?PHdTlpL2=4)O$hq$kcTZelP#vu>TVO1Lf^koQeVT*!NmM}a z)7HR3r<#rVoNZDv2)ME3U1*3`0|X*f!ZV-5PLiGEo`>yOXQt#j;F2r zcg&G6&^gyn;)CGzx|!pZc<9CWX(tFnJLg1~TqpDObuBh{4=}e$2;J@#4?|pF5EAH> zRM&!FFfiEWlJzSY38)@Gttt|f1ebwmkQs;>Xb4H5Ugxrg(Y8^bL#>P)gNW$Dm`I*~ zeXaK66@k`7GVq$@C3sW6nwVHGx+NH6l(;x1`4WtWKfQ58I}(7uYT~@f$91!nuH)PS zUh=T6d&WZ%_m&_SbrOLBAq3|LB%DX~g%n5ziUI6(=__eTfFL~+yR|=G!!Fm06^xf( zfg#-7ff$T)o~tDgbm2ecvK_kewFl$!UJ~$L{XNHV2ml#?W4JzEY3p%hlqk{5@vvcS z!(|TR)Fh*@9{QwRo!bXH7~gR@*OT5d9&={jw}N>Xm-+7<-Qj%oF}n)-1T)kg+1N^63s0W z!F%G{64yXWM-e1cJYEICLICF-;VO)k6&pZC#vn{ zL~bqF_9oFKF577I_8en7R?frPxgq8`gnl_+f}3R879Aj?mkF$?&(9^tQZ><)`T9c= zF$eu5D!UE7Dq`G(Yh=#vhHpt+D@+C`K|nAiYQ;eSWV8a(CPA>B+fE8WlF_yZf0Qke z#H`3Fi5TSIOptcGRX?bXu6Xd`B;zeV$pErtQX+u}&Zi#wfj-sdxi0lf zjM{w%ypD)(0JD8oqDnx61aetj2oA{xscL_>bev1DZR8<-D?}Ch81THeYGrZ(R4?^U znN{4XOC>JCu>|YjIIoout`i-+j#eP5k2)q;1Ls{QYXpqV-1K9-)D`=Wk|S->CKCNg z+;0E^s8+WGBrwo~!B+zb_`NSuK>*gRW(wyMl7_8e1p%LPWyQ|Gtra&DjK>XlB`Ysj zTQ5-0HA(w{OkBBc-g}%i3D?V9i6ky-rQeh7y@gjD$<{u6&_HlYaCd^cySux)yM{n; zmq2iLcR09P@Zjza!7ccAGBfwiOy2wb2ftdUYjtr=|C}C!`3ghJW1A|1Fl5Sgc8(} z0f9xS$&2beF0g|QR4;VHbwX~66m;MOHO-&d5c%I~P$3KY#0Uo@8kh8$a>a$Vrq~;| zCG~(sc_)lv1DwvSZ-1s9taQQ#26i2!f(9DNHW|puSvT_WNp(Iw<09EkxDHxJSjw$OKkb@w_}IWVBqDtBva#SVqtbER+n-->>-?i)6SNO)-?J*aL-F<%Z*kERgI(F3 zL3t*zkth}8>^pnIBqPP_r z5fj1F)jkwc@4MCBSNT=F-|4CHK1AY04A(p6smrS5q} z7!3^In~(x>q$KE*=(x#H&|YeEF_Y|%Hv+GWRj8wBeqqSvN5aTgDUP3*sFux$7fPG# zavmG3N%Voj=JCRB*|Uz~l^5Ag5@8u>%DI#4)%{r)=);Bx>d!f-jhApW`!VpDtlmTnnQ?$7fAWr?e{^h>95N{Z3hWM*| z&AM{l1e!DGPzxe9)PNYqx*A-<$N;-ovmNGA(Y3}lK}stoqC7n`f%Z|7_$DYDIB^$R zEq^4}FMrvhdgF|WcU~>Inz5i}K*Oi5o%$IWZ5++`moUg=U+%DDp3;nj@bJYd-DOS# zQ<-Xn@uMjk#g$LHZ9_S6b8pQFDJ+RlVjUJ6)pUT9-eUZ*32Vk(q#KCfCc7rKHc_@A zGt|0a$NF~y?(>JNY_L&B)RvN{M^AmY@>}>_prpLCk-O#eAHBg zS+gK^Fx1!!s*V6OA4M~-+N0EP8*Cr0n%}W!wOMw;a##ygovlA6(^uFy)B{zL+nSJeyFcx79;E;H#11;@=2KSBe z6~mAZFsN^d!3Qx==V^WXf!<^@@!~5fX8W;W5E9pcRmS2K=R)q5XM zdFOPJI&}1h5ckjQ5l7Q1*>9e+?yuN<`M+d02vZ-;Im@57k_Aaf>Y#~LWMYtift+7# zP!iHw(rkZDP_paQ#OWL>SXJxs+~NT7ASfjCNQbOliIXdwJfl9u<=e40Zn$CQO6feF z9#)ORURNZ{1a7yqNs@J6y?Gxh@5$@P%@g(*O}J4ZC$T0QoQ%qD|8`8ERjZwbl!m=y zywA2TK%~JxhVY_sgKLIjKDh^*=pLW>L~^`4 z02kA?+H)+EBVJlcWff9ueX8!(@vN8$9WakeESM(|L) zey0`-gBiiS#FwBlSY|+TdYd%gGPYYM330J>8#Vzt$aRNo%wetxx9hf+-T9`@=!wUN zIv(>v5kSF*FTb^h)#M1}cU3d&&Etf$LPr8(RyVVaAC=nZKJs1Ec%a@z^w?fHYd^AH zke-?$)+vcg>v^`dhJcuM zCF{b9Ov}mDr3QvzgDfXl$n;oY_~u+zh zo#pEWmXU-KT?26$#u3A1N4opNyZM6{Lu4N(TQBUWEN0A?KsuxmH5%H={jIT)?HY)pEH@d-j^`+P|J}JlEC#1m6Z-AZriDj|-iG z;GA85XutWa*E-LBW38}cW3!o-V7G3K?ST+ZDOsp}5WZggrfjv62u+M$d((Cuhs|2r zBba(WmnK2pM6ta80DoH;V6jgui7HNK=HV~)84j5&B%Py`G?MV#jM5N1n5JR4S}El9 zX!|hnmXa$*QH~1_JFJ4Je-MR4&a8G@530fr@$#rYA}J=b)BE~yWH3gb@4>bC7Pw;i zHPEw}o&VF-#58?Ir|;v%SiJp-$Cts*m)){?-=};1qXNO>R`aSX{*O1$^S;kdec)$K zF3BMTv6yiCTzqbJHJEG`ngD{!yLgUJYgSl z9L0*PL1s(A@izS44sAJbddu4qDodJVVkLY^JaGAqFAW+c++1y(H0*ML#^^0kczT+Q zmL$VfU@_lMFtVW24&my})5ffm<=EgKV7X>{D{iC=%oE%Ga>zicv&#jE8eO^a$;q@3 zF^e-JnMp=$;~y&>M!j_@hF6i-voN*8rZ#nBswM-aM-&$)<=;Qdz^8!exYn@h9a>oBEA`CgG{iR%85#QqY1xZ?gQ1yyf9s*^(Cd5V zG$63I_U7BhU7}y#%Q%ozU`B%wE*Sz+HJEnQKXmg&Mg)(7maTDEc2qd6Ij2qWx{uV; z8mb=jReXY~Tfev4vqN==;Y}Hu7wv8_j-?M&Vk+(By694eM zE_JWG@P2)sMRfnZJdU%>WSknS^_zIHN$r*>Ke!g}!KBt!PZ1xQ&|!y;LK}_CBFV;& zm=K0yZY_)FSj%^U&duq5aGX=NA z+2q8_W@nvckp-A=LVB!O5lvHN#Bs)cfURk@p9Vb$9*2L9`$T}++%JRb3KzC#bxuoq zJuVj{y4;gnF!R3lyS{W)*X|DCjl{~Gk{Gfb@|5vzk&4gms`8!77sfH$`47w1G?hi$ zmH{GkD>A6f43Qg`3%Goy`i;y<*wA6frtgKou_EUloXt)NYtl`S>I< z)IgJ!xqGd?Hsrz7O`^#V=W0l&YLvlCq~W@ND{&^iZM+{leoqS-e3_`U*qX=(2NM3 zBn=eGOC3b%TV$(=nZSz1@~v?X8{nLeW~lWIQa{Pu06<|5=(L7PNy&RkvpP3{DEX zJHh@lFTpV+sf0v+N>A4+C9C^Yuae`(o1?BOwTJd4VTSO?ty! z)xk2Q$Cc22)57D8+IMqjr)3RW?ste_BRm!}!6#m=^OOz8tA<0zcUlE*-wzTVqOunW zZXr5n@%dabNDyvYZn~`DwF<;oVWjl(at|7p3JTTBT-(hWSwjuAL_W{j^!k|g;zaD~}O6ZeQ!Aje_=-v|&FZn8giM@Me*b?u8uzvQiS4d*9G3!n5&ho55(ls-?pIn?y0 zmq41-2di#A$cTuxHJ0Vvj%KZ=+g1ABe$jdJ8#bCWu1+nUz16KGdEb}m!AI4kJ6?oi zItkI$iqf%JqxHLx*{PcV|MB`m(!h^xz8z{6kqBWam(6H2eIgv+emRz zTbAF4*v+XuE80Synv=P|TBEqDa>lfpcgugc>#%H;N98wOBXqDxlU;MI7gXiM(U{o} z+eHe`+LWg{e&y;dH-pDlp z^InSo$%QU;Q}PMs3&Pffi|evat8Z3|XAIq&#qx<;&AYyDlG~NuSz`Ax+alGqiz|Hd zDV-CRN0OVyu4}!g;&p&BiUaE!yE?aFED9<0wArMz7Rx$U$vu$2+|Z2od9JxFv6iQy`y2ARmfx02lbX!$RtyQ{u0G#NkXKEZIhUrQdFU& zOfKLg1Iw7ibPifVea8ygfee)yx5$bElub&7LG#cgTGTx9-+}^KyM*wx4j_ce2Z5U7 zc)+o67Qc_fJTXd5{K88SIZ)}xpL;X5iDOZAaNdu4Sb(vw1Y*J#$Zbe@_I(YO+4&;r z365f<3h(Y$H0sDBPpyn(3aP%*lYl4z?@5@SUl9zMh-19a4!n&yT&F-vT&p^7#~iQa>=0D z$3k8c35hq^{^f5ujeo8niW*$d1)@81oCSWRHu@H;Flh*WM=?VzTLV?Y*dzy!h9cF5 zPp=AT`XM29flYcB?KawU0A%ZHJ_q+2l-k3)jxJbWI$!)swYHMhTry7vL)RqE;yL1cB+mJM{iRqg?79#X-DSJ7gwRuvJmVuN$&a< z9?15y1KIMBxLnqnz{u8RjzS~YAMug)Eo($I6cNTx4e|t(=z(uT41sOYZQ*(B$avcy z-c7r93+NAO-J@S}MRN>Z2v3W1_-`i%L2Ji4gxPaC5~GLQ(-wNY8MOS799=UTtsPL| z7DuKLTnkreWn^#UJx3Aw5s_KHCuJ5B79#kJU(yP`LyL02oH_4I6P*xGqXEz}j;BFtWW$Mnsw9|?u9=DIjs)f~O;xUEUYiRhHH z1oc~!zjnj8)kHgVkdq=9iaEg|aqgr^9}a+P?&8M(ln%Q}_x#4bFPp!S=|wH>i1&Ky z&POf2jhtzgX?KQLIT%5RSj{$sS7`-v0S1&E5im?qfO&H5r|=teHFYm3ax5H9?l(px0wetK0STBJW+9PnP=q zMpo70&x?<^Y4aoNl_3+e$41&Zv=7ghkNZP+X|?V69nSmfwY`Q9IvpK`>Jha~i+482 zhllfQ*7k!0$2dMcKSM8IaePhE40Bi>W;pIHn$H$F81KTIv(g`Tn&rnn9jz4Y^YLDv z5FmMKzq>h^-74X{K1;2&IlVgBXMPc$-wCwK!n<_wswM)mDi0n)6}2{L?;`PXI%;1> zM4T$vl>IRDz)P+DX6eGo=hBABZP2^XD~-^*z4BysF63eIM57%Xk}oUxqs^y$ZvQR^ zDSp#*@@D=X^byw%OYWiOY7|Y#^7{aMyV7VC^!+@fg|~=3k;ViTmvoe-t7KNY{%7zVXZV_IN~e ztu^K>$pE&I5C*c;#|9}HF+I^XY5vIJ2H%_->4{xh&}Gr8X3&Qp*b=L6Lx-INf$ zWSu-*%l&zFgxUL&&8TeQs^QN|4U+fP*5HqX38rG{?@UpQxP|9qRt|YW>8!&kT73?6 z>JqC*ODjsl9$)|Gh;a6W!r>7f0Lb+M0FeGOA~?Hv+L->ad1=egbx-4Z7kp-3agX#t ze@v%dZp*t~lY+NQGEH)h)K)GdQ*VTDFcokK7$3a9A%TqyV(+o3pEC8x*=Lk;SvN^DA>Qfx@VM^t zk#8(wK6&2{JpbMjH8bj+Xblfu%#}koT4HCCbY^LWa|&6x0zqAfV^8hi?4>ScJQ+=& zOkb`C9>I?jyclYkxe(%gXOv5NCLJ;!TA3$~?Hb}?7X47ujzZqM9Nv)+T4qc;o|TAP zVbXd%B3j0`NMMvGhUCti^lN1SB~~U1)ZV@mPYHcET_}hS0Kg!TZ#N^KN@PuVJtuLG zW3+X2=!a;1(+>jmLPUt<$6g1oE=~QzdBj>94%SfvT!|Pq-It>A95If4!Vwy7)f&Mu zZ1LOJC&kbmq~l2i%G4tdrDEb^pd^u^;OlrxKZc8D`A=M>#|WIUwPxj z_3A{4F2D7(42CNk^}DmvDw&O!P0J7X8a2Zz)YDn;Iint$E;*^6JJD(09n;5@0jXXa zwQQQ7hi06HgbbnBUpZ(N15bb9{yGy9!tkG-5_QM4euO;VK9&3Fv$A@>8q><1@#yGoZVyT`iP}$8uGr98{6Guc zYC~uGW!V3X*A#d1fKoqGgcEiq{*j?&n0}@)g`I8hrFD;$i&dGO8Kkd&|hNG<}*-?$3c!u^ZkPgXy6+QH2p$bLS0y z-SGO@n4{+jziu!!QWaShMMPZeRU zV%HVP8hLkUzqC7>;B z+8Ots;{zGH3cfip;nW+$dY!ZLX3RP%nRLj~hN#>0V%wFSl#6J!zm-&weomv2#qRLZ zy_BIH!G9A^XNo&wjz5V~BuYNgVW$1kB{4N}sUN@VzuQxufF1fG{QLkOxsgcwhTyyI zsEAbF^1M4Nnxg=FKvPR+JM8JM5MAXWw@m=N9!S`0T1o(*ZuInZOPG$#>r40K7V=D6^)0@HaXxL4g z%8+@5plRZjjAH@X?q@ffgUXwaD0MvV$FmgLJyk+l_Y?`v&inQI<7-Cu%91O=<^^is z=|BvnDh!m7j#0%doWme3oEOI=RgYlecT;ipKB|)wGcm$PO2IYD4slLY^rD1+{d`J?UV#oG9{8~=6`U~3nUjla?^)Dod}_lpMEad=M}RW!wOfNYE% zB*7`-+9)aZ7ja$^!<4~R685?su^q_?35~sn+g#l*14U8a==i-27mcmOIS!BbZ?GpO z@8w#&6!BKHY}Ca|NtRnSB-QDyG{}D920zS$7t8TaG;Keo4C2K&eo6DMiBFqaR<{#L z>h?LO$PsswnPSL}%mU++Zz?Av5zg5}PA zE5~jpa@Mvjb}c7hCsHM|120}c%3k1Z=S1Q~LB$T9QLz#Fc|?r?KdHi?O&;ivo@K*o z@NPrwIuo%8#Uf7#a;X@??5!Q-a53S^NO7z`8?{Jw)LT$W4=m<*F$2E23g(%yio^vY zb^f34e<6_l{>XO3+2|hp5eq}3^R4)9SHFg^Pg#<0yr4N zzp{Q+6l_XTB!aS4`Rf&}J?k|)b>tux#=s*8;vM;x>d&J6rRZ0Ag#K?4QwiYSU}6kx zN50+u1cLC11)=|0ivMc*^=PVsd}^v9A^4~A(8?FhXOY8}Q6B=|xoOXSpVzF)QmN-^ z`(2=7{7bQG#Y*|h9dWw!J!buy2M6PEln*jvL=a0+F&=-}vW~MPcn1!9gK#R)I$=1 z;YCUKYvT;^E(NPhq(%u57)^t(lN(xFJ~XCp3DK~&Rp@0rHlpbtDXgRh1^66<+>C!? z(pL-%f+5RiVm~Kfqy8II9yh5Z46bG+(ItfdtEU7J*$L7@%PQh3X*-_Fk}pPO6SPfk zs77cf00eg&T6}x0L900lqQ6CKG;8^5Rj>|LB-%`_1a=%X!+tV#K~wPJV)QUvrL6C< z`<0^CISoR;{$HCM{RuSXS`gh^+C?}5Bpszq+aURO z#Oom7X7p4rP?5;>D_Z#_()Qoce}(^{=3g5AP}4LUrwnqURPx{GP%Q|?UtB;IEfE83 z^=hk9ql0qcJOu4UhB*^_2?(TxI=_P7G(wlsAv|muf}OU?SeWFD3Cp+&zceTRbC*h-&LshQ4%=d?Zj}=8Rs^0d|}3C$2z9n!J`J- z=v%t1L495RjURDb?;BVeo0Cl~q+5^Ys-<;tbocf;I!~lh%9|H;fz--mpXS$~)%<5O zRpy~>WC~Mx-)s5S`P!ddj&!e0ir!ZoRe1GWBUMy(1Q%fw_B^nDnq6Jp?XFzUYa9WL z(N#@k_yy34zo{!b5wNXliUy*Vcm@*CQ^a3c#T>W$-D*Q19V4CCBiN)K>X@NS$asAi zQ|eezm@I>Mm5!R-LsBKLT0bv>?m}n=#;M!bGFKfmz_$B~mTINyBfz~*)3=SjalN{e zL^^rb2AY2@d0HJ?jgRPAvQC>uLcRD5#h1I^TYbREoJiBv@%*_hYO0b-xAx$#^l4VF zw2*vMhtPbD$I6!4y2CTxLN#k3O=Jv8e2$y%G4gJcTK#!>R0!zoX}x^>dCnxd?VK-pl0KgW=)Pw$?eUiJWk@7z*JwFoH?b8^L z2iGZ1dl;3@17X!{32HN9e^jkwNeCtKeQmcv`m{N2?#rM4 z3AnlER%BAWJMi}V2m@BXVHYbD`L!1oAFoeSYLK6+2s5$Zm1)yHTbpjXue&n6Rp#r) z_@)F7q3Ci@?nt#urIyH-0y6c!=NNG^FzZc%(pI>adTw4$a+^$)D|*zjfW3U;*P;zNl=q2I-2nA!nUf(ROv zD=-w(rZd?JnSD`3JFu~c5Rvo%tld__NX@}eg~fdsw#axqPX8MY-y|8NF17&V?I9v0 z`?j{h+R+*(%u|KDZ`NTXWHq~-HL<;+)A%*32B-(lSwkVTT3(Q+2~|0{5%Sgu!8@pu zO*9ZI8iEkF^5rq?Q6uiMEA-O)=u_NHRx~`FGB&p60bZN#BB+DY_Z%6rsV}$-FCB-= zs5(KUk>2R?XAtx&MA!s*>-{a>L{BiYrss(15I5quS53wPA5ZfqJ&pqxsym-J&Qt;~ z$BPKT=;3M|zZ09#*BK5!eaJJ^n$EgMwsc8#InGQXNh`2q>Ku=2bb3PCO(wesj zOxVs!$ZHs@2wH!5R)4;_623wuCYxFB@CGq>_~U2o|L4{^`z6pN71U&O;r?;hW@c~a zqGD)dWBTi`Z6{&fZixZ2|BT`UFL$NUQAh#|gL(=i_6WXUFLdFfa9^;zZn*Mu%2GO<-=u4e{Ntix9;8+|4woBguu> zh~ASnG*3T&teBJU*L9o3fNFx9IG~b839h8%x2dG6vNSAY=@<2BW<R%a3}rAVY?kIHq>hct_fw0Pt-0c?TvIrR8t{lO^)ot&OwBT2%ZA=LdVo zaU2=A*eWifxHUW6)4i5Pjo7rdAHmBNiaQTR0n+fh(nj5_DAVt15HZzvO2+b6WscAM zv^VDzVkct?RIurr$QD(g*);s#wCcC=o$f`rY`dCED^oVnXv~w3jU&vFgatv3rDP*N z%?z{egLIRJO1Z99WM*qmk|C5rZssYF<~Zw&mzyo=_(E3RFbBqkF|-ld`WOEJB{R zh;bYv%B-_DUdNi1!{u}e;ncQp>j_J7@A{;CEIc&?tHJ!)PAD6P!G!-gAkSc(P*o9_t~rytD&CA!&I zd_VbSmuI}C7(Klm?uOwXYE)+YksN-1w}G;3Y(eJB|H5-9wyvbZ65HuTfE`(aBsO>1 z+s-Tae2CQ@8p3CgF%hxhM=R}oIpdJ>lV2{XL5c83{%FXAxfo!S*#J&eP=Tj+voatSGcNH1j9$ZzY^m-LHqvWDmk}RN?vTb%XE}!=zJ6z+$ClFY>4ov6 zzgTolt)j?|F48W04ZZq`PDO^0!u$~70X<{9l|@n6397pPJM(@uR-)MiwX98$-2xc^ z^LOJjba41Jh!W;P{=c>ds=0J!S^H%M_;#cPUj}dc9dDLnaMAu)qXR>2h(6niR2hmM z6Uo?x?8PbqQ})c-6P7G|g2N(;xUI&j46114TsJpeISN(H&#IM^Zk==l#}n%8rYMR_ zV%X+}x|xKDja$#$9SGyX0thvPusHL(V3Spt5l4WGNHi(!;d(k3R4Vw7drN9GP0mLJ z5D_+Ns?yL^FhIY(;TZqj>?W?Q*KjsDGT)05KF=w%w%~D`Fv5-k{;_<=1xL2pHaAMy z3U1_x#WzxsMC-%6B8fTzVE^6>tr;5x9&F+u2(ST9Pm`PuZk$c?Nvb}h;L+wdwa zsQ0=N{laI>65#2(Wa+0h5I3kSnIip~_?PrcaRFG95XAO^rvcX?l$G(_j9c7f2sXt~ zxe4Up=AE!UNK8@gG_Fh@eX}U&Eeayf@z6Xp5ob(7Rx2$$!`TJ>PLw$Q_&5ZNp;~!F zCyb|tAeKkFgf4EEsUg8NK8$|Lv3w{J?JB~np<8O&2`{`FGk0^{6_#(Yoz8wi0dyUT zVt{X-xPEJEJ-$6}8);oLzctT~HWf=Qg8dP1h+6b@6gY$^7u;cq^N%e;42OV+`O_TE z`KJsC?i-di4Mtr#>W2q1F6VmKZDrKIy9F3HEokxiANuW}v3T59P{&w*0|21?Yt;Gm zpe+nqX_(r%=&N`-m^$nJ>M07+{}zD%1-tPKlvNHitb^7l{|IBCzNYVFYU4~#_bdJ# z2h0+`qA)JFoYD z!T(hLPa*%aAYLfgo{ON;tAQ*SzwDWR2O{jhB$?UUyV%*g{Atek{SR>uEmvIv;T1sm zU*xU712x`XcvCx*|6jOW>62R#2o4LXJy3Q0BQO*G1-CFYG%W}fpIo@#;lJCX|AyaZ{00A)b^3Sw@7Alo@o>5S zQU70C*6-low{(AlnXCQ+|F*&VUBd4x{J$lb)%+#lU+ewf@&8^|{f!0y80rCl|Fg>a l9sci`@t^Pt;6LDh&65h!P@w+ytDU0&_#pv+zUE&~{~zXhnO6V+ literal 0 HcmV?d00001 diff --git a/BMA.EHR.Retirement.Service/Templates/retire-emp-1.docx b/BMA.EHR.Retirement.Service/Templates/retire-emp-1.docx new file mode 100644 index 0000000000000000000000000000000000000000..28e5c9eae0ed6730ff2928e56237dd99b920c0bb GIT binary patch literal 47953 zcmeFXWl&u~l<#|RcL^FGxVyVMgy8P32Pe3@yL-^!?(T3xaCdiixMbd&Tk~$s$MY%Iz(3pn-||0r1R9dZt@@cz#UFy-1E-r7W!k8U2Sy8GP4O+B z!6Mk3#DBy5-MxDt91f=@4;RI-NBld{`5?tIVNo})+5kzE#7>P*0fFs5TB-6&YH8t= z-p52q$grri(m%8uA{F=2KB91ksvqe> zJGd$LUGIP2#LI1<@MtntNxMsr#)*;P-}V1O{6Co7|I+o! zgx*hx5rr-S-vXyQm6m&P3KbZQ|E%CF!N6)u%VDf6S}nZ4@hvWb>mC_Oj{luW{PWW( zOTuL{N%s;jMHMNc6K?)VuV4G6!yS+u+)>QzKJT!Nn7My%_BKW;O+6MEsfiT3|1nl&b?FU$d|E`oNnB z)~2%3$Hn%o*Xa(Z94EH0L|akc(c#%JF_LG+8&$&NU3N?#|19h-84M4mgUg2tXMFK8 z(?QpIKZw}J738=e(QMG)vd7QT6~6gz`Dm^CNmzvp0JLTU0BE0s;%4t?!f0Y|>|*=5 z;{99dPIYYUSLU(a1q?p?H@lqsjUpI->h5W!?9OYf;D!ep3Hb+5RMT;Iosg5S(8JSd zgL&E$g;Y{?0KgIqG@1>2GO99f?H`q0r}iSLWYx&XB}(^s{GC1AFPuR92iyl=mA-9w zoy^t-ljQsFwaP24(}aTqkw6WrJHOl?94^%4F$2E4$*EQjHaVXL z-HIKe=raG3@^c<)`=k(NxlC`G2twg9 zu=MM$wQO^he@u42r($duFwrZ68j*4A zZ_mXSij|hXq>sdK7awGN_x<8T7@LjR$bM8T?wb|&K$=05xychE^Qe^st+}7M!c^${ zRV38$$}IjP2HVNtpL;9^-OoN&B#r&XAe0GsiBwE}$O%KcpvjhrV(OPew0C4GOd1uMu$US&5BC1y+c>yEm1v&z)N>~HA5gR+5k-s2bpecqd$yA$-5 zfXUzh$J9Sshq6xSK>8GPR1;iW$s8vbTuktamlf_Qlh49j50dMwkcl-y>5Oh z9VLn9+i(p(89%3ubP9gZE$U%}@UR6W!ZkG_IE^<-!dGDvK`Dl+@NadIch& zmneZ6^o6WLZPUt-O)oiuF+5cK0jC7_|A%AgA=&H?^^0LyL7B_bFCS62#0lE#Z+d9B zoPVl8d*+RmuF20KH}w{gQ~*KFfoiYJ{S~%PvrE=LkcNDNL1U3_D+TlgfcCSH$Gg_o zRoihZM_FBx`7LdU5xeyn-NFl{4)5wZ?UUJZN30|Q4)Bglrl3YRchIS1$zgD8pOq#i zA_^aYMs8m@R_hO%x1`R902oNP%x|t&ctQzZ^RIrdi5}uc1XPE<{W?6g;5Eh9Gw~?- z+MT2rPIXJSLcyerLL@2N=?ToV>_ZNYNtPuXjK;UDLZm4hY0B6P@=udi@1nCje0{P*(OI@qTue_*~$IH5h)k?#} zI)G6(nzL|3XhitX?*Z>G;{>fh{!GpEtkhhG4-ETjP&Sq^(9 zP?JtL=l4E)WBV6^-M-ARcsY_BGoM9CK^@x($0jp-Bg|T%n4{`q@;I|(B*be|ry&Tb z^;f^<)ZN~*qjzhUj(!#6Lh8x*op<(K@J;61pNul7-ilc75+c)L=Z14T?G=pA$GP*< zxj8MYy9_T>nLblT9a$}@4dd!WgQ&& z3irN3R6Xjmbs4-1ns0FHc+xAVHF8h{yhdOcg0%{~BRL{lJ}+3XnRRqXX?o^FLTWrW z%hunciME&&<`ftHf&hdH%IpDkgm>T|Vuj!P3@++%9#ME9O}mVvOUg_pmQf0>h_G9W z^1QS$+4mevI561dG}7`=nm06TZu_bXk48XgVKz_{@7ZXVa4QHDL2tD^1&1t#Rw8d{ zl?T+o9jc!b(EO-=FWNQLHuMu@XEDDT=Du;i)`PH*DlCfIe&pGc^quM3Q3>Bz6Z5Rm zC3w9*C3R_hY=s{Mv=+@STj^GWDMLB2KaFSg-7A!0XggAjVNJ}KYxHJ?Ee1s*?1Lu{ z-N+ZElowb1+E%uAqDauTDlQmnSEl%DRX2Y;#X5biS5|0xZ$fe0#MVf_y3b8fr-Na_ z9|=w3^MdrQWF}x6k_#DIPTy|cy z4zkJGdup`rd~esCvDxZf&rHn}A2Tl2i(<&bG5E~S0>rThpJG zX9!Kat$H%Fbto@1v>N{Yi*VVi=|-j66g?%U@RVAL!Z{ zv~d-S3aEKU#Ye655qZtn;t02YVc`FKga{VD5SuEcU`hZ{%bNh1yQhJy8&h6c-VKVe zS`ds^AvcfimzM1=>*@E~Y^P23Ba%numz$S6Hww>D$^3T(cKFoA0}2&&fj0_{S(hw7 z-N$lElYr8FvIrO)8O#r8!zaYqGw7&c1^gu1gJJ;NIZ;8|6 zo(J?kS>5$JZQvc`o~U_3uss#k`If(7donVee|*0zA`^%}Q#2S`(eBU*9`IupWoVtR zfKxW66&4@$fxv%3u6zs)Wd{z%Mh*hGyn!mpn*u*nT81c?86QKJTBZvpd48ts%YABtgyu59d^;{hof)scv<0(}H46Hc1%7P4WSt3KKw3oKbs|F0T>N0Isf;xGM2yiK z5%AHc4P}5ifLJfi8cF68SF{~IF($(t4zVu#92GcjUz8a6f))-7v6iX)tvx(|dF}UJ z+;^ENSL@86#G{yMq$RHB-syHWpE_CNuVShrq0^)n2@|b^jsrtm;tu?!v9M2dUT8i1 z(i>cEcEDz>{t=9Smt2oCjam=#y zJ9d3&Iz`9|p`%aSsz0(ZCm%mTHTX+glcfcA>b(EL4?m*_u0?SBKtDR=5Hm6pP_Df! zvHJIerD%9m7WQ4A!&9@Ruprd7NyLg!&y?Fxv;AK7z(@?+w@ss^?m+w8cDXMk<2oQ+ z&-kBMOp*4%Jq>c5i{yeRxJeQsx)n{avch;Pm)b49*qIC19s%u=IV0NX+K-MH}dhor?PB7S7QJ zO1F!=2OwO~9^EC#0ETKU^e%L=_AuxXsMO9HZ)ViVn zoY4w3E`TkDE~*Spt%LW7s6g~g;vY_aE8pv>UTlVu*3Wq(L)LmtH)k9KMs8?(+!MaM z$P=`>>oT6yzi8AdC8ink8q?yWjlmEVntH=z&e|p@)6)_tY!8 zQN!{3JV^Jifmkdp+xLD5=*oC0GUCj+k$#t%o}0j3G8`Cb)YwPqdLu%$XPNe7XOVrU zQPGVgvuL>dJG`DgU9&_mn9vJ-5oHD;`K3-X;=;ME@~K|DWPdXd{<_>1Wz|2n_%b0ARuXBf0&56rlf_;{K-^1okQEezyLfT^f>k z75kYmgWbs9MR&8gsK0+LpMi?wsS!e}$KT)+vyr@~Z}|9Hp{o2vE3!B0VNu{AzcgO3&aqt<3)J|d4a5;kAqW*g*`Osv(VtKg}6(ZpxHl|ZBpD{&R`XD2L` z8I7K}?fNXov7~6~1ha2auxZyN(SD4(H!jvlB#-w**krfU?AMyUqy#iIBy$WLm<(044+1awqUh{5n%@pnU$WhZpsB@xgkMB$q z!6!(-(>7^=Sgaoi*wx)#-*a%lTO;BA0>1d5t$0uce0|N0u>Wt}v}2)2bmdc@R6_&+ z&_7%Jhnt$&+dJFYJDWQF6JaJ&R_qFyP(v=%m)(R`OZMaT{W4UJE$7-*+g|`x%wSkV zCYf;^Z_9rE^h@xEll$LrRz42WYt1Jn&%$@7&=As@+nnBLK`u<+HKls3f3NSIKs-;386X}%H!_ayX z$V73H^JloU=nTpRp<5uSf6{g@i`HYOTC_2cLmQ%D7h!ob@dDi}cnE=-{a8OZm=<8*p*c*vP zFR#`;`?8aIo$SpWoBte{S2yfvHSSG@NEI!&@bry83)qIEZM=fMq4LTjJwfQ+cGN)C$A&eV zuS6gHI6Aj`_3pQNyYDFfEeg~QD5T4uD184!;eX4KrgkR(g@j4ROeWOfi|iv_rd2{P zjsZg>^W~zm-FfL{nD0SM8F{hsLA4uQ=o?6O&?ChHI3!0OGv_kx+xO>UP6uJd-?Gkh zKE|8~Cr}k(c~4h2y1S~IstVam;3l~Hc^{&ZJ)1vXkD8*GWpNfXxS@Mhbl@ADSbvKn z`ibEF`mU=KK&H+&ryi!fWW^7_3ojrYXDVJb+86*jbBDbT-IT|;ZLPV|DaBWnvqf-a9zrJ zxCOs!$b*xj;>&DpDQwPX)sp64!;M`Y&hS}^+Vga75SP*iGR<1t z!ksWAr&J`w)CmuNWZ+I{ja+pC2rSp9XoCfr{}Tj%<$oaTH||}0g3xEDC03GV^a(;! z%6F%Lm*6kKW~e&8Mpu?#y|Abxd{=cAxGN!G-*eYeL@9o$j+eO&I!X~~_!=R3Sx*4s zFzZo!+Zy`?D!qDsge%l}w7ACl&50nfmbMj@g&z%w{6Xi0F!KIX+}eJcrMv6>=|gt^ z708bn8cTkRGKw%gGcs%u>pjYFHavrhVAbvM?zmM`SjWYk-q}&Nj;NLFjUfar=tvpOaUg56wekg>+U0vaPC;6BqN356sk;vQ4bqSGi?**0@R2$w5;V_>mP9f_ zCVcEphUK-bK)#Cc@M!OvBfT8>6COG?x88Sb=j)7Vp6@85KSk~~9)FJ74y035mNJwd zG?QF1h!MX!IuS|eT@p!b7BF6j71~$4{r91ZUHPkP$tMVK2mk=;f367@riLb_j?Dj7 zgrbx+yA@*0;S0($Lb{ETMTLxCYKd0mb7gMI&HMl;0&S>30Oe7R4~(z9F1cW1X4 z)^h&uUnm4<>mFFGRiolaa4{Bj!m}(aO{}WJFr|K3D&X+Q$}3?83C}5Op_?Jn-T6gg zaQFn0rdqO>zxaTru`ypT4&k_{qYL-+;=RNqxth6;1=_ z73{#9Z5My>ykl4+d2PN(gW%sCA#6LFlA39B>bfqdvX)Hgjd3%c$XsyiU2bYy11c~2ma@mjhyNg$ zQn*~ceh4>Y+F~OtP?lrf0YM3E4&xCd}kQ_t|F!Q*z#%ELeYCvpxyKTg}~$!f(Jy}(%pao z#bFVv*S_9wQu-(?e<~Du_(|e)m0gyK@!YOvoPV72G~DV)-=a8tC^OZBW6eLpIa0nR zkfAh7Z>twx9yQqA{(9=QKy;IP?sy2?WJW9uEyb77h>*2|Va0mObd6R@>p|Ys&C-I_ zPv|*vKlj3GT08h_3!AF;JQD6)_y%=DiFk$A3Y?Y8gFatAhWoPA|KA5X^1brkO8-x% z{uk){Z>L_d%la4Q(B2WEK4?OCjzF7YlxY@^=_WU!Y~s}CEiTqgeto_rgn-sshF$FJ z(aZd}apz-we7~AVcPo-PC|G>j`gX~$g&My*Xn64PdgWDr$8YNpZ$JUxcEhOh5{r^6 zxR$#;ONBBU9HW?U`Z|GwOIjo?!997kDjH73ydW%k3(2-)J0ro9GG&RR&l9p~MzyV^-8MvGp9FAp;7$ z0{Szs`76lLoGuv=Qncbxv=`TqEr}O|K?L0r*Wfp*LKTDbstPku`;&1G$&Z}lNqfrC z3Cyeph?Di@f|U+jRd#dSgk=jG82`OVjzIkM^#>NPu-o!({$618PW|cZd6!n~)&l z8QSS7iPF_#lyf8YcC(H9H6` zqi`%A|5dn!mAUtEmg@?oM`t|VpDV8hY$3d6*KId}o7{+nlrKj74JFhJ=|__)xAtQk zT1Mc!er`Z3Dlcj93ggy0%fq(n;x$x;=*xJlGKm+2{aBoB_PU+YbRneGra26TI+X7J zvV{u&IVt}uB!IiJ1Y!;g0N~$=^;z>D;k&J=iKQWk#pkeTsj=wyM@U5`;Bq+Z3xHfGHXY9_}yYPK^aoi85 z%vtQ~ep~5&+bPNQc^-}xe7_?4xVrcNV)-ItJotX(e#}s+zF&ssrm`yXT@8Gd(%H-X zcm;)ydWN;6Q@=kme*E1?WX^Hl4u5+*YwmssSde}JeXQz#Ea<;GhIO=uzX*1}xeBW^ zjeGfQhf~Jve7tRZtUN))(gno1GrBUz$_a%SfM33BD3Vdgej30S1}oV_g@l%Lt7#7# zM=G6wwoCW${`|5EVxh_R{YLgGu|s8x-UeMg;r%)Hqm{rCgW=_x?|uM7ksxq&n+5ar z{bl3jjnw4ir(1H1-okt*=WrZ<`qpPhar*5o91my?I_Ry^pSF^3Q{Tz+7&CzBw*5yO^ z;PpCAV{C9*K>AE3CwlhK1);8*=%HB zDQ^?KPlt0S>;?O*3_l?OnW@f|kH7tjAbEMeEfKs!X35r`@$l5zp1R-oXOyF|<%9Q} z_#k?-F_a}_AUO?u=s;cYK<@JO>%-@$gz$}L+xpxcn;uylyhOCDPmh7UpLD-hL^5HR zxPBU-j1k_ZNN{rmegUqz)$3w=DQs7n22kd>8{w~emFqXhgz-KLJYWGm7+R4v7<00d zn*JFDPDpvU48D~JzR%EZ$K+%eO&)UGV<_^~BDpo)4Jo5PY<#@UnyC=$qosxjH3@934tEWylf(7TYui(`aF>y{R%LBrQ2BYxO z9EJ2A>9Cc52QdRgbQprAr2gCM)`8&r4vL@;U2aYb<>9!wQK#Z&=Cc75-?+V%R}OgD zLThzcMgPrnGMMDj3chBc)$70knD61GkMH&qZB=^!ik`}5^DcU_8j)H~w&#fr%%nf? z^-$PiMWFuKn}n6U?ym5ZQULqW#Cc6-dcs=o?I49a@*q8150(-*vqezt)c% z^YPIA*5&oHwM(3se#BG8eA|(@9+1l$PdYS;Z8L~&h6?j4^_U!b^RZPjGufzfE9cMz z{Y3M)$5?VP?^GgYAgUGP+jysC&=iyw$8=yU=;P(yvHj(sdPFknD*dcZW4XKAv1e0p z81J=}E1)yQeWXyy@%ll+jvQ7F9ky*#4kB65chnrQm)17n@bG1N$`hpqnwHj=lFoZz znfoF`uzq^k0e zRfIMc(QA+A_A`O)8P1|qTMVE6y5c}!v|B|d%?i#e+1-4qaSPU_;;q?V-TmI?WyKpF zoOj8xwd`pqlJ5JwL!@U4wj^H&nSCOn6x1X|XwIk?_Fz00XQs81MJtby)qAkIj(`|@ zCH1`$t~VZ*=K};`Pf824_PbiT86geilK$&rIhrb1ui_RMkjcC5C0<#V{wFLJjVmX3 zTMMVTFf*tUMh`A>KTP)cWhQ)3FeDll9B(hrz!`=cPG!X&Y$pp#t4K|*8G~zdxcl!V zs-yEvc&%pUnFfXehqf=&u3g~CD%$EV5`D18WOw32PbjTu zR|z1uEV6YR{(#lDbJvX zff&`aGmc>iKFz?JD4X-ih9K={vQ}#*kK5mDFdd?_Xda9SLU2`ZN9rVSu_hEqss|3! z@DZ78ap}qv4%Ys!s&O34Z3=>v9TfU^?WaNERw_qAltR3UI=Bb$MWDW-vzSu~lFpsz z@3Hkxo6|=7Q!khadO5f1$d74RLfo{cWf2ViXsTacm0M4sRIsmS@2&Ibz2EQjKkh`1 z*r=FDCu!*09)eRZGP`LSqVJaiY$YG4yS{~e*>=W6(S&3vi#BSuS6pd*#ZVv>qDd_9 zmPmCZi`H@{GmEwjAJ-4u^;&2ovfLEf;9z4CQd6$~xklmMTnE1Gkfr|2bB^7Cq(@7x z8MZSvf+{3RD2)?~IG^si<;$}XVB6hqCswn*guRfdO3mJ7qa4lweikMw9Tv6C{6SXX z$0!v5oR#En+jxnGKBsf~zMJAlpQcE@cMrzou(qvcJQ1cQ{HwrQHRnA>^#EOMpMU6$ zrgWu!wagEX30H2!&4120sZybRQu&m0)tl(-(F~{_U%NbPoi!Q+pUS1@y?1B*F0nx9 zZ;LBym(@A!zYc`+`aB?5jPpflC{NH!@KdIlzVVW{dWX*1jMVyc z8`Www3s)CJ(bX5{0z?oMU2ek%V4?>EHl|xvl(fma)02+Cvm{-~vs= zO?x`#gK-drGGz+L=)gyN<4+*I#KGI=n-yFDvP!a*D_hQUFd+)mbWH4pe4tv+)|AD9 zfSu+Qr&B0L(s}ZtgM<3FMDf@?l+Pwx`JfT|g6<0cI1Ck$Mb`$@=Q(EgKe-^!u)W2O z^Wg5VCj~Gmthp&_1C?+z@KH}#>W#8vnZD_TG*$v?L<;Bu_gSJ_kZp~hWELn>6?5=* zyj5Oh_uRI*rzi7!fWi`(hPWhd&`t~#0X}X#KX5W7^=i8L9MDC>!mT#li<;>Q8Nlt}*7)nM-nO(TzJ0Yd>!XOU3`J065>Epc-5={K zcbj2W2aw+aCnp9q8b#Vbg4YIq#=fa}tZ+YL^Ai@bl@c&N3`%Yx3c5k5(t^IR-O!f# zJb0|j^4qWo$h!#!&tMi0WR!m-r7ccDzzbC_j-7n_Tyu}ZBg$sQQO6Jbgbfktr1DGa zT$Ln{RG;5YzTHMiAC1O~ZEtNCgfqeRf?j$f+Np(OrAUtz<3MI;kMV}-LBLx~mFf?Q zEyW_fH&l{bw|%ZIlA%s7X{qJaIdF8KK>D7#7;2lI^~Wx;rqb^|Hb>dsV0Iak;$3gm zK2V-wTZ7vc-58**96t$qSI=GkmC%n-gEoM^)-VB=TOCBv@GwN&XYAX$S12e@Ay%Ed zMf=UznK6ppLuC4xch1asry+UkYjfS#P}3FCKaa|9 z8DMu$&@gkua;x}lGPlu};L^nhghOlQDPcc~K2(|2s=?U&u`oKSjsoYS4l z_x)H&fa0w}`x<>%*s>l_B<$GvERu<~aEJaq9tQ4lAV!J_+19!j^4WpfJ$0&ri7HJf zPbkGz39Aw1LN^U;jQxuVj2*j#@l;;#*YK8ebcq$Edb)%GAWWdjHahX$RGk6x&@J-_ z8G~?q7y73@nISY`r7LZf7fmzTDUz+Zpl8udr}bR&scaM@GM zX_@PV5z{;{P<%sU7D*->#d7vzs6CAxhZO794)@CgRlN6ok2PQyc1aMwRbkzH&xO|p ze-r`iERZ#x%n)k||9hB^?6qn>N}H4p210U*&^M0?Nj0Qr?xE6*Ra!YM&v4n*HieNu z6R4Di^h*giPB7{E^1mC|<}rzJEwWK`iHtbjvE}H_m~sQ=S?cjitCFp-Vu*ViFf89I zPT~B>0M}Gf?ZlcM{2{vJ6ixEmRq-gjTQ10uf?NHoKh%$m85gFG_n2QzS(soIc}r3= z^$IsT)J=>s`B9Tb?p15B1p3vmSt1EpXz3owFq)Y=)xvH!YPQko(&2cPAE}0bv|z;6 zMSbBqbQoYm63j^x-hZ= zH&?_p{(fS|ZwDZFD@Zksegp-q{|j8Zr3q}hayBEU(cVB?`YYrOM52nhNvte1*nSO* zqpNE2PRe73bpZSkkXBF%L@@N^;E4g8rTMy^noZ}a%t$DTJW_I-aP}Y zrAJ|if`Vj77%EV9Wtv?`t($&tIu^Yz_FR=CU8y=w0^29aS-aTh-^if zC_oop{t7P0n1(Cjo=xx+;IwEW1~mW>kbZLU@PIk!{plS|=W3;~!;iQm2I#nK>Z6WpKULDy_3?-zex4}L-%<_S zL8BH-;d+{HX~*t)VTaSPU`HvG@8NJgok}XEfp^CkB?%N39BBlkak~Dz-pa#R==IpB z7fb)%cbQ>uBIeSAN*q4kS`U_?R7vc0z7o3Uj}jF8$PTUTd|Q}N=!FN-IUW+5Hi$rXC`IMG7U z;8QtBUOVo&U%pTkqm99i)0oOjrZkmpc?Q>P6>vr{4YO^Li4LpmcmD+j9m^kcz;tQE2i*r40qL)He3E2L)B^sdt zc{rGsMc*I&-q*t~@>Uz)G}I)m zbsF7YLmqR_JC6w~5of^Z%5;m1w$bz$$sB)kP8q~)(Vz9$0ZsjL)7%c)aUHPtNb2YQ zsZ!+LO(tk!!6^^ppeeUsYUQt8Nz#W51g822JdTVLz9LRl9wVhBN=-s$nOmz+^1=w| zU;tS851!4NyTGQbxuVi=<9Fz$D5dXVjaO-FqVK28I$YTjDYl5e-S^M?&VZiaIX*m20OeSXpR<&X9#yGUPca`FuD?{J#rvK&ay z(`Fy(%sbg|g<33M`8l0_*T%7~Jk8~+5GL;)o6V&lF%&~uN3sY{G`kT0h68au9db3g z|7C~a;hWUAR$WoWx1hW)Z`bK7p5V`*Q`KM>Lj(gI(t%{q@C-$7>#2Ex)0c>|2EhXw zn3@kNv_tb@UdrbXR-k_%1YR@^&RCuaIV_l|i%GAEfpoaBE$g#AF?;!$`^Iqi6Rvga z)pYy1V>gV++}H#B%BPSejXIBTq`@1*-)s-($E-nnaI}-Af)ImN2`fx8iW9q8$iFj9 znF#$Rm4c4-%`t}S&>z1oI;i$`i{HNm|0pxyT>9($$w!{y9@~3OVjkhanvxOkvJ}g* zm2u1)Xybi87GVm64)*59i+>0BGO$MmemDUdc2I-zpM{}+?Xl#^6n9LG`Tg|*%3p=H z-hWVsjt?U9z7NVn!O(EYJgHLv|F!g+i$d-3UK38Q-J;C!3daQ{>}g6?$|<75%9>f%en1uny|N<`?g2`gT@;zfvzGYE6z)K~e?)RtQ^Kd#D5n0W!)`(78Mh z#XDZd{T?58mo?wE)&G}MEUdy;^63Z}vFEuw0{>deOsDgNl(^O4D-Vzk5u9`!+1A`6 zNeZz+!SGw1z%rnD6c#A)dawJVlJkXxOu?Rlyv2rX9wXf=`wP>T;a>yELU630Fd@I2 zLikWtB(-85Eb*I%UtuG}S_shlpqq3y$NXF*TkALzVjDPbSdRG+jD=G$SULu22Z4KC z2xDdnV`IkGrqrItg`!Cvl~tvH4?3yks)g%l(PsG>EZ>O)&nkT`?9@QgI*6U@r{_fo zWhO>u-nzsa*Y+V!vY1St4Kju}MD~8zI{2}uu)f-N)uB4o0SD4F;t96fasAv&++N~C zXYRaAl{)OLFrASNFR6!;qcz1iV)Nw$Ku}tH8-KfP^Y_LPB>cn_jD{A z5M{^!d8wHY_vhA(<_JvyNwBfg#3l*GUVx^B8al+zZ;_sL2nVy{;K|8>!ZBX-`GCau zRlHa!TG*b3Vrk;HR*J{*o`6m17`}(}O=}?|%DXNJma4nd9(8oAExxNPTR?8(6>JnK zOcVULMP=j?PLzZy9HuPz_Nfx83e#8NkUtxh8`H0H*iHm`xC@(&$2Z4(q=s_OOxT(U z>H@6hw(X{Qu>r`+kBB^IJC7NEBy*(-=y8Ie&6;h!s3T>~D+T}3FYfX5&DN`sZ=Fzy2^`kt!5)A0!yqjcnghNwczeNFg=jRm((BBCBwJX^XCx%JEhjimnWzE?Pj_O_Ndr=ubqWNbo0oWgT)8`H ze(7()Z#eczs7PW{tY02r_=XqPV#m#$Ss#x%&I@~y3U8Sq@%fu$erv3xPecddI)(WF z;>bb3Kq^qAoLQ9iC`{Pky4`f5$Bh21;i(*+P_8|?y7_E18crIZb%gB9mQt6_=lAn6 zbe=Og(J)inPVM)2CjxS!o&5_Y;JP`OF3TSvqRHISGLf|Q!4>8+oZM9E*;AGU$5Ud$Mwha{0T>!$(X3 zU3AE?2^b~gdKuA@?mxkS=vNf@DAyAOoAl+1F2R)nv%s+u8fS-@=m2UiyE^8S_wO0r zra`kLX`vUU4uTQjX&q)24ZM^>e8oRwj&&5=CizXHTU#gCX9B`fa>Zh>=kA6IX9NF{ z&c*1&V^Up-qV!qUmr^cLzba8PR~mctqc=f7#j7jemKB(NFI?kt2e-4`O!sxdSn0%= z@y%MFX;phmh?lu0hVr{3D#ZbBRr-x1LZ}ovQtlikQcP%*l?=sFtcb2X^t+Of`;dWL z{2*AoPN6Q;gM0(KYnAKvs+6F+Khe$Sj&hDbRiY~?VmQJpJ3?XwYTy+&Tz`PZYUQ(I zWuk3a4-Ud00mh(}L_W&!j5MBKlN#husdsE2w>#m{M^0Q2H8l9iwl{xujVmZ~tM&eY zZ}|9;o$5@#qPid#=@N7QLkb6D3iQ(aNL$ggW0G)uk{OiJW?-DKAntDkoZ_}oAGO!iD0iSPzBpVTk!&!r!LJ@TezacHu;0E8eVpBtR zaIH4L?jQJ*p%)dsM)UKBjR{P8T3j2cyj(bf)f0>f+QqJ_I5A^lCQ!lv$ljxpw!;eU zB*3Z)eNV^wRrollcaf)y^SLc^h6Tqtg@fayD*i`i?3605E-mc@*k$k0Vr)(i>z`=^ zZ`+sk9?)TA6!-^TOaDlOM92NfoGYChaDex|2Bms5Typ|`s92t!$-7HnJ$EXZ+qNZ>(qu;mdOIpyabKOY4-S)`=zav&2OmaFKC}TfDASLva`b8 zr@695AUi@xYJT55oS$?`u~{mOm8ryJEa*+mM3sC$sFo_N zdxKdIovPr>HuH^E%6)*+r@WTh*J{;jN9G!>>Kntzrbz3wEd$eHrT%0X~8;<@qX z&wM)?7RC`n*i{q^hv}p&e=9qj2r0p6C(iFOM8H8gmuMdJc6aWyb-PwDJ!Ga4AT7xxGvQ$gHQzWZja>(^NW9GgGve!ykoN@Ff!8p13L z+uf|8Fa9Y>B@W|?e{$4?wY406f*h6rB8DNIvPVQm>dGZf+bQ68^8&#lFZo0$%CsSb zcmomuWkY$f>74;hw}S>@!kN+q=Dnux(@k%9ObSTzWUMiJFYOAIw9==KKJ>vgDV6U0 zhLAuw3mX3bOct)X2^tPu{A0@S^!j;%T{;~!N zyT_~#`)N7fm#MIlkV!YS6`{r-S%CJsb#!dR4;O0v_78DF40D#25d4Aj|Dz(Gp? z6-&iBymZwxkK%P_v0RBH}u2K zlIzdeB6Za7ow%YrhO-x#<@$DU+f;_r@6^Gfm#Y8%^Y2Z`4D0rz|3J@)heCw{zT`{# zU2Q%Oaf30Pi;n0*H&iPRch!z?L{ze7LO1qX)g?=lG^~`_JJA;8v!ky+ju;($I$Ldi zSLtDUYM5!zU5pWtH_N=g_m2f!WVB|5pdDZzEZvmPlCJ~>cs_3fl+t7XwEJNAH?u2YyJj(ymfCpWSg6sZYo$CXN}VYW@55%%5cN5)GI4Bx~6sksFeStCYnrEusw%H zn~YS$R(l8fjS*cEW51QW4rf=5>yAfS+bNQJy%1$rHqP$``t;NhxSNP(2*#|uL+U2$ zSyPd$SqozghEZn_SPl*`4=;y}XNr|fS)nR~h%JkHX*Ex3Jwu7KMU=VE)i#+hs8rf5 zQb1x|@9I2U_QijlKwx~zNCNRTl_1JK*%79%1rl)5pHrDD9T3dsA2FI{G4U=2S zS-muWrvFtj{{!zCWu=hc+R$g;$miA|%En1{o5E5v@UX~MK$r+FLcw~)^a8;k_4$N! z$e8~5S9M_{h?`c!ZK(Xu{G%y~E7}5Ga6tVZ90?*U@={uG?*?9BYE@AZFjz6=J5H>fgIQOPgbc znT~tpR>V!cD+xwBiWzd7U3YYBp=)wvu9=U49Gd;~(D7#csn}PS8_4PmY_>IPCI~{0 z&$14IMw=ut2?519&f+s0({5Cq0mQbVJ6-2c44k76KF)18aZ22FA92I0*^hR7-h)HdGFUU)^o0%PGQigUG$kbNMrv7n z%kiAwGLZTud9C0`R+OVihq1PF?npR~qY>J32Rz5fcF5NHQ+L*?-8@f!|3fvLFIhY3 zn}5vswnZvo=~urpN-;f%~-_cefpk4c0kbcgJ%9ZfpP7Ru&#WfD=R+2LnbqrX>EH%=$Axe+eLtIjMvfj7r}naOB=R; zyr%s)*OSNKDYZR99H0Z0V}zb$mbO(t5|aKw&uftWt#O9U7?(9yaE!jEWOdp`&xE)e z#&`i^5(~7gH?lY zXIzZoYZ7Qi-md3?ZF2@2<1ntD{RAn#}JSvxb1f^Hn^LAn#pVIPyfHg#=VTiQh0o%e~nrkx4)#kVHMzEy4iOjhW$ z79&*UxT*-JD{t5x&CBqj_E_~&2fV(lM!R6w?ViEjs+ zyqCPZj@DoxEA)}bJusrpRsu+Hea()5zw2cX)&%FeS*K*-mG!&_m`6QWlhruyblL`c zY11{^S7>k7YEp6>Nr$l33hbu6*kz>8djBQf7~ku7gf*~x!GC!?!8z{y(;wS255A_d z_PE(tCb|%hCUJ!Ij@Ds<*u#ezZbDoXnZY&+3tMYn!2=js^|}!k)aOFEuEA+}Q-b+2 zAZo@mn2-zvP^$}b*R}>TbL@oy%PT}whrGE$ps05x@2%n35=wolJv&`eZB! zKeI;W^YiFhchp~~8?j*zZlmN)?zyME@ z+Sdxa1;!<@U^4^EadUp*__Q$ynZLHX1|$w`(<{@W8{=1>c!MQvZ{n<9d(4m|(g%{! zq~UqQ9ah(C(UTa+`x5w}b#jdl`=kzQQJJ_HezU7(Rlx z(SCu7W5mRWkSbn<E<7!KvTn4by9I6XK!-3qmqW;&2vK!8zI` zkrc+D59F8;nZ9OV9L+py#9G|4t z0dv#Ob-xuiAud1}6k}l-#2hEH5mdPl!7(a{L>!6E=rq|G1(I;6cv#I4u6 zth=^3BIo(q+5vVY2G9jUdRb8Wuts#AftzP0$O`%+qjT5bX2ca<$JVWb*^a;)0i)4| zg{31?`U43p{x7GeusBA0Gpk0+VCHj*$5UvZ$p&3U?G)x>eL2rw^s#r6mo<(uI_q@| z+T^2(*oVN@CcGa7dhgfT@>+cb{JCTbzSEA{AAxLpt4I=)F}7??dx4%Nt|~rEp%_EP zi^qu(e9du$@z}QK4b2qJ`SwK{1B+8e06 zC*XNiUxhg;Xq!aGM}8jZaTMt7*hr6;Ng_jk6BqX8Gsm^jwWxMev_CPI31bBGg3!j$ ztP*p+7o3(DMhWs-+?=>TGORh8a|8}XA~06b)=L1<2ZLs|S4Kg&2eaS@7}`6hDXb5N z_<44nbOfx8u?WtNwr1^!uS$>wwZgLA z+%YIggxY7c3)uturN4<8KC@2j&pdm`MH_aOv#*xKmc8GCcOkApnAZv%8wDTWx`OQt zK|`C_mZQQkbp}C@hQ|?%g#ig@#zg4jG0BmF-t-kXs8*{K=C5$Ry+LL*sR=0cW1iGY z(1-KDwpFmP=h;cr07chaWBQ8s96KkK9Y9a!blx-Cv}?9xjtkp2|K+(V_>siPE1$8E z=PD!;^Y0-jKo{f1aYz165SQxR*ScIw-xBXaTrjGN3V|~NTOt;XfoRLZXMZ&yVS9uz z0M0@9UJVCyCpTeyzvn%lZ+&QU4yv|&%j-7U&DO+t6|{$RqjMvee6$VyXIu4})cDMC zFw;>o)PN?)M#+m{!&X4zZUwA3CGOMTovvVAGva2l0Imi7(iaKFL-gkiY0E8OE83wa z{fLddzb3K132O_EabfQ!#5HSTJB@9x-P*6=-H40RSOg2G;JA)S*&F12TQ|82cp=SUueNF% zKnEPxue+9;CN+2@fr7KAI?In|_@wGR$_8|R#SzS-|AvyXPyS@kBjkv(=$tb8XBn~0y>*8@1 z$7wUm+A|QWS+Poe^zmN-}DcAVF@z}*6ZA2P9!Jkp*>wpZwfzi5jPV>AD8{H=u5j&%-w#e3p7 zt16GkavWXX3-3x?ux9~~04L1?k>hM%aqbZ!r?I=C8*$U%7w4MLo&d%XkRPcPfX|6u zsiK0sg}k#Mlep-Y7zH*`@6w*u`?}&LadMrv20lw)kW3Ps_nf{UX7GhR==wD~*R|N5 zwj>#d#&PD$_07QC3Uk!tQN=>T(`qB2w{72=G}iF$#9gbp6&MfWW1Lw~A~*%(nROKm zm68F^`!gE%ZPT5*kqgMCsSo4jdPpee^UF2mEnzieUhJdp*ud*Db>sRKeQc+m1{7`6 zF0=!)ihBzf1L*5~B6UaV`3gzKx{$1Nt_#5*+DYMj#tSyV208you#ID^#W8-j596Ze zd*R)Q%VJqbtyFt~(RdyiBjfmP=%{cNbz6;n-ijR0F)u1(*XL!sOGWmHJ8Xx|N!GKc#zxRS^krSf zg))i>gtiFtyrn|2%P|pMTcnQQLx_t}H0lMKov6qJ91hcv@0G0>A3c~u;9vp--TGG} zKYpBpXKial#quvEAhb=#XZ9U^pLrCFuU@lYMpxF51m!zObbuTq&`0|-b`tCw^lQ;& z-AL{L(A#I(sczV4c9}w3$cSF*#%Fw$cWV9KpD`i(Ey4G~5}S3Q2lH3-_+DZG9p91w z_Toc`3nUEzKLU#-h;Ran*J%*xmPAGeB!gQJw8|bBFlQ;(x9kYKA$ALA?ioY!YBPy6 zBfDViw~`k01i08ENt=mmbp9HkuVazeZUO9N>@hC!itT1G$nhShY_nSeyoC+-;KPVZ zV7QgryOp31*>1GWA@iAu>p6~N{CZAjq>#NKAg?+I8IgozWO#I9ygC+w19KZ|z~%!6 z?+F5@Z$(=YF$vCs?$%%)D-Sk67i`5t4{0OG?KT}->VrMH;wNJ0)c;4{O<0k0tUa&v z%Q(SbBi~ohhBfweG@moZN0AnM7;ym$lRUiMB1>v44B6jr*p4|dG&*bwLD&e;17r!v zDg;Zvi@RK-rC8;7&w4)+-hBF~gaNQEb7B*GF-&gFo+59MT}r$DUF_a{mG4Vi|E!QO z4tQ0DpW8N=S7Z<9M-m$8%sM|BA4*(+0nE5v8X;IX-_z=7Gay;@y{ZMIVvelBTz_$# zOo}i+)ywy^N4S`Y(VvqNKN z#b4aGh?yoP#ajVio}NiQJw=AU-Rb4LDYq&<*Wkm6D|jf}fW9Xoc&-6~Me;GASX*5m z*Xw&9R(~`Q=($z}U=!YBqif)nTA3yDOjKD->wA(c;$_S=0--+>pGI7PQ|tD_;3tc7 zj;!_;BOjH%tzF?4`k@a(FcP$*A2kppUf)@P=(7hTkoVec?8ROkdbN#93AVHlqr$Bw zHq(CkdW6%sr|}&7Jw?5&w(PdhfeM^$Mr-?Md^&MOk{3hm0Y=5hb-TiG0wRTLz5F~2 zNF1gf=p+apL;^A|yM_@UEJ4p$23=NP`mW*h>!efL&mwQyfi2_=taY|?V*86P)}TG> zPKR+FXH=>6BiFdyH!X4Oe!nCc$Jhe8(SFNsABnpmF3^bt!nvC-h*HSZN+s`7dYv6T_d}%B6##RQp{f`7Z{kg8SKMCYB!P;F1$>d&eSHxut zV!#jtWWx*^NJOA0wBs0rp*osD6~^<10|cn&>4y*;jK`cgc1rD=LZD_JZgMdeV`s7; zD058u(SLN^+xxx|Th(q0YdT=lOfb1S0jw39=ud?4`g#X?aXhO$lKmM^dn4?i@aftb zv%Nwe^lC2I&rTo}cST%)Ve@7L#+*G7!=sBh1A_XBQ1!TZfSktq(QgcSoFK?P zIy5t?MAvb9g|Ta8XI$px5+DOvVU43}s@R0}pWC@*A_8jlAJJS0%+mU?v40)*D2#JEWtdUo$^l7})E5e+{1pV{<+U|=sjBn*sFl%X?3(;@~2Lm8-f3imS82oa6V#NiJR{gf^atG z5qL9&@d@@yrlX)g>%j)zgu)+ypysppB_K6+;_?+w+;ATmrL- zW92cbxdANHnLw_D$@QQQ389XOpcgl&j)M6}65M?0KksYF4Pm`U69S~`Th}%gwox~T znt`nT5H*1=-OkNM33f?TT*ssT=|=}I$Eu8cya(79!S2|l|0$VejL|$P__g})o^aR1 z1sU8p04Ebr2ti~})$jW1=e@$0{){`4oEyo2_IkB~j6SO{d{yKxocel}hyAk+0S5tS z({})Icf41P_L({849qROSQ~(DXgD)0DWj%bycuM*ENy_eY%de zYr4*ef&SW9yZE!kTZp?SY=vGVH}qvako#zo`wZMYafL>o8I|BBU=*vH+qRKh3!J@P zC0=KA+B@$k2nn2e9Yx#PZcSM04Cp-Svn2$1CW6s8t_$(kKWlQC$XW$#iDGwkYpS46 zPg+e#COx5t4hUoWp7XTryO7B|Ju$bokAjCGuFk-~9|eL5hvRsH4WVKg47MR@pw~+< zzQ`yT2t3c%>PA(hk5v({?PhM~a3{fgh}w@WW0=(FPxYV=dNR&l;xZon7@KO>=Usjr zlEQIbXUPIxa&0|dL)d!2k2G%GA>9z_SZFpuV=t!+V4^DFvJB( zlKlFFAjHuG5wBE0q<%*HJbFd?_rA^1BQQU~0|By{bZG(2wvwvCIF*dF=bD!O8Q2mC z^3V^0(sx$DN0N7RN9Pgd2Hf#J>)v!`o<3LHFj5F;uLsc$9VLL((F)D$tEAD!*RGC} z=h1^52K}+^8njRPiuE!l<1z+1A^D#3_VXHDFec}bN5exA7kF)nv1ivSZ$+=d%Qnv7 z$bd8GIF3G6EWu}jgkVwE+Fv1{kAgrXI3=LaHVArSABe$ch$T2jUyd=>(eIFFyA0Ne zFt#^&MwO1^=+G;BBX^F$aqOP+B(x2$1nsr|-6Q=S z(@N=jgnt_hLiFU&&d-1yT|?@tupaEAP+e(%6h6d(Ui4>&!M6XJvF-tnN8AN$X9
2^G@u$8o^c-RUZupetYlISFT3ge;Qp0A)%==bP&c;X72A%O`DwqQ8>NLD2n z2qwibAsPY4E60xz6+k|!0Ar}1;~7kRu9b2m>caIbx}Yn6y(zuSHplZ6xECefTPfcIfN&mvI=A;|OCPNm_tDR$0NZj0@KO74T((g}!`Nrwsz36G-R5M1oulN#5dZCriB+((EY`GJ)p zuzm=8^cqyT7Ve-R?N`N@CN34jU?zxCaO|GDl_P-=Ct3angmT`XL1q;JaNf35GOE@s zK%!#Ztn-%Nb(_ev-)LRf9I|4U*$+MF`(nfG>+3aP4(z929dERQ`AYcG#03P>R$%1y zSsoi5N7evoM*ScXH>l7DA=o%h@LPGQ()S7y06Jnzwyh9^&ul|k562+OtM9*;`w6U# zb|aknMV}@n9&AZ}iRo?zG2zv|$XCagULlUoU*fQZq8dOhf`wPB2A?Ik+1jn4f#>@@ zfQ*huU*A{Z9N;`bI1BF2hC3H+BXKXJUDtEH?@os6;Mr~?!I~Yzgy!W9iNUS*?6QM4 z^|A)ve`S10;^OplqQZ?Tx32X@5ssz}fg`vB44F7aa5&DMU3*UU~j_RROh-! z@>&tK4f@tKoX@p2^Ii0j>**Kb{&+=PKr+a=yz`7*ldv^8^7*aT_4kS|MO+9~oyYd{ z)z>SnGEhOhG!%i=cHI&l=b+lVI|W zxgyb6%hBs}NLs`-565qpBM<|! zWu@E;Z9?Bu#r<(X+|;8A{=uiTAxY~m*P8@p%(~NhRD22I8tjT0Kex9TxZY?FCz<30=H>XC)qF3@|3uvO@`h0JtI1y_7(M0Ch`{RNni)2h&q_8f)`y||6 z_y@`XuiX~mgWM6hl}6B~5)oZJZyfO2$=mFHO=7u6JT!3$YHyk-1TfVR2bpQziYBZa zv;i}cchW7#4GIG52nZbd@I(=THmi!crR(czfrfrtWT;h~x#&lbYXgO2T%)eR@#t%% z&BT_XZ6(*yq{iC%S2}Hre71EXauWB2tUvl^6G*+R5hgx__xO$Rv<1f={r_-!XyVqO z90`F1QE;}BWG5hn$8c=I>nnoT0L{SKLK4-n`;QcsV=118;9l=RL7{Brjzp;fp+HtSs#MvV$8&uM9pLR6{+@^33fW-!+XQS61O^t zAcCY2u~wipNqCIF$t{><<&^;Q{T}p+(kd9BMDatgZP7k3Ia9T zkrWb;TYZ?{ab}g4wgi6PO|2g3uS@^mc6<%N^D=SS=(f;88)25vW;Bs?+GWe6YNeF^3s}$!X!J4qWP3Y(hj;sI`e)s6sH(>y=7WApfB*nm^9doODtsd7( zR^lXE+BpSX(M7$H`lzO3qVEj!cTCo(O{yQ$SMGMIjFN=s5`DaO*`r?ikq1T|4G%?J zZYbm(S;6~s_$Yge9x%&YOo9UPA(hNMAOns`y9~M!sMhB(CPkTZv|)?NcwWzrgbE>e zUv-^{?Y7!yl?AnGrLeaA-w1MNBC=AZ?g{TC>o>>G3OUomgeloabI^u!nLN}hlc~0; zHn?{@6mhf45bQwbrNgsYRCuGNI%-tGY9_am5ERZqaOL$dU$+v7&QE^{$Q!gke; zkm)q$Pu+IfK1pVxOd+`?No`Yo3DDDi3D(EM8@UU!UDflR@i4?Cz%(#oF!~%TK{Bc= z9HTAZhKucMmNv+8jC!3ZS#`Ws((!?Jkoj(zz=B{oroU#f;wS_^V=~8R9SVAzC`>}= zEk-*6Ypxay}Gc&Mi@k*7#jK zWwzD}!K`T7QSg~L>Cf(lT?u0V`*03a5_hz`mO3M%`WD;Pq?56b=jf}hX#;e^E@%6< z{wCpj#zPP{9VU`MwJLfXhu{PpRUM4<2i0TCn#eh`8UOlnK$qq3R zo$$-J=gi-hcB*!#<0Fx{ewO$E{Y-MS%b*V1n!PM}{h z0RZigF{RH({=PTdJ#jP8yeR|nc*X3vTc`wM>fALMUHg~_cm;0pwt`tQ)j*r;@$ze# z9`xnBfp#Y8K+uh~j`}+`)e7C~d4x5l{ej!jEx6r>w6nYJK>KY;k_}kBkqT{E=K^&s zlksSrd&Avpn8wV&(aD^zuzhA(f|3BIUxm))9Pc4Ot$awWkm$oTDd*f?8Tcc;gl!W1 z?2qS{-Hy6G^xPW>ARHjOpp+Cuq`I5YVYRhrX=l0ia z?F`r-(pqWzLn+I(KjS5me2V^1JYM?(&(+H6LmS!-U?cs8xSP=Ch;8o`cTe0|1=u5! z33v?WR2UQ$_DQM83MR274!VyZK?J0&70n$UNt&i9JDC+^6oZOAW1M}6wsnpT14V=ldjHh zn+k}IzS@xWQPmQx%fC>;`##SyUe%Fmzh`#?ByQc!R6E>4p2U44G-P#FZfg>Co5+vR z39cF(IEH{4kxq=ZZwL=pRAg{69|6e^%spl$s6j?W82vW1R$MXDX(ZUp1h6L9rv6~U zb0b{b`K5o`4vVxWHXtGRx$X);CZd#45;7TLGv;9zlJl)fivyDZ{j$Of$47Dpa*ude z;sVcAc)V`6MnRv~I$VFz2Z1=51R!COppFe7r3r%bkePn{IirUvWH<#JzXiy@nc6r-~#{ zg_(awT&rQiu^aS1%e;zpKpE3^fB zh&v+;6)kr)+Hec*3|KEVMXZh6=}wf%Ly-aA+$oOhKgakWDt=e>8_y@ndH zN@iK!Llz8=Lkv2kVs_j<)xo3ol_2z~B!sBhj`0cNes?&Iqw|QsNnE$-^k<#+RjhJp zlaVmbh!he5@^BuQv_?RW+_Iw^dPxR^HICLbnjcAwj%7dBoA6wnQ@@f-(2?_qI|OtW z7K2P8_lmJ+?g)FY=`OBSLkJ*{RS>Dr!3M6-!Sn?HU`nLlBeeJ>%|)3w&e_sn<+8x*FT|$jPy%CwTJsdq_UT zR8dK9C1p*uibM7qybz%Nj`vnl?aJKPv{rIHlUV9q-cf}VYoukKkzcAlsYlM0ebyw$ zQ+F;wK;BSfo=3n#5SPlNVe9pWPN>ASLe%yg&p;ulAPL8LpIau{al8^PgCLKerID*v z1%aM5kh$Rm8K|~dQ3)bGGZyDB?pO~5JxAMkgyVG`DF!S=VyBVg@E$uL-t1BMPJ8xu zn(^B#S#mt>$k??FenEEaGU9t$ke3w1NW$@)zyG%?OWQ}Nw%{R%3veKY#o>u`YQ?BM z$n0HVaA#2A%vt*kkSgf5hDsy|4Vb%Dv^@gBE3p*D9+6{BQvXVxt;$3K$o5r81{I@r zRlJvRN(e7Iu}V(hxr%z+M7rg#d&28g-6`^qb>noeB?)pJkr@|A_If?@n^z3bu48sanYBG%U3NU-3ab#&qTd$uY!)+ENz^+ z(I&;?GqN*Zh2JA%yM#$Q*Wmc|(&V1;FvJB}`EayV(PKEtV;}=40qH$nZzg4>sOLQH z+(}w=OZ}=4@Tpq`TVWDS-W}(9mH+exLnFcqrn6S~W)1h+{~SfGWl@ zUNdlOpd@i~>ol4tMXXgDgg~My%|IY?Et}(T-Oc{e4<&8i-XM&so z0&t@(#~O)ME`4ZH@N<$|c&_T`$>&k=u*4NL^W&gI604E|vU;w<;K;1a`&0Xu-=Er_fU@!>xvmvR zmN#7-Gr#Vs!AWp?4t?tPvFMlZIBgt1=LD>|Cdg~$#eSDbB6Y-nwBx+Txa>%{Td3>F z!FG~HxuqIf_zd7N#t?G>&DT!TM7YcvY1_))gV=BB?W1-y5{!~b>?ZS(fNvcd@V7h zP6f1ss(`^!No0j!JpUH( zUSTY^{mfgpg2Fbn?+AVCx?KMBnb(e+tIw8uBhV-srx9v6Q^s;WxkdmwjNnb z0bWC@Q%zW@YtEIJ{o;uP%J>j3$Hkr%B%GRXsls2W$|k-naRnr)4ka)ND8y=Hr6Ev6 z28)<|rmd(IU!pRf#|-#vR|TD@X7!Hf9uTy%Bvp}PBj|>%TRQ?^o2+UzX(iGZl6tno z>FW=qT_Gg>(KY)Z%$ff9ium%x1vq(n4b-xIR%Y(7iu~?WNM`!jFSmXDx^WpxF*($# zF(WS9S9I>}2i0f~nA3T;klSU2H9?Hjxk@IYk&Tojb?uXFAz^OSNwA5U5UV)e-gQ4R zz6EgwL4$KgPER3#5XsJ~$iP$b_o$Tw8GMzfsS8zy^8_Aq%t(!FZGVpO+LBLhSx1Q?zY5B=vs=LzYuk6)yQ}#w_*TRPTrUztqu`lI61N6W z60@Eoc+LK^z~lOu@!b{dNi5#1dMnT^cN=s0(c7WpXlr_wrk6jUfJk``TljR^=vPE( zWAv)$M?#|b{rAiN%JaM6+Yz?{=TlXv9&4l_XlvCW(+dJG+Y`jOMFe~%Vfd>h$jtl% zd5+6_^s!2f$OU=p_($;TufGnr;Ry4(Ui8Sn3c{M*jaV6Z?aAkFe_KXBq3X?zHWOJ* z0L&w1$%J`m$ByK?O#}MY)v$pSqCvhj35z;{79F>9h zqc)>phLi*#uh|dc&4SiYgTn|F7i|fdNKpLL&9qGt1#y}Ux$@SJ}zB}5Xx;rpo zaNKH|m|c>f8-3R%VofKz>~N;;I-Tlv4RrFY>laA ze!ax@cB_)qp!YhGfX=(Xkj86H`anv^>?_CkzkR5R{9v5^{W?3UHGLVQ-azB{8d!t2 zVSUU=h0klh;~05X1n|LJ-Ks^%+Hps3P@$99Z8C> zsaPxI^*R|6=XLR#Jc+vxPvY*tUgBne_bn}fynJ_=RhxD*NrK;ta3g7?t74*Im*DrR zBrP7*%*wlmyielp!;`o>u!l?pr68C&mS17qBOz}t@l_AtnwjL~cAK5XuwMw*-%o7g zN!%TH^5ypGP-O4)TR*R33@H?#7; zOra{frQVXfBmcx0PvUN%-f7({RNWc+_ObordhqpMgZR;_*642RjO=d}wz#9Q`Anja z(2x(^xOQa?{rw_;|LtW`Rdqv1j4V~#Jd$pI>b{=&>Xwx>+QQG zZW?h-MqAQb;0PIB%gbMZJc5fYX}ewo*CY=g)&RqGY?WD z@#uUNo)+7C`5^%rn>eUr@rU}h2w3}6agNWz-4Hhk$a?}QGv5}-m^h;bHlG=fb2yTA z8326dczq1ezUYu+>Ts%(?aA7-Bj|bEx;EV-J?L6u$eUE?!O!u~nKixj=_q*-)_sE9mLSm&Kdh0vhbDZ%E925< z5A4VI_?kI#JoNia+y!xUCc^uK$B%#j!QeQsl5s3QdOIE%=zmGSIf}kGECWc=l5Hi_ z%z=^sA;TL`Ym&P9gY?;dU1>laTjyzuN_+ZxXSkBYbpm%F-g(|5jzgajct}HiB@#Z% zHJ}$aBYA~1IqoiL*YW(0*aUP0%*hz6Jwa?{BBkAD;Vy`)!+6Cw8U&|S1L90bu{U!< z0G&_fCo89LygRn+dC>aGuBdaMfeat*X+9lj@E zbB!KHc#lpeyTAUkHRvvA$1&Et2aKINff;E!IokC+I@ku=R+}gEqw8`G#ADctHJbRY zfotZK`2;r2dl2aOS@?9~;xr9`V0;XOlh(2<=X4g5gg*+dAM0OlU@R&yfglJhbs~^w z*ExJYTkqUb)n@HQl9c!8;2%y;TQE1DO?Xw(E(igbl>(t}AERA6Iu4(mM_cw_ZJ+lL zHy^lA)~-!+y^u{16C@9GPYiz}yc3Mc9L5`ZVV7zv^+E6%ZLoo4u9A9xBtDh6+&CH; zgN&WQG&9;r2rwnHrNGdfV_e(OF9|w>TT1$_{mrMWwkf^eO7LYM_aIux&%E^WT(2f= z7|5mmM}+`Z7Xwy%dFg_!yaC5NYe?OzFxL#}+pWY4fzod?ov>H7%b!7BBWwTb#M;{4 z6Q4?4996d-I1&&{IEo-Luy6?P39uB}aU3T()|N02eUvS9U6;OF&VsmB41>tNyq^W= z%kOm;X|`eg!XVE_0!eTNu>Epfh<0Ia3D)mYFR)*(tJ;#?#|*X7G7t0O7yPq&XTP1& zKCER8%;TDJzCDuLcLX~aKN$-q=PIlPknGYn-?HM?EM1Y$2xHVefD~*=*;euX{JOlS zTKzT&B>Hh3JOYkWmn733;A|Xhfbu#5#Bq+^rl_gC3?hkcA|U7pem@K6HOLG++wmT$ z$ROtZ2n4=u@KK$63#^GhG~gI}!93m1?RDg1Zh-as8_VZ&o8@(e^m7$Ke~*33ikpFI zr3HP9Rt&h8xO;)|A)r70R)<*zP2zQF)o@1g83-WOL4U>?&4muL#9+WcJOZ9EGH@hz zj(f84kA8k7#PQuWanp-LkdWA!acnOjT-B$Na0b>(Ak7Fb839$1wp`!Mh)HE3F_IMafO(U6 zM|T~Ww2)IZJN=2-HL&iid^6!jlzywSr7LS=jNHj!7v6i>fPJwmzDK9e!lx4#U=WCr zj}U5Xw`&>=BXZ2G%1nB|F0zMIy_yZiL2x9x5I8tZWF;6?v>9dr$Q(KT3~SZVbr4jN zByU8Gu&3J6W@z)t_I3ofE5z)&+X_9_z9O)(2XAjaT5BtMb~>6-zyCJ>mwfOuBmB<~go4*FUZXF-NOw7V7% z7F`|dT5u`D4Y2N)V*k{gISl+YWPbsHw(QS(j;sTp5CiDmUz=$2HK}^gXY^H2O*)@} z`$!)Ps(}bTjAsy1k^BSSZviBScy-&xxLaeVE>* zjYJ!z{dm8&-Dj<~WqeToiU#8<*mRcII7edJlPpGXj`|f*O6oB>|Jk@(;?{uU7}qa* zv-=1q;*YEPo@-vg7-V9c>SxB7>BaHf9_Rm|!&g28C~HFpD$pok4fZ40v+W*%fUWa$ zOhS`9-PK@I+o1QF9cj}E-q!98Ng4Gc5pXkV%U6 zpN+dBt^t!ls?*4hp5GggR@MwI1+u+m%cHaTD~?>}&8i|gq|+h00R~Aq#=6dEr$+iB zAA!^ry{t+ifAk>PAhEtd(7z^L*33DGhJXF;_UO$m{bsUsOa=X!`v{4Rv0TH2Pa5-c(bV*LL=4~%+S0i zAXppZ7xc@(W(}Z{Y46uYM=|>OpipOSQt{+bg8#vvw%Au6v3}RWS4Y@NZ$ zY5td>%!M!yyMt;wY;M~`l3Cs9&*w_!&%)gi*DT7bL?j5@mSIS4&*KotYv!-Q7S=yT z{$5DJf?8qw`DkK5)ISt%P$;xhcY-d@@txp#XNbonCk~E#POMufCvOv~MPz2g&Ykm!S(&%%Xs(ej`8PHZ^-+j6r%=SPW@wUeBz?Jwe*fw`#=u1XZk#N5Bv` zf;Lv!(fCPP&gc6hfi+41j^lgCBhag#Z#xHc&bdZo)$Oz3dvB{dZ5)TB#uzz2f~_F> zk+t0lnOIr(tu9M$iMuE6=@&#NBjVuqBrnA2PFs?N!Nf6w*@^)OC@QcSn+h_zYdNaa zw9R#J-lvG_`e>Wv;98#~;1(QV`^PoqT97mDDImw!dFbv=0YN7L(Y)WldXw19$u1!u z4P+eV8j+a3u7@_P4=@hr>UwQAO9<})bjZ2y5qD2qVNe~dFk4_Of`V~Rfqj~T_eoSh z@YB}7LZ_OI`J8Q1G6=Y_f(r;acn>hQNeJEU6%RvPU=R}M zl~mV)U@$P)=92X*840K!K&>hglmwT7Xpk9*8E6PepkC*)hS9cBphK;U9D|7H!k9>& ze|@d?;}wC{L^AN2FS;cdW0bf!CixPKhd;e>L^~3IziQ&V$;WlGm9FF5 z0$%d4u6xEq5%-oL7kzfMd8mUTN!bWRxh;%ki*b zZNp^_&^WaQE$HBQgPz=Y#>1QiD>05bU`l@P-#87P-x)eIj=l z;+zoT7(jQABP21aU;;h02c%-1xki!}Pp{=zRp?)JWnBEkc$_D(dEE7NJB2;o0us$F z6Ty4p+Y;A6OGgnTR6JeR-#Hkg9LI}T?h`z2B~U)w{)CKux;caek(*3`xx-Nw`yf_0aP#b zPnlKRs!JsCu;3HZG)Qb7RLt!4`66Ox9lVFdx7b7jTOz^xTG6O6|VcqJ<@ zSX(bp&oxQ=flOSvZr*#GH3`?tT!|zuYo*_lxZeO)Gc$jM!EF^I%k!Cj86XB6=aU?O zt>LILDxA;xAqVYePwEh$euv8SDc8^aL5yXNthkITNory^6S~buJ3k}5PjJ(Rwe>mL zHo1};TO=8&oV3q`f^hvRSetg6kvG@Eaptd8T$n(f#Qg>^NC>bINjPVqaNdB*;IS%K z!ek{Q;H)~5pJN6YWAolbG7HX54zDklubBYt{l2#bu7s@B(&I_OBp>g+nUtW)p*uvW z3D315{ce0apFinS(pKopu_W(#oVG@7VY`uSJeO-NVnQwGNMO9;?g&_<4In&r=#sLiN-l| zy9)T4=-j^vgoME8n#4|$NxV!1uGMxX&e2ysUSp?CzYD%CaS14ihGgY6h=E(NtPGHZ z2O~s2Pfd^l{>)2~!Q+7Ky#;sO$kH}yj+rTDW@ct)W{jB`V`gS%j+vR+G21aS#T+xm ze0OH%oSDgc?mzgnwrWZ0eyUp94b@#G)f-MjKy+NP_OT5HtFjw7?_lXJ7d;X1jqL@v zM=Be!QYrSKlNV$fLR`^g?-2)Aenl^RGiYVHhiARk;@h`_5r#gm4_?~4Ak|1WWU#?wC~1(_Przu$Udwwkg+)eY%R;HH-$ylx|dbq z)PU%dLJEjcl7LU53A{7bAWvlaha*;=2qWI4J9sluu9*@om$%yH zKQ~zs>o*IVB??2c=bj`gudtn^KrvERa;G(_2e2;FhmS7nkkwXeZv9$#V>igVvpE4u zAY?3O1H&!K=1uCmD*?^~*gQs(^ECV7I^l2InF65J4G`oN;63@=h|Q7&9fH1oavH>qB)R-f-4z@pt*U zE#<;#6eqx<7HC|U0THBCEr`S?1FTZbF34*|mpYqdDXrMZ%B-+t+Gk0kyWqU$>4&gd z`4hPz`RjJoJ11n^%Ua2e>}53rYCd)C3_nDaDHNk$%)ruoxx-I+%CnOrB35d2*EkJK zWNP82PQFnqu6yryj^-yULYongTM#0}*{`&y=`_!JiSfrJZyNQIY{P>X?;G1#N85zX zQ|X4E>OTm$EgiG6K}DZXSx6$EybR*V@8b1V3K1og@NX`IxWL72`NU0~HHo0|QBe}) zE&#lP!6)C4bp#;z$lG|;o~6b*p$2i(^hPAE7f;d|!e@qQIaA?D8rr?}u@A#Bc)~w} zRhc6PsR@FC>@OmNRD$QBh59wsGeDq`Q%9pQ#!6;&>Y?IHdz=B@%NvEX9$D7p)Kn(> z;750or&rC6A|$#RRK;$T`!2RHU3Ee4+tpfjH^`PV*u@BG9?k#)hO0W<-W6o<*pgT^ z2KEGr3{3l4uIMLM4-zMgJ+7#9vIaZ6&kM13Ij{jO1RKOE3R*Rca5c3qp* zkVaDleX9)wF?(^11TPz!vPePuI7on2$sN2A7|r71RD@;|6;|#W_!7j`j1pD#FaoO| z9M3XFPyS%y=Eoj+GN+RF;WhX1hRv5hKCelb>SWPL{<4EKSVB?or-)c0d!mXR>%ht>0sp1Kidi6~^L&0> zGX-^9l{_D`*WM{f+IRB-BTU|d*MXZS{5ghTyGl-CQ#K?GncWV0QlLYti<*R*y?biV zW-w5sDIk{Ms%4vNo_r~-AB*r2rDY_s6(0!1OU<9B86qI}?J=_Gqa#mw@u(q!!^07X zn6~ACLxmjC>S_xA6&ry4=;ag2Cyz;vfUV#~8>?W|IjYUk`Ra_tqv|A@|kv7cEaes>VDv-UbhMkNvrUmh$z;9bYvrc7??7pGvnL z)Z!p9Be_@kl2t}345-iVQ%ykjAeO9vHzp68O;PN3) z#lDdTlJnuo?{0oF{&0;S(l+ind1P40`c2~6YKd*LDKNH6 z$*QC($6|J4wTU6bAlDHJEGteJwrw9#2$pg1djh^utz;H{{0W{UGwt1AtZpDwM}R7S zclld;d3!sH)6VW|8kcw)On*X*Hm|w^ZdTNC8j0aZmp21Vla9DVQWTO2)Ojlm&3Ytv zuCFU_b}CX#9q4s9M=X~u$^H+oHX1R8s6kG)0jLRCOhA}MI)rhfm>5Ajneis6V#OFF zNgZ-Q#?mgv^f)9}_#`PevtloS9etu;EKJ4j8Wkg9A)}GG@+V{%B!zPv!c_;uY({Rr z-iP8-1rib&=k>-Y4)t8C$~Plh5>V{>}EZlA|TV)gl+T-xrN*G-aw zJlfd#y>F)H=(Br#pRXnp?atieM|$4&E0%m;9`#R(1y4K7YI6BM-@PvRzP=2CTsS(X zh2qCy!W?q(x!TrYvYBfF;a@)_a)cp{N;@YiNidTyEUMg+K0x z=w#_JL)?u0se=r2sfCK&%R6iy>u{P=UDkB1mPVfM?Z7v^XG`d8tj)cAd2PTK*h!`Cq8KC*9Z(ph*!YKb^;!o(R&`Qs!IJv7!} zeOntDtj+7tH85tdAR zjH*LqOUL#yoautF7(Tz}Z4Z+rNj0_qmLffQk;HboMCy-fWHnlK_OQ$|aY z;U=h*?oMPNr7g)<1b;)hoHO>cf8 zzM@e;Ce|x@e42+%2i9?^W7Ru0w=7cXU&wDtY$g0;$WIT_0nxdb7yeergq`nQ6#4 zs4RKhT4E7Be5_31tT3LU`q=SRywtdUSCk(_i}z?&Yq$RkABxa%w~j(5wet$`_K(<5 zhEi@V^Vc{FbU~-KEPoizZ#JRKkKpmMK0&6nGO9I*5ydA89L!{S_TL87HY@|C_ITOk z#48pSoMaINn6N|ptytkrGGxTDCw~BMYIU6lKM9^j_$7GbBexC7AiKbXA6Q<}lH5+o z1&glr7ZlH9)X(Tk*YxgxC%BVXe^3%bv_<@8wEsoLXMaQa!8x9B(q@Tf&5FAE%brD` z2;I62avMX`_VqFjpNW18b1K$t5@oXmi#Gni5>>eMcZiHM-b2hOosAEy#4M++C_hKR zCfy;Pg2q(BLL^6UM~n0MNg5_vCv{Wbw)6o7&f~$B^R?i45jqQW z9fFg^#>3cHx6wll6OS!}8(+F>RJ_Mh=|%P&#mSS!hd$I^RlZMY1!Nu zsZe@e4;wNmIo+;DU%WW4XxepqfCnDuF`o}P^XyonXgb|696f!|Dt4VYN`8vYTfx5v z?ODL%bIvA)yKleiwSv_u7Gs5!(km=DYFRBVQLk|6GHqeKY)43K4#}?RRzFzibbSNF zz zGQ;a~ky_h~j{amV^UiMTZdkvjeRIBzQrE_AQNCIG{rY&?_vvTtsrA-Cqb)e9OG`z4 z=3UuE=7#H<3AuLqd7zaw-1cl!^6^eX*M?e27dLz6glhD0q_86T>s*Rwj?Og8xv979 z>4r>QSwy8vHurtt*c5K8sOcepq}Vg_sT@`K_*8P$v%`>$m2_?20^rNP?ZH(~h1Nhh z=n2p7P2tbwItRRn7v#;)+p8~{du9{Zs2PhU&8ORb<&KQCrSvd2_+dFqZ$MVl+Bjtb z+3tHstBzNEAWxwF6F>HzIbPC@U^13?MBn)E9w(G_aHPNEGap0{@b^Rp)DOk%Oy~Iw zE$n*(-ig_86k8Kqx;-#%KMc#ljNO06d_>zrH7FiGujV{^&LYc!$}J!N=*piFEOH0t zO$%RT@g;*X)H(UAdwsE*H57Tme@0KxxmxTr#CInylz(rAFXpMCGRJlB@0vb;vx1V!=|aVNUx zi-Xz=UC_jHxF5>1=}g?|i^g3x57DE3w5~2&?T+cG9X%aAXZW={@brRc z>7n}RWj@QAmo4DkXlI2VBUM0rvcu$(+TAz(#3KU{YdQt~2;4s{<;@GI262~hYXoW7 zD4IwHiB)`!TI3ta;F2QEl@RKcNfsFe@*GLDgUB&KKcbKcm10G>f>a(lB4rz-f{4Gm z1DmKvrNZDB8c|;8F#Nnt!tPtpD+uIHLqP$HhzoL5!!XYzb1X6nrlc;&CO@VE7SGkB z&wWZRdc-sYN27{@E@4`-XsUcu2TtMQM6wc6TLSI{v&<%0fzpa140Wgn>X3jr<#{ML z74*z&ZJK+65hOVaK`A`B5Q+H@dn%|@3L@T}9j+M^mYAPGp_CCpQF~@%#9wK%Xwh%s z){ybcaU^;9Mdkg!P({qrAc;uwG=Qlx3ZD8K@Fc*B62L>oK;rG7NlK6rv6KVFBX5)` zWHGZe1SMeAa1$AW)!2c!4%lB50qt^SkU*_skSM}VgH!VIi+Ix(6JTv1pdx>9c_nwowi!M%5LEqPq$UUCkXdpDOb^Mx0bmMnu49uoA-i1mRm5xS=%s9%}yqy>@6I;v(* zI>4=WCKLv`eU4#=5buNGm^Xgar~Hy{11^nW{p`-hL2f0GK21ArsgP1cK92DOK4G5Z z(3C??gwP&gTfzllKcQP_L$rcjnaxR}^31S?lJ;8qC`x3my6`qMiukNC${X z7V$iU#Rp6fqc_3MPw^}Th1>~dI;2685w0-9 zd^k8-l-plaQ1e@+L_vt42;hQIOKavNLfrR3Nv>vJBneMZ8v9GMkUOV~wSqzJ{6kI!qgL zlL}4++Q=xet};C0LmPrg28k7g4PaPY8xM?6?$)4A_`EA!2dHl@pO~T>E1p$ZEe1=t z8iO^xu{ZNu2BL&NW1!sy;QyFyy2o0{MSy8tkr7Z&ub;0PMK^%WwoJv)O&b&6t||=V8UiT)*MA=FOL8d^{U?QN8ub=edLD!_f!LmGjUgyyr=l z$y;;hR#NuU`c~dMV};`#YWA~@pAN0X%wpnEa?3vr>kSJTKI7>aJICuz8bwCw8hgcG zMas}HImC~Ol6_b?E~~TAoOfeCJ=`JSZ?AVoV0-uK^F@@EJCZA@8@;uLnmr)vdx=@8 zI(NIT`#^pm+N0+p8?S!g;7(j@lr+^ae${p{v<{3-#!mk+g#2WG|3~mO=2eaL#~kGb zkOHT76E?l=5$UMLJ2>I4-J9=kYpLEe(A{LvWO9Cl&YjbV!HFLeM<9<$h;dG}guah_(Cf*%a5{RyR*Dq%UZFgFdpqE60M z!d#2{3FKe6DE1ks1XZfzDsVu9>A6VEITTUv7C4afNVj8)L$?>La43;yxwH40$brGy z7VR$g>NknvRHPF10NcmF=_-p&BBj~R8!({J6Z3#nr1S5mm{i=;U&#Sy{YD?(?_c22^3(ZE48 zK-7P>19i<}y}55&x+(8UUQJnG%<}VIubGZ%7`u50&sp1Jp)F`S+Uq}M#g^v~xwP0( z)OKoa)0_T22lN6@ASE$9QeZe?2_YrdvgT+$ZM9%uu3U%XxvEiP2H)v?02_xMi0Cu( z(-7OrXZTy0WP@SrK~!AR5QLdVOKJ&`ttVMP2xbma1&|Sw#Z9_Nf?+0`jlZ~$Ysllh z5yqW7Y2=GYA$}omw$lBknmnk+T`<4A(38>B%a}cUdKUTBoSH=N5-d-^DYrj12c>-P zn-IZT$qPwn{noJjYn|aeF)bMzDc6Gn_-+#$H2J|rfV|%8=YR^Z1(IGP(Yzp)<_0QE ze72BMyNGQJ?jfb1=J8=}SVJ@q+oS3f=?^PNdJMuniMWQ-BUvm?^?O>gx1qR3u9X>9 z?o$~bH|L{M5Mm8k`(0iAgp@haGPC-O_;TrA9|!h~j)jkntn^j{qdRtOzKk+di5#In z!`A39zlFN3-M=TZbVXUqEM&hIS_=m+SaulVx~V-S8rIQTof9~o8P89txilp7n{y{Z z*Gi8<1U`9Ma(+lY)hoY~vozE`q~n?3U0`Y>KjCJyS6f7g6!Bd zl-G3MgyFU77O^Muh^ zMUZ#+9P2cs)J~LFm4`pS1KRulRpc+?$?czEfq)7;fq)SH(<3-Jdsv(Np~iP+>$+uf zp@&?URXrlm=uhf2%I$hJYLfF-NM=edlGw;)=je?SjAQ^^15J%Q-VsAZ1+({C*O9Z^ zz;4C{>2+x%AMCYk=_hcw?`qN+d$O(i_OL^{V~t4^A($2BKBQjoMFn&kSPXtjziybN zo)+)%eR|&V`OG&Nxs-P34^s4Kft(YKE?UQf8+&8lhmz8hDxFiF?UYVhses=Q>d;?5 zvT&_Skw{8CC^J~#j*I`}3^$faW-*lL&H1} zpGs6+L?b6rze9{wOc+g!z6p&$qYxoH*{SEzyL0Q%SRs+tw!KyKFjoqib>FpUB1f#l zpm3yyYpq5|EL-9p)>$b;H<{J|2~U@ggvqdJ9Z{Ly$I=)y%-T2g zlGZ+@@Kv^4-EK^m>GE66$)LHgQK4U)*T`(Y?O4#@Y1ECWP|fAS=1;h5I_GEj^`KIt zpVG%xG*iB}Xj!+tj?O!d3K>!j9e}Hc+q}EWFA<<~#p2<3>-hex`{r1$N?0KToA=T) z9OG|JwN>_moi?#{b^MUMyyMDTs}$FaSUF|&=gkux3X^j9z4h&EQW=|^2T`>M>ec+y zr*=f7_onUR=`8WgGhdyIZXFxQeJWL7dAS%_@rawFQ|Yg%TzBPNlyezAbHH_P_fpo- z>^twe1NYP`tGgIk$5-UbTViP^g@_-xh+1Nrv)fj=TyJPq1QT1RjxMX9xjZWZVz$cj zew_|zqZu>2PX~x#mE{k5;iH3=c5uM67jVKi&X|~(1CA%gjJ=~0 zNI61bb^1oG=fT;vj<${wNdEJ4!oJv!&tR8(=W;)N);AtEVmr9ApB>!H>;P%T(T6F@ zRohxCG_(*M)^sNEV*wvLzj3DxD-AJ4I%4JE-MWi8#(||VHK>AK8EAc-INabJOq=)R zp}$>ajJq+Q!@Er|vmf$!8Z>JA4jf4|L=0xCLpz2;yE_MnjB@x%imJC1&A=^n^tu5wWBA#xAcAu2ouXk9BxMm+;uMH_O`t4&2KEa1vg zExyp`C8MnF*cuI8;x#k&7JqeQ!frHz^1Ni{&0cU+GVYe84OO=uz_P74s}#}df|gW} ze$Awo#p?Fdy_TUJ$NP{-XM!_shBu4!MU-s3+f@6lSK{0FwSMA$zS+q{H%vM^Ns6~c1}UXe z_KO00wW;X&Dqe&cu$emfHOX#02&t4e5_>D>LLM#e@$um0UABw`98Hg&%NY*wCsme2 z=5|8lQLtKfl)(y10K>#PDaSH|?aw|odzBBLks5d~rg9a!JXAtE4ixb(E{F7o66+=o zD$=Tfmjvq3bwEcm6oxBECMjc=FCh_@FH2)nYsWG1`Y1UEp4G{Sm>6NBq@X|x6Z(2f zSr{0UfJ5~2jrv0LOMa0G<^He|AoQs6+<5qRk)CS}hH@WM1C`f_-qNRZf5p3o(Nw?b zLrY{C_CXDaay~P{V~C8#?NKFDn$M&{KGBb$#2kWJ-_Dshm)HlsHg<+(0+mfGjszv1 zA&Zk<)AJEmWSA;0Ms7z@L>f_sPgU7K;GnE@y4t9xwV@c>qn&?3zYdNf&f$z1#jRKz zpSy03Iy=d&+|k?2#$Rn4W&zi+AFn}r8quFg z8AJXWC>v`FhJOyfIYELIFV0JBm_E`$%---_?7QT&gvPHP@Qe0DW8RHV3{H$-GbqY_$~B6xtSStdwAVyfiL=jY=dh8XAz&4~02Z%7AC4 zf_Y)2B5}n?RrKfeUkF71KeC;0wz!3S{)nbDBNhzVs7MH6qr$9@W1xZ=%S;vZ06_f2 z{AXI205%%YudGRmf~~2Fgy1$Re_f)rW4&dkiW>QdHv9~Lctt@|{#mrY1pO+H(ElxB z20qLOOtj&>DCj+J0EAC081>Im{8!SiQxg@$a}yN_!9RtERljMziX693_~834&Up;^ zyysSzOTE_G?KdkXzLmOEtyjK15M@a}Vm5BNb1 zz}Rs6xqR!7pj&<(%+Qk#^3!lj!d`B_wqkrO-XPv>d13BMg5i|s$5^*32_^(WM?&@0 z{!;-eD~|tmHH#7W1$Tc(eG?jdW-GP0wtsDK2U7zYBz7w`zP7)(zueT?tT^x5acg%s zDq?$kp(uv~N_#u$yk&MwKI2ow0okqUwCoXeVW!`=x$r)iQ?YzB*TR>g5VirSGdmS_ zU}{zpcS$&gHznbumU+Z|a#oorjWR-D>Ly=DSCq~o2u$BH!ZB^juON_M@-}fsQSjtq zbT{0fXzaI5%AIdrP|BQt6LUeSRoYXNmMe3pDE==YZTU0#&pe|mvX=>1Em|i@{~?aj z(b}|4l7ACkdjZ!^FU7-EDO|tE`Y)C?|0e%s{)d=q3GuAe>Oyf61|0Fl2zZ zfGkQ18t}%=ZnZ`?#qwn+%9{*x&iK!8ap1H3&Ab@Lmv#5oWYf$Rr1A@l^2uA2nX0wq zf@`Q+v>C4}u5x|l;y@SKfRi)B1ykjnlf%_|5fsQsXpHj8T2I&H!qStEjd!vt2T+Y` z;M{mBR#f%Jey4Wovh5Z%*Po*oPb%H=xxK`*qY1gzN^heo0`MzHYAZs@`5IciOfsJK zxS~zf%i>-Tb(A{$;m0sDlqT5WROzOR~ z385CSkfX8$VJ%aV!Hc2g+u&<=aXsF*IV<{DbyDToe~VC6-5v4;i=h9B)q7!MW52I@ ztFUDpXp*jGI@>>xR{TRl#hHLjO>0aua+yaEK0SHjjb-dZCw&nzNYa8a8qb8`X0MT--41GA5_c{8` zi4QI}50VIH=$(M^*Mg_Rp~L8eo+bCZbv(?I&rp1=55tlMJ7+pmSI6V$o~VgR2HoaU zfRguuUU>=Ggbsn(CXb~Jl~uP#qPc4BaHhy4xcDMB-*eQ%4wd@r+JsQElZVyXc@q7y zL~XI*DbA-a1rn?q-@0rHYu?BBUjfsg|2t1|m6XNU00si$02n<`|1(c=Gx?t5q5xt4}ud-z-(jl;qFuff=?jX&KPtIF9GsYO@C|JT*Lzi!p8( zFJD<0{P1&Qv`9Y~W`i{lK9UfJGr+d8bih4cJv|zVA|ICRsQ4pcJ0i#M*zo(fTSuxT zZ3RDY%#lhkR?k=qTV7-m$>LT7m)L?tGjM45FU%V)oh!+nt#x}ClJ<_74L{X!3TUlVK)QcXk$EhoUoI6JYl=Jz_`!N(lA7X z(=k~u|;g8U4(V7dAxcoN6$H%tBj@=h%R_;&93BMU-D_DTwLU$26iN$pznn+MhYPM89;Zm|x`Id!NdHY>bmKtWrn;qNL=% zqok^`IwoY{AMHIqE@AN6SWXv)jK*f&$HBZtAxHs(9p!@{iUv-6n@L11SbX(LEZas( z1w~q6Ag2XXkE#B>Y_e!W=Jdi}duLH0ZZ@`91&h9wbVUV%O~d~~hkgg&`9Y-f zo{O2ZGDR!3#uC}&6x@j4x8p-3x*v7@)%;pJ;VaH2-m#;#K-Fa5=WsjiHct7Q^&MlCmj> z()t1{RD=1i!$Snw1~%tZKO3L zb5dXaH=bj$EhQb6xE@b@tf(>svBm3wE?&Xcc9~GIP z01cQT4+xOu|84(z0zvVL(gO^r!CT<(!lR!vi!w!p)_5sCDh4arA#xE|{?O{@NSgOuA|@2*6Mg_r`E*t1MVJHN#=d$VxxgDuI0Cny@1pLoU7 zeFNWo<|= zDFRMx@BEaAvyc=c@mb3$nI9@9*Nj2H2utD5CG1{{_ru;I9gZgG57=&8LtVzw4ya}i zn-wb@u)=IKC76u-L-^D3HFM=21G-0il(VPT!}=`HmX&Bt5~JNaZTAJ!`qxAOE+SzWeQz9Y}CEQ zaG}x|oZ}38?;iVJ#kalPP*i9i%cLt1j$80k%k?Yw6{fde7=)4_DxJ+TFk>aB{1Z^N z6f+ffc_W`IG-cH1^R-3u)|;m$&`>YE6l!=o$ZG!ss;D4q10DP;UrFqmhVEOPT#zeC zMPZCep*a1UT-QbSA7JbN=*<2Sx@<|+ zyu=`p{WYD6JNN3t>7tt;CsSH%80=e4j?me~wdc$AB@<`*b7bED9fd4&dMdu!99&w` zU`+kYjKoNO6qJL!dKeqNzsdh03BD{Q{>LHbXC|YjVJ!`=_!A z3_=S~EdS$VI$|WAkOXKLTL5(v#VQhVQ=E3`>UlWNdKDwUOZOI z1t6;&pj!v%DF4_d0c}m+(Zt$`p6(a@9oSayiCYa11k{%Or)SUK$TNV`tDBvpF+E_2 zaIpc1|6g)Ln>Y+i00ln+WXAa$3pg4>`wRZ3@P9J-p9Se$DiU4;lwJ*Bo%m&|{Cji2 z{EL&Rot?9-o%5fjir;U9(jI|$6@XU&;F10Y1CG1^PD_8>h>5N7|1aFO{KfSP01gFs zdWipm2jl$(H#adfHgWvJ-}iUnvVF2drT-JHMfi`x{|gQMmlQ&vyRrlVHgh1LUpwHb z{bOSw`3r4hVr*eZZ((C-X2M8kZ)^5@0e<&V{+ma7)L$%q_gVg(#qT~|f3wI*{ENju zJ-&X2|88sk8?Ku97yMtA=il+aTc-ZTo8|sT{D19Kzk`3DuKf*;t^5o8+vM$c4!>*o ze{;yG{)@xE^!?xQ|JJMiMgswv*8u_jA1&*5_`gTSf5P`0{sI4MoK%no2eh|e^&AO^ N9}EcSrTN#{{|6I{nbH6N literal 0 HcmV?d00001 diff --git a/BMA.EHR.Retirement.Service/Templates/retire-emp-2.docx b/BMA.EHR.Retirement.Service/Templates/retire-emp-2.docx new file mode 100644 index 0000000000000000000000000000000000000000..3fd290de2884254b3473a9000ebedfaef964d45d GIT binary patch literal 48591 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

adY5^o3vxe#&V3o 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 literal 0 HcmV?d00001 From c035c1340530a9b13c0f616a03bc752b1afce50f Mon Sep 17 00:00:00 2001 From: harid Date: Mon, 20 Apr 2026 14:31:28 +0700 Subject: [PATCH 181/183] check runtime --- .../Services/RetirementReportService.cs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/BMA.EHR.Retirement.Service/Services/RetirementReportService.cs b/BMA.EHR.Retirement.Service/Services/RetirementReportService.cs index b60fad8f..d3253afd 100644 --- a/BMA.EHR.Retirement.Service/Services/RetirementReportService.cs +++ b/BMA.EHR.Retirement.Service/Services/RetirementReportService.cs @@ -205,10 +205,15 @@ namespace BMA.EHR.Retirement.Service.Services // Run LibreOffice inside Docker container var dockerCmd = $"docker exec {container} libreoffice {arguments} --outdir {dockerFilesPath} {dockerFilesPath}/{fileName}"; + + var isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows); + var shell = isWindows ? "cmd.exe" : "/bin/sh"; + var shellArg = isWindows ? "/c" : "-c"; + var psi = new ProcessStartInfo { - FileName = "cmd.exe", - Arguments = $"/c \"{dockerCmd}\"", + FileName = shell, + Arguments = $"{shellArg} \"{dockerCmd}\"", UseShellExecute = false, RedirectStandardOutput = true, RedirectStandardError = true, From 04d0067ee8e3cae4a82f08a6c1ce6f4abded230d Mon Sep 17 00:00:00 2001 From: harid Date: Mon, 20 Apr 2026 16:38:58 +0700 Subject: [PATCH 182/183] =?UTF-8?q?=E0=B8=96=E0=B9=89=E0=B8=B2=E0=B8=A1?= =?UTF-8?q?=E0=B8=B5=E0=B8=A3=E0=B8=B1=E0=B8=81=E0=B8=A9=E0=B8=B2=E0=B8=81?= =?UTF-8?q?=E0=B8=B2=E0=B8=A3=E0=B9=81=E0=B8=81=E0=B9=89=E0=B8=AA=E0=B8=B4?= =?UTF-8?q?=E0=B8=97=E0=B8=98=E0=B8=B4=E0=B9=8C=20BROTHER?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- BMA.EHR.Leave/Controllers/LeaveController.cs | 16 ++++--- .../Controllers/LeaveRequestController.cs | 48 +++++++++++-------- 2 files changed, 36 insertions(+), 28 deletions(-) diff --git a/BMA.EHR.Leave/Controllers/LeaveController.cs b/BMA.EHR.Leave/Controllers/LeaveController.cs index 1bd92859..c77bad95 100644 --- a/BMA.EHR.Leave/Controllers/LeaveController.cs +++ b/BMA.EHR.Leave/Controllers/LeaveController.cs @@ -3246,23 +3246,25 @@ namespace BMA.EHR.Leave.Service.Controllers } else if (actRole == "BROTHER") { - actNodeId = act.child3DnaId != null ? + actNodeId = act.child4DnaId != null ? act.child3DnaId.Value.ToString("D") : - act.child2DnaId != null ? + act.child3DnaId != null ? act.child2DnaId.Value.ToString("D") : - act.child1DnaId != null ? - act.rootDnaId!.Value.ToString("D") : + act.child2DnaId != null ? + act.child1DnaId!.Value.ToString("D") : + act.child1DnaId != null ? + act.rootDnaId.Value.ToString("D") : act.rootDnaId != null ? act.rootDnaId.Value.ToString("D") : ""; actNode = act.child4DnaId != null ? 4 : act.child3DnaId != null ? - 4 : - act.child2DnaId != null ? 3 : - act.child1DnaId != null ? + act.child2DnaId != null ? 2 : + act.child1DnaId != null ? + 1 : act.rootDnaId != null ? 0 : null; diff --git a/BMA.EHR.Leave/Controllers/LeaveRequestController.cs b/BMA.EHR.Leave/Controllers/LeaveRequestController.cs index bbf49dc8..fca7c27a 100644 --- a/BMA.EHR.Leave/Controllers/LeaveRequestController.cs +++ b/BMA.EHR.Leave/Controllers/LeaveRequestController.cs @@ -1433,23 +1433,25 @@ namespace BMA.EHR.Leave.Service.Controllers } else if (actRole == "BROTHER") { - actNodeId = act.child3DnaId != null ? + actNodeId = act.child4DnaId != null ? act.child3DnaId.Value.ToString("D") : - act.child2DnaId != null ? + act.child3DnaId != null ? act.child2DnaId.Value.ToString("D") : - act.child1DnaId != null ? - act.rootDnaId!.Value.ToString("D") : + act.child2DnaId != null ? + act.child1DnaId!.Value.ToString("D") : + act.child1DnaId != null ? + act.rootDnaId.Value.ToString("D") : act.rootDnaId != null ? act.rootDnaId.Value.ToString("D") : ""; actNode = act.child4DnaId != null ? 4 : act.child3DnaId != null ? - 4 : - act.child2DnaId != null ? 3 : - act.child1DnaId != null ? + act.child2DnaId != null ? 2 : + act.child1DnaId != null ? + 1 : act.rootDnaId != null ? 0 : null; @@ -1911,23 +1913,25 @@ namespace BMA.EHR.Leave.Service.Controllers } else if (actRole == "BROTHER") { - actNodeId = act.child3DnaId != null ? + actNodeId = act.child4DnaId != null ? act.child3DnaId.Value.ToString("D") : - act.child2DnaId != null ? + act.child3DnaId != null ? act.child2DnaId.Value.ToString("D") : - act.child1DnaId != null ? - act.rootDnaId!.Value.ToString("D") : + act.child2DnaId != null ? + act.child1DnaId!.Value.ToString("D") : + act.child1DnaId != null ? + act.rootDnaId.Value.ToString("D") : act.rootDnaId != null ? act.rootDnaId.Value.ToString("D") : ""; actNode = act.child4DnaId != null ? 4 : act.child3DnaId != null ? - 4 : - act.child2DnaId != null ? 3 : - act.child1DnaId != null ? + act.child2DnaId != null ? 2 : + act.child1DnaId != null ? + 1 : act.rootDnaId != null ? 0 : null; @@ -2214,23 +2218,25 @@ namespace BMA.EHR.Leave.Service.Controllers } else if (actRole == "BROTHER") { - actNodeId = act.child3DnaId != null ? + actNodeId = act.child4DnaId != null ? act.child3DnaId.Value.ToString("D") : - act.child2DnaId != null ? + act.child3DnaId != null ? act.child2DnaId.Value.ToString("D") : - act.child1DnaId != null ? - act.rootDnaId!.Value.ToString("D") : + act.child2DnaId != null ? + act.child1DnaId!.Value.ToString("D") : + act.child1DnaId != null ? + act.rootDnaId.Value.ToString("D") : act.rootDnaId != null ? act.rootDnaId.Value.ToString("D") : ""; actNode = act.child4DnaId != null ? 4 : act.child3DnaId != null ? - 4 : - act.child2DnaId != null ? 3 : - act.child1DnaId != null ? + act.child2DnaId != null ? 2 : + act.child1DnaId != null ? + 1 : act.rootDnaId != null ? 0 : null; From b3298e88c6e2bff72cfd92debce1be6ac6bc3d3d Mon Sep 17 00:00:00 2001 From: harid Date: Tue, 21 Apr 2026 10:34:21 +0700 Subject: [PATCH 183/183] fix path separator --- .../Services/RetirementReportService.cs | 117 +++++++----------- 1 file changed, 47 insertions(+), 70 deletions(-) diff --git a/BMA.EHR.Retirement.Service/Services/RetirementReportService.cs b/BMA.EHR.Retirement.Service/Services/RetirementReportService.cs index d3253afd..82bf2b27 100644 --- a/BMA.EHR.Retirement.Service/Services/RetirementReportService.cs +++ b/BMA.EHR.Retirement.Service/Services/RetirementReportService.cs @@ -170,7 +170,7 @@ namespace BMA.EHR.Retirement.Service.Services } else { - // PROD: Disabled local LibreOffice conversion + // // PROD: Disabled local LibreOffice conversion // await ConvertToPdfLocallyAsync(docxPath, pdfPath, timeout); throw new NotSupportedException("LibreOffice conversion is disabled."); } @@ -184,89 +184,66 @@ namespace BMA.EHR.Retirement.Service.Services private async Task ConvertToPdfViaDockerAsync(string docxPath, string pdfPath, int timeout) { - var container = _configuration["LibreOffice:DockerContainer"] ?? "libreoffice"; - var workingDir = _configuration["LibreOffice:WorkingDirectory"] ?? "/app/libreoffice/files"; - var dockerFilesPath = _configuration["LibreOffice:DockerFilesPath"] ?? "/files"; - var arguments = _configuration["LibreOffice:Arguments"] ?? "--headless --convert-to pdf --nologo --norestore"; - - // Ensure working directory exists - if (!Directory.Exists(workingDir)) - { - Directory.CreateDirectory(workingDir); - } - - // Copy file to shared directory + var inputDir = _configuration["LibreOffice:InputDirectory"] ?? "/app/libreoffice/input"; + var outputDir = _configuration["LibreOffice:OutputDirectory"] ?? "/app/libreoffice/output"; var fileName = Path.GetFileName(docxPath); - var sharedDocxPath = Path.Combine(workingDir, fileName); - var sharedPdfName = Path.ChangeExtension(fileName, ".pdf"); - var sharedPdfPath = Path.Combine(workingDir, sharedPdfName); + var pdfName = Path.ChangeExtension(fileName, ".pdf"); - await File.WriteAllBytesAsync(sharedDocxPath, await File.ReadAllBytesAsync(docxPath)); + // Ensure directories exist + Directory.CreateDirectory(inputDir); + Directory.CreateDirectory(outputDir); - // Run LibreOffice inside Docker container - var dockerCmd = $"docker exec {container} libreoffice {arguments} --outdir {dockerFilesPath} {dockerFilesPath}/{fileName}"; - - var isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows); - var shell = isWindows ? "cmd.exe" : "/bin/sh"; - var shellArg = isWindows ? "/c" : "-c"; + // Copy file to input folder (LibreOffice watcher will pick it up) + var inputPath = Path.Combine(inputDir, fileName).Replace('\\', '/'); + var outputPath = Path.Combine(outputDir, pdfName).Replace('\\', '/'); - var psi = new ProcessStartInfo + _logger.LogInformation("📤 Sending file to LibreOffice: {FileName}", fileName); + await File.WriteAllBytesAsync(inputPath, await File.ReadAllBytesAsync(docxPath)); + + // Wait for LibreOffice to convert (file watcher handles it) + var stopwatch = System.Diagnostics.Stopwatch.StartNew(); + var pollInterval = TimeSpan.FromMilliseconds(500); + + while (stopwatch.ElapsedMilliseconds < timeout) { - FileName = shell, - Arguments = $"{shellArg} \"{dockerCmd}\"", - UseShellExecute = false, - RedirectStandardOutput = true, - RedirectStandardError = true, - CreateNoWindow = true - }; + if (File.Exists(outputPath)) + { + _logger.LogInformation("✅ PDF received: {PdfName} (took {ElapsedMs}ms)", pdfName, stopwatch.ElapsedMilliseconds); - using var process = Process.Start(psi); - var exited = process.WaitForExit(timeout); + await File.WriteAllBytesAsync(pdfPath, await File.ReadAllBytesAsync(outputPath)); - if (!exited) - { - process.Kill(entireProcessTree: true); - throw new TimeoutException($"LibreOffice Docker conversion timed out after {timeout}ms"); + // Cleanup + try + { + if (File.Exists(outputPath)) File.Delete(outputPath); + _logger.LogDebug("🗑️ Cleaned up output file: {PdfName}", pdfName); + } + catch (Exception ex) + { + _logger.LogWarning(ex, "Failed to cleanup output file"); + } + + return; + } + + await Task.Delay(pollInterval); } - if (process.ExitCode != 0) - { - var error = await process.StandardError.ReadToEndAsync(); - throw new Exception($"LibreOffice Docker conversion failed: {error}"); - } - - // Copy result back - if (!File.Exists(sharedPdfPath)) - { - throw new FileNotFoundException($"PDF not generated in shared directory: {sharedPdfPath}"); - } - - await File.WriteAllBytesAsync(pdfPath, await File.ReadAllBytesAsync(sharedPdfPath)); - - // Cleanup shared files - try - { - if (File.Exists(sharedDocxPath)) File.Delete(sharedDocxPath); - if (File.Exists(sharedPdfPath)) File.Delete(sharedPdfPath); - } - catch (Exception ex) - { - _logger.LogWarning(ex, "Failed to cleanup shared files"); - } + throw new TimeoutException($"LibreOffice conversion timed out after {timeout}ms. File not found: {outputPath}"); } - // PROD: Disabled local LibreOffice conversion + // // PROD: Disabled local LibreOffice conversion // private async Task ConvertToPdfLocallyAsync(string docxPath, string pdfPath, int timeout) // { // var libreOfficePath = _configuration["LibreOffice:Path"] ?? GetDefaultLibreOfficePath(); // var arguments = _configuration["LibreOffice:Arguments"] ?? "--headless --convert-to pdf --nologo --norestore"; // var outputDir = Path.GetDirectoryName(pdfPath); - // + // if (string.IsNullOrEmpty(outputDir)) // { // throw new DirectoryNotFoundException("Output directory cannot be determined"); // } - // + // var psi = new ProcessStartInfo // { // FileName = libreOfficePath, @@ -276,16 +253,16 @@ namespace BMA.EHR.Retirement.Service.Services // RedirectStandardError = true, // CreateNoWindow = true // }; - // + // using var process = Process.Start(psi); // var exited = process.WaitForExit(timeout); - // + // if (!exited) // { // process.Kill(entireProcessTree: true); // throw new TimeoutException($"LibreOffice conversion timed out after {timeout}ms"); // } - // + // if (process.ExitCode != 0) // { // var error = await process.StandardError.ReadToEndAsync(); @@ -293,7 +270,7 @@ namespace BMA.EHR.Retirement.Service.Services // } // } - // PROD: Disabled local LibreOffice path detection + // // PROD: Disabled local LibreOffice path detection // private static string GetDefaultLibreOfficePath() // { // if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) @@ -304,11 +281,11 @@ namespace BMA.EHR.Retirement.Service.Services // @"C:\Program Files (x86)\LibreOffice\program\soffice.exe", // @"C:\Program Files\LibreOffice\program\soffice.com" // }; - // + // return possiblePaths.FirstOrDefault(File.Exists) // ?? throw new FileNotFoundException("LibreOffice not found. Please install LibreOffice or configure the path in appsettings.json"); // } - // + // // Linux/Docker: use default path // return "libreoffice"; // }