diff --git a/.forgejo/workflows/build.yml b/.forgejo/workflows/build.yml deleted file mode 100644 index 875cdce..0000000 --- a/.forgejo/workflows/build.yml +++ /dev/null @@ -1,50 +0,0 @@ -# /.forgejo/workflows/build.yml -name: Build - -# on: -# push: -# tags: -# - "v[0-9]+.[0-9]+.[0-9]+" -# - "v[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 }}/${{ vars.CONTAINER_IMAGE_NAME }} - 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/v//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: ./docker/Dockerfile - tags: ${{ env.CONTAINER_IMAGE_NAME }}:latest,${{ env.CONTAINER_IMAGE_NAME }}:${{ env.IMAGE_VERSION }} - push: true \ No newline at end of file diff --git a/.forgejo/workflows/ci-cd.yml b/.forgejo/workflows/ci-cd.yml deleted file mode 100644 index 3f97003..0000000 --- a/.forgejo/workflows/ci-cd.yml +++ /dev/null @@ -1,83 +0,0 @@ -# /.forgejo/workflows/build.yml -name: Build & Deploy on Dev - -on: - push: - tags: - - "v[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 }}/${{ vars.CONTAINER_IMAGE_NAME }} - IMAGE_VERSION: latest - 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.ref_type }}" == "tag" ]; then - echo "IMAGE_VERSION=${{ github.ref_name }}" >> $GITHUB_ENV - else - echo "IMAGE_VERSION=latest" >> $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: ./docker/Dockerfile - tags: ${{ env.CONTAINER_IMAGE_NAME }}:latest,${{ env.CONTAINER_IMAGE_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_SALARY "${{ env.IMAGE_VERSION }}" - ./deploy.sh hrms-api-salary - - - 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 }}\`\\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/deploy.yml b/.forgejo/workflows/deploy.yml deleted file mode 100644 index 7c57397..0000000 --- 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_SALARY "${{ inputs.version }}" - ./deploy.sh hrms-api-salary "${{ inputs.version }}" diff --git a/.gitignore b/.gitignore index 55e347d..e8ab805 100644 --- a/.gitignore +++ b/.gitignore @@ -131,5 +131,3 @@ dist .yarn/build-state.yml .yarn/install-state.gz .pnp.* - -.claude \ No newline at end of file diff --git a/src/controllers/FileController.ts b/src/controllers/FileController.ts index 3359dcc..0a1dd32 100644 --- a/src/controllers/FileController.ts +++ b/src/controllers/FileController.ts @@ -22,7 +22,7 @@ import { listFile, updateFile, } from "../services/edm"; -import { s3DeleteFile, s3DownloadFile, s3ListFile, s3UploadFile, truncateFileName } from "../services/minio"; +import { s3DeleteFile, s3DownloadFile, s3ListFile, s3UploadFile } from "../services/minio"; @Route("api/v1/salary/file/{name}/{group}") @Security("bearerAuth") @@ -257,7 +257,7 @@ export class FileController extends Controller { const map = fileList.map(async ({ fileName, ...props }) => [ fileName, - await createFile(path, truncateFileName(fileName), props), + await createFile(path, fileName, props), ]); const result = await Promise.all(map).catch((e) => @@ -607,7 +607,7 @@ export class SubFileController extends Controller { const map = fileList.map(async ({ fileName, ...props }) => [ fileName, - await createFile(path, truncateFileName(fileName), props), + await createFile(path, fileName, props), ]); const result = await Promise.all(map).catch((e) => diff --git a/src/controllers/SalaryPeriodController.ts b/src/controllers/SalaryPeriodController.ts index 12276e7..57b3a30 100644 --- a/src/controllers/SalaryPeriodController.ts +++ b/src/controllers/SalaryPeriodController.ts @@ -72,68 +72,68 @@ export class SalaryPeriodController extends Controller { const data = { group1id: salaryPeriod.salaryOrgs && - salaryPeriod.salaryOrgs.find( - (x) => - x.group == "GROUP1" && - x.rootId == body.rootId && - x.snapshot == body.snapshot.toLocaleUpperCase(), - ) == null + salaryPeriod.salaryOrgs.find( + (x) => + x.group == "GROUP1" && + x.rootId == body.rootId && + x.snapshot == body.snapshot.toLocaleUpperCase(), + ) == null ? null : salaryPeriod.salaryOrgs && - salaryPeriod.salaryOrgs.find( - (x) => - x.group == "GROUP1" && - x.rootId == body.rootId && - x.snapshot == body.snapshot.toLocaleUpperCase(), - )?.id, + salaryPeriod.salaryOrgs.find( + (x) => + x.group == "GROUP1" && + x.rootId == body.rootId && + x.snapshot == body.snapshot.toLocaleUpperCase(), + )?.id, group1IsClose: salaryPeriod.salaryOrgs && - salaryPeriod.salaryOrgs.find( - (x) => - x.group == "GROUP1" && - x.rootId == body.rootId && - x.snapshot == body.snapshot.toLocaleUpperCase(), - ) == null + salaryPeriod.salaryOrgs.find( + (x) => + x.group == "GROUP1" && + x.rootId == body.rootId && + x.snapshot == body.snapshot.toLocaleUpperCase(), + ) == null ? null : salaryPeriod.salaryOrgs && - salaryPeriod.salaryOrgs.find( - (x) => - x.group == "GROUP1" && - x.rootId == body.rootId && - x.snapshot == body.snapshot.toLocaleUpperCase(), - )?.isClose, + salaryPeriod.salaryOrgs.find( + (x) => + x.group == "GROUP1" && + x.rootId == body.rootId && + x.snapshot == body.snapshot.toLocaleUpperCase(), + )?.isClose, group2id: salaryPeriod.salaryOrgs && - salaryPeriod.salaryOrgs.find( - (x) => - x.group == "GROUP2" && - x.rootId == body.rootId && - x.snapshot == body.snapshot.toLocaleUpperCase(), - ) == null + salaryPeriod.salaryOrgs.find( + (x) => + x.group == "GROUP2" && + x.rootId == body.rootId && + x.snapshot == body.snapshot.toLocaleUpperCase(), + ) == null ? null : salaryPeriod.salaryOrgs && - salaryPeriod.salaryOrgs.find( - (x) => - x.group == "GROUP2" && - x.rootId == body.rootId && - x.snapshot == body.snapshot.toLocaleUpperCase(), - )?.id, + salaryPeriod.salaryOrgs.find( + (x) => + x.group == "GROUP2" && + x.rootId == body.rootId && + x.snapshot == body.snapshot.toLocaleUpperCase(), + )?.id, group2IsClose: salaryPeriod.salaryOrgs && - salaryPeriod.salaryOrgs.find( - (x) => - x.group == "GROUP2" && - x.rootId == body.rootId && - x.snapshot == body.snapshot.toLocaleUpperCase(), - ) == null + salaryPeriod.salaryOrgs.find( + (x) => + x.group == "GROUP2" && + x.rootId == body.rootId && + x.snapshot == body.snapshot.toLocaleUpperCase(), + ) == null ? null : salaryPeriod.salaryOrgs && - salaryPeriod.salaryOrgs.find( - (x) => - x.group == "GROUP2" && - x.rootId == body.rootId && - x.snapshot == body.snapshot.toLocaleUpperCase(), - )?.isClose, + salaryPeriod.salaryOrgs.find( + (x) => + x.group == "GROUP2" && + x.rootId == body.rootId && + x.snapshot == body.snapshot.toLocaleUpperCase(), + )?.isClose, effectiveDate: salaryPeriod.effectiveDate, period: salaryPeriod.period, }; @@ -192,8 +192,8 @@ export class SalaryPeriodController extends Controller { _salaryOrgGROUP2 == null ? 0 : _salaryOrgGROUP2.salaryProfiles.reduce((accumulator, object) => { - return accumulator + object.amountSpecial; - }, 0); + return accumulator + object.amountSpecial; + }, 0); const data = { org: item.root, total: item.total + (_salaryOrgGROUP2 == null ? 0 : _salaryOrgGROUP2.total), @@ -470,7 +470,7 @@ export class SalaryPeriodController extends Controller { const Level = await this.posLevelRepository.findOne({ where: { posTypeId: Type.id, - posLevelName: salaryProfile.posLevel?.split("/")[0], + posLevelName: salaryProfile.posLevel, }, }); if (!Level) { @@ -489,7 +489,7 @@ export class SalaryPeriodController extends Controller { throw new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบระดับตำแหน่ง"); } let type = salaryProfile.type; - //SalaryRanks + //SalaryRank let salaryRanks: any = null; if (salaryProfile.amount != null) { salaryRanks = await this.salaryRankRepository.findOne({ @@ -583,9 +583,9 @@ export class SalaryPeriodController extends Controller { : salaryRanks.salaryHalfSpecial; salaryProfile.amountUse = salaryRanks == null || - salaryProfile == null || - salaryRanks.salaryHalf == null || - salaryProfile.amount == null + salaryProfile == null || + salaryRanks.salaryHalf == null || + salaryProfile.amount == null ? 0 : salaryRanks.salaryHalf - salaryProfile.amount; salaryProfile.positionSalaryAmount = @@ -598,9 +598,9 @@ export class SalaryPeriodController extends Controller { : salaryRanks.salaryFullSpecial; salaryProfile.amountUse = salaryRanks == null || - salaryProfile == null || - salaryRanks.salaryFull == null || - salaryProfile.amount == null + salaryProfile == null || + salaryRanks.salaryFull == null || + salaryProfile.amount == null ? 0 : salaryRanks.salaryFull - salaryProfile.amount; salaryProfile.positionSalaryAmount = @@ -613,9 +613,9 @@ export class SalaryPeriodController extends Controller { : salaryRanks.salaryFullHalfSpecial; salaryProfile.amountUse = salaryRanks == null || - salaryProfile == null || - salaryRanks.salaryFullHalf == null || - salaryProfile.amount == null + salaryProfile == null || + salaryRanks.salaryFullHalf == null || + salaryProfile.amount == null ? 0 : salaryRanks.salaryFullHalf - salaryProfile.amount; salaryProfile.positionSalaryAmount = @@ -935,7 +935,7 @@ export class SalaryPeriodController extends Controller { const Level = await this.posLevelRepository.findOne({ where: { posTypeId: Type.id, - posLevelName: salaryProfile.posLevel?.split("/")[0], + posLevelName: salaryProfile.posLevel, }, }); if (!Level) { @@ -957,7 +957,7 @@ export class SalaryPeriodController extends Controller { let _null: any = null; salaryProfile.remark = body.remark == null ? _null : body.remark; let type = salaryProfile.type; - //SalaryRanks + //SalaryRank let salaryRanks: any = null; if (salaryProfile.amount != null) { salaryRanks = await this.salaryRankRepository.findOne({ @@ -1051,9 +1051,9 @@ export class SalaryPeriodController extends Controller { : salaryRanks.salaryHalfSpecial; salaryProfile.amountUse = salaryRanks == null || - salaryProfile == null || - salaryRanks.salaryHalf == null || - salaryProfile.amount == null + salaryProfile == null || + salaryRanks.salaryHalf == null || + salaryProfile.amount == null ? 0 : salaryRanks.salaryHalf - salaryProfile.amount; salaryProfile.positionSalaryAmount = @@ -1066,9 +1066,9 @@ export class SalaryPeriodController extends Controller { : salaryRanks.salaryFullSpecial; salaryProfile.amountUse = salaryRanks == null || - salaryProfile == null || - salaryRanks.salaryFull == null || - salaryProfile.amount == null + salaryProfile == null || + salaryRanks.salaryFull == null || + salaryProfile.amount == null ? 0 : salaryRanks.salaryFull - salaryProfile.amount; salaryProfile.positionSalaryAmount = @@ -1081,9 +1081,9 @@ export class SalaryPeriodController extends Controller { : salaryRanks.salaryFullHalfSpecial; salaryProfile.amountUse = salaryRanks == null || - salaryProfile == null || - salaryRanks.salaryFullHalf == null || - salaryProfile.amount == null + salaryProfile == null || + salaryRanks.salaryFullHalf == null || + salaryProfile.amount == null ? 0 : salaryRanks.salaryFullHalf - salaryProfile.amount; salaryProfile.positionSalaryAmount = @@ -1200,7 +1200,6 @@ export class SalaryPeriodController extends Controller { return new HttpSuccess(); } - //OLD CHANGE TYPE-MULTI /** * API แก้ไขขั้น * @@ -1209,8 +1208,8 @@ export class SalaryPeriodController extends Controller { * @param {string} id profile Id * @param {string} type ประเภทการเลื่อน NONE->ไม่ได้เลื่อน HAFT->ครึ่งขั้น FULL->1ขั้น FULLHAFT->1.5ขั้น */ - @Post("oldchange/type-multi") - async oldchangeTypeMulti( + @Post("change/type-multi") + async changeTypeMulti( @Body() body: { profileId: string[]; type: string; isReserve: boolean; remark?: string | null }, @Request() req: RequestWithUser, ) { @@ -1272,7 +1271,7 @@ export class SalaryPeriodController extends Controller { const Level = await this.posLevelRepository.findOne({ where: { posTypeId: Type.id, - posLevelName: salaryProfile.posLevel?.split("/")[0], + posLevelName: salaryProfile.posLevel, }, }); if (!Level) { @@ -1294,7 +1293,7 @@ export class SalaryPeriodController extends Controller { let _null: any = null; salaryProfile.remark = body.remark == null ? _null : body.remark; let type = salaryProfile.type; - //SalaryRanks + //SalaryRank let salaryRanks: any = null; if (salaryProfile.amount != null) { salaryRanks = await this.salaryRankRepository.findOne({ @@ -1392,9 +1391,9 @@ export class SalaryPeriodController extends Controller { : salaryRanks.salaryHalfSpecial; salaryProfile.amountUse = salaryRanks == null || - salaryProfile == null || - salaryRanks.salaryHalf == null || - salaryProfile.amount == null + salaryProfile == null || + salaryRanks.salaryHalf == null || + salaryProfile.amount == null ? 0 : salaryRanks.salaryHalf - salaryProfile.amount; salaryProfile.positionSalaryAmount = @@ -1407,9 +1406,9 @@ export class SalaryPeriodController extends Controller { : salaryRanks.salaryFullSpecial; salaryProfile.amountUse = salaryRanks == null || - salaryProfile == null || - salaryRanks.salaryFull == null || - salaryProfile.amount == null + salaryProfile == null || + salaryRanks.salaryFull == null || + salaryProfile.amount == null ? 0 : salaryRanks.salaryFull - salaryProfile.amount; salaryProfile.positionSalaryAmount = @@ -1422,9 +1421,9 @@ export class SalaryPeriodController extends Controller { : salaryRanks.salaryFullHalfSpecial; salaryProfile.amountUse = salaryRanks == null || - salaryProfile == null || - salaryRanks.salaryFullHalf == null || - salaryProfile.amount == null + salaryProfile == null || + salaryRanks.salaryFullHalf == null || + salaryProfile.amount == null ? 0 : salaryRanks.salaryFullHalf - salaryProfile.amount; salaryProfile.positionSalaryAmount = @@ -1544,653 +1543,6 @@ export class SalaryPeriodController extends Controller { return new HttpSuccess(); } - // //NEW CHANGE TYPE-MULTI - // @Post("newchange/type-multi") - // async newchangeTypeMulti( - // @Body() body: { profileId: string[]; type: string; isReserve: boolean; remark?: string | null }, - // @Request() req: RequestWithUser, - // ) { - // await new permission().PermissionCreate(req, "SYS_SALARY_OFFICER"); - - // // ----------------------------- - // // 1) ดึง salaryProfiles ทีเดียว - // // ----------------------------- - // const salaryProfiles = await this.salaryProfileRepository.find({ - // where: { id: In(body.profileId) }, - // relations: ["salaryOrg", "salaryOrg.salaryPeriod"], - // }); - - // if (!salaryProfiles.length) { - // throw new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบข้อมูล salaryProfile"); - // } - - // // ----------------------------- - // // 2) โหลด PosType, PosLevel, Salary, SalaryRanks ทั้งหมดทีเดียว - // // ----------------------------- - // const posTypes = await this.posTypeRepository.find(); - // const posLevels = await this.posLevelRepository.find(); - // const salaries = await this.salaryRepository.find({ where: { isActive: true } }); - // const salaryRanks = await this.salaryRankRepository.find(); - - // // Map lookup - // const posTypeMap = new Map(posTypes.map((x) => [x.posTypeName, x])); - // const posLevelMap = new Map(posLevels.map((x) => [`${x.posTypeId}|${x.posLevelName}`, x])); - // const salaryMap = new Map( - // salaries.map((x) => [`${x.posTypeId}|${x.posLevelId}|${x.isSpecial ? 1 : 0}`, x]), - // ); - // const ranksBySalaryId = salaryRanks.reduce( - // (acc, r: any) => { - // if (!acc[r.salaryId]) acc[r.salaryId] = []; - // acc[r.salaryId].push(r); - // return acc; - // }, - // {} as Record, - // ); - - // const profilesToSave: SalaryProfile[] = []; - // const orgNeedRecalc = new Set(); - - // for (const profile of salaryProfiles) { - // const bodyType = body.type?.toUpperCase() ?? profile.type; - - // // --- ตรวจ FULLHAFT → หา APR snapshot2 --- - // if (bodyType === "FULLHAFT" && profile.salaryOrg.salaryPeriod.period === "OCT") { - // const checkPrev = await this.salaryProfileRepository.findOne({ - // relations: ["salaryOrg", "salaryOrg.salaryPeriod"], - // where: { - // citizenId: profile.citizenId, - // salaryOrg: { - // salaryPeriod: { period: "APR", year: profile.salaryOrg.salaryPeriod.year }, - // snapshot: "SNAP2", - // }, - // type: "FULL", - // }, - // }); - - // if (checkPrev) { - // throw new HttpError(404, "ไม่สามารถเลื่อนขั้นเกิน 2 ครั้งในปีเดียวกันได้"); - // } - // } - - // // --- apply posType/posLevel/salary --- - // const posType = posTypeMap.get(profile.posType); - // if (!posType) throw new HttpError(404, "ไม่พบประเภทตำแหน่ง"); - - // const posLevel = posLevelMap.get(`${posType.id}|${profile.posLevel}`); - // if (!posLevel) throw new HttpError(404, "ไม่พบระดับตำแหน่ง"); - - // const salaryBase = salaryMap.get( - // `${posLevel.posTypeId}|${posLevel.id}|${profile.isSpecial ? 1 : 0}`, - // ); - // if (!salaryBase) throw new HttpError(404, "ไม่พบระดับเงินเดือน"); - - // const salaryId = Number(salaryBase.id); - // const ranks = ranksBySalaryId[salaryId] ?? []; - - // // --- หา rank ตาม amount ก่อน dynamic type adjustment --- - // let rank: SalaryRanks | null = null; - // if (profile.amount != null) { - // const amount = profile.amount; - - // const possible = ranks - // .filter((r: any) => r.salary >= amount && !r.isNext) - // .sort((a: any, b: any) => a.salary - b.salary); - // rank = possible[0] ?? null; - - // if (!rank) { - // const next = ranks - // .filter((r: any) => r.salary > amount && r.isNext) - // .sort((a: any, b: any) => a.salary - b.salary); - // rank = next[0] ?? null; - // } - // } - - // // --- คำนวณเงินเดือนตาม rank เดิมก่อน dynamic type adjustment --- - // const calc = (sp: keyof SalaryRanks, next: keyof SalaryRanks) => ({ - // amountSpecial: rank?.[sp] ?? 0, - // amountUse: - // profile.amount != null && rank?.[next] != null - // ? Number(rank[next]) - Number(profile.amount) - // : 0, - // positionSalaryAmount: rank?.[next] ?? 0, - // isNext: rank?.isNext ?? 0, - // }); - - // // --- FULLHAFT dynamic type adjustment หลังคำนวณเงินเดือน --- - // let finalType = bodyType; - // if (bodyType === "FULLHAFT" && rank) { - // const halfSpecial = rank.salaryHalfSpecial ?? 0; - // const fullSpecial = rank.salaryFullSpecial ?? 0; - // const fullHalfSpecial = rank.salaryFullHalfSpecial ?? 0; - - // if (fullHalfSpecial > 0) { - // if (fullSpecial === 0) finalType = "HAFT"; - // else if (halfSpecial === 0) finalType = "FULL"; - // else finalType = "FULLHAFT"; - // } - // } - - // profile.type = finalType; - // profile.isReserve = finalType === "FULL" ? body.isReserve : false; - // profile.remark = body.remark ?? ""; - - // if (finalType === "NONE") { - // profile.amountSpecial = 0; - // profile.amountUse = 0; - // profile.positionSalaryAmount = profile.amount ?? 0; - // } else if (finalType === "PENDING") { - // profile.amountSpecial = 0; - // profile.amountUse = 0; - // profile.positionSalaryAmount = 0; - // } else if (finalType === "HAFT") { - // Object.assign(profile, calc("salaryHalfSpecial", "salaryHalf")); - // } else if (finalType === "FULL") { - // Object.assign(profile, calc("salaryFullSpecial", "salaryFull")); - // } else if (finalType === "FULLHAFT") { - // Object.assign(profile, calc("salaryFullHalfSpecial", "salaryFullHalf")); - // } - - // profile.lastUpdateUserId = req.user.sub; - // profile.lastUpdateFullName = req.user.name; - // profile.lastUpdatedAt = new Date(); - - // // --- log diff --- - // const before = structuredClone(profile); - // profilesToSave.push(profile); - // setLogDataDiff(req, { before, after: profile }); - - // orgNeedRecalc.add(profile.salaryOrg.id); - // } - - // // ----------------------------- - // // 4) Save batch - // // ----------------------------- - // await this.salaryProfileRepository.save(profilesToSave); - - // // ----------------------------- - // // 5) Recalculate SalaryOrg - // // ----------------------------- - // for (const orgId of orgNeedRecalc) { - // const org = await this.salaryOrgRepository.findOne({ - // where: { id: orgId }, - // relations: ["salaryProfiles", "salaryPeriod"], - // }); - // if (!org) continue; - - // const beforeOrg = structuredClone(org); - - // // SNAP1 / APR - // if (org.snapshot === "SNAP1" && org.salaryPeriod.period === "APR") { - // const countFull = org.salaryProfiles.filter((p) => p.type === "FULL").length; - // org.total = org.salaryProfiles.length; - // org.fifteenPercent = Math.floor(org.total * 0.15); - // org.quantityUsed = countFull; - // org.remainQuota = org.fifteenPercent - countFull; - // } - - // // SNAP1 / OCT - // if (org.snapshot === "SNAP1" && org.salaryPeriod.period === "OCT") { - // const total = org.salaryProfiles.reduce((sum, p) => sum + (p.amount ?? 0), 0); - // org.currentAmount = total; - // org.sixPercentAmount = total * 0.06; - - // const useAmount = org.salaryProfiles - // .filter((p) => ["HAFT", "FULL", "FULLHAFT"].includes(p.type)) - // .reduce((s, p) => s + (p.amountUse ?? 0), 0); - - // org.useAmount = useAmount; - // org.remainingAmount = org.sixPercentAmount - useAmount; - - // // --- SNAP2 APR recalc --- - // const salaryPeriodAPROld = await this.salaryPeriodRepository.findOne({ - // where: { period: "APR", year: org.salaryPeriod.year }, - // }); - // if (salaryPeriodAPROld) { - // const orgSnap2Old: any = await this.salaryOrgRepository.findOne({ - // where: { - // salaryPeriodId: salaryPeriodAPROld.id, - // rootId: org.rootId, - // group: org.group, - // snapshot: "SNAP2", - // }, - // relations: ["salaryProfiles"], - // }); - // if (orgSnap2Old) { - // const spent = orgSnap2Old.salaryProfiles.reduce( - // (sum: number, p: any) => sum + (p.amountUse ?? 0), - // 0, - // ); - // org.spentAmount = spent; - // org.remainingAmount = org.sixPercentAmount - useAmount - spent; - // } - // } - // } - - // org.lastUpdateUserId = req.user.sub; - // org.lastUpdateFullName = req.user.name; - // org.lastUpdatedAt = new Date(); - - // await this.salaryOrgRepository.save(org); - // setLogDataDiff(req, { before: beforeOrg, after: org }); - // } - - // return new HttpSuccess(); - // } - - private chunkArray(arr: T[], size: number): T[][] { - const result: T[][] = []; - for (let i = 0; i < arr.length; i += size) { - result.push(arr.slice(i, i + size)); - } - return result; - } - - private async recalculateSalaryOrg( - orgId: string, - req: RequestWithUser, - ) { - const salaryOrg = await this.salaryOrgRepository.findOne({ - where: { id: orgId }, - relations: ["salaryProfiles", "salaryPeriod"], - }); - - if (!salaryOrg) return; - - // ===== เฉพาะ SNAP1 เท่านั้น (ตาม original) ===== - if (salaryOrg.snapshot !== "SNAP1") return; - - // ===================== APR ===================== - if (salaryOrg.salaryPeriod.period === "APR") { - const amountFullType = await this.salaryProfileRepository.count({ - where: { - salaryOrgId: salaryOrg.id, - type: "FULL", - }, - }); - - salaryOrg.total = salaryOrg.salaryProfiles.length; - salaryOrg.fifteenPercent = Math.floor( - (salaryOrg.salaryProfiles.length * 15) / 100, - ); - salaryOrg.fifteenPoint = - (salaryOrg.salaryProfiles.length * 15) % 100; - salaryOrg.quantityUsed = amountFullType; - - const calRemainQuota = - salaryOrg.fifteenPercent - amountFullType; - salaryOrg.remainQuota = calRemainQuota; - - salaryOrg.lastUpdateUserId = req.user.sub; - salaryOrg.lastUpdateFullName = req.user.name; - salaryOrg.lastUpdatedAt = new Date(); - - await this.salaryOrgRepository.save(salaryOrg, { data: req }); - return; - } - - // ===================== OCT ===================== - if (salaryOrg.salaryPeriod.period === "OCT") { - // ===== currentAmount / total ===== - const totalProfileAmount = Extension.sumObjectValues( - salaryOrg.salaryProfiles, - "amount", - ); - - salaryOrg.currentAmount = totalProfileAmount; - salaryOrg.total = salaryOrg.salaryProfiles.length; - salaryOrg.sixPercentAmount = totalProfileAmount * 0.06; - - // ===== spentAmount (APR SNAP2) ===== - let totalAmount = 0; - - const salaryPeriodAPROld = - await this.salaryPeriodRepository.findOne({ - where: { - year: salaryOrg.salaryPeriod.year, - period: "APR", - }, - }); - - if (salaryPeriodAPROld) { - const salaryOrgSnap2Old = - await this.salaryOrgRepository.findOne({ - where: { - salaryPeriodId: salaryPeriodAPROld.id, - rootId: salaryOrg.rootId, - group: salaryOrg.group, - snapshot: "SNAP2", - }, - relations: ["salaryProfiles"], - }); - - totalAmount = - salaryOrgSnap2Old == null - ? 0 - : Extension.sumObjectValues( - salaryOrgSnap2Old.salaryProfiles, - "amountUse", - ); - } - - salaryOrg.spentAmount = totalAmount; - - // ===== sumAmountUse (current OCT SNAP1) ===== - const sumAmountUse = - await this.salaryProfileRepository - .createQueryBuilder("salaryProfile") - .select( - "SUM(salaryProfile.amountUse)", - "totalAmount", - ) - .where({ - salaryOrgId: salaryOrg.id, - type: In(["HAFT", "FULL", "FULLHAFT"]), - }) - .getRawOne(); - - salaryOrg.useAmount = - sumAmountUse == null || - sumAmountUse.totalAmount == null - ? 0 - : sumAmountUse.totalAmount; - - const calRemainAmount = - salaryOrg.sixPercentAmount - - salaryOrg.useAmount - - salaryOrg.spentAmount; - - salaryOrg.remainingAmount = calRemainAmount; - - salaryOrg.lastUpdateUserId = req.user.sub; - salaryOrg.lastUpdateFullName = req.user.name; - salaryOrg.lastUpdatedAt = new Date(); - - await this.salaryOrgRepository.save(salaryOrg, { data: req }); - return; - } - } - - - private async processSingleProfile( - profileId: string, - body: any, - req: RequestWithUser, - posTypeCache: Map, - posLevelCache: Map, - salaryCache: Map, - affectedOrgIds: Set, - ) { - const salaryProfile = await this.salaryProfileRepository.findOne({ - relations: ["salaryOrg", "salaryOrg.salaryPeriod"], - where: { id: profileId }, - }); - - if (!salaryProfile) { - throw new HttpError( - HttpStatusCode.NOT_FOUND, - "ไม่พบข้อมูลการขอเงินเดือนผู้ใช้งานนี้ในระบบ", - ); - } - - // ==== CHECK FULLHAFT เดิม ==== - if (body.type === "FULLHAFT") { - if (salaryProfile.salaryOrg.salaryPeriod.period === "OCT") { - const checkPreviousType = await this.salaryProfileRepository.findOne({ - relations: ["salaryOrg", "salaryOrg.salaryPeriod"], - where: { - citizenId: salaryProfile.citizenId, - salaryOrg: { - salaryPeriod: { - period: "APR", - year: salaryProfile.salaryOrg.salaryPeriod.year, - }, - snapshot: "SNAP2", - }, - type: "FULL", - }, - }); - - if (checkPreviousType) { - throw new HttpError( - HttpStatusCode.NOT_FOUND, - "ไม่สามารถเลื่อนขั้นบุคลากรเกิน 2 ขั้นต่อปีได้", - ); - } - } - } - - // ==== isReserve เดิม ==== - salaryProfile.isReserve = body.type === "FULL" ? body.isReserve : false; - - // ==== POS TYPE (cache) ==== - let Type = posTypeCache.get(salaryProfile.posType); - if (!Type) { - Type = await this.posTypeRepository.findOne({ - where: { posTypeName: salaryProfile.posType }, - }); - if (!Type) { - throw new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบประเภทตำแหน่ง"); - } - posTypeCache.set(salaryProfile.posType, Type); - } - - // ==== POS LEVEL (cache) ==== - const levelKey = `${Type.id}_${salaryProfile.posLevel}`; - let Level = posLevelCache.get(levelKey); - if (!Level) { - Level = await this.posLevelRepository.findOne({ - where: { - posTypeId: Type.id, - posLevelName: salaryProfile.posLevel?.split("/")[0], - }, - }); - if (!Level) { - throw new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบระดับตำแหน่ง"); - } - posLevelCache.set(levelKey, Level); - } - - // ==== SALARY (cache) ==== - const salaryKey = `${Level.posTypeId}_${Level.id}_${salaryProfile.isSpecial}`; - let salarys = salaryCache.get(salaryKey); - if (!salarys) { - salarys = await this.salaryRepository.findOne({ - where: { - posTypeId: Level.posTypeId, - posLevelId: Level.id, - isSpecial: salaryProfile.isSpecial === true, - isActive: true, - }, - }); - if (!salarys) { - throw new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบระดับตำแหน่ง"); - } - salaryCache.set(salaryKey, salarys); - } - - // ====== ตั้งค่า type / remark ====== - salaryProfile.type = body.type; - salaryProfile.remark = body.remark == null ? null : body.remark; - - let type = salaryProfile.type; - let salaryRanks: any = null; - - // ====== SALARY RANK (logic เดิม 100%) ====== - if (salaryProfile.amount != null) { - salaryRanks = await this.salaryRankRepository.findOne({ - where: { - salaryId: salarys.id, - salary: MoreThanOrEqual(salaryProfile.amount), - isNext: false, - }, - order: { salary: "ASC" }, - }); - - if (salaryRanks != null) { - if (salaryProfile.type === "HAFT") { - if (salaryRanks.salaryHalfSpecial != null && salaryRanks.salaryHalfSpecial > 0) { - const _salaryRanks = await this.salaryRankRepository.findOne({ - where: { - salaryId: salarys.id, - salary: salaryRanks.salaryHalf, - isNext: true, - }, - }); - salaryRanks = _salaryRanks ?? salaryRanks; - } - } else if (salaryProfile.type === "FULL") { - if (salaryRanks.salaryFullSpecial != null && salaryRanks.salaryFullSpecial > 0) { - if (!salaryRanks.salaryHalfSpecial) { - type = "HAFT"; - } - const _salaryRanks = await this.salaryRankRepository.findOne({ - where: { - salaryId: salarys.id, - salary: salaryRanks.salaryFull, - isNext: true, - }, - }); - salaryRanks = _salaryRanks ?? salaryRanks; - } - } else if (salaryProfile.type === "FULLHAFT") { - if (salaryRanks.salaryFullHalfSpecial != null && salaryRanks.salaryFullHalfSpecial > 0) { - if (!salaryRanks.salaryFullSpecial) { - type = "HAFT"; - } else if (!salaryRanks.salaryHalfSpecial) { - type = "FULL"; - } - const _salaryRanks = await this.salaryRankRepository.findOne({ - where: { - salaryId: salarys.id, - salary: salaryRanks.salaryFullHalf, - isNext: true, - }, - }); - salaryRanks = _salaryRanks ?? salaryRanks; - } - } - } else { - salaryRanks = await this.salaryRankRepository.findOne({ - where: { - salaryId: salarys.id, - salary: salaryProfile.amount, - isNext: true, - }, - }); - - if (!salaryRanks) { - salaryRanks = await this.salaryRankRepository.findOne({ - where: { - salaryId: salarys.id, - salary: MoreThan(salaryProfile.amount), - isNext: true, - }, - order: { salary: "ASC" }, - }); - } - } - } - - // ====== SET RESULT (logic เดิม) ====== - salaryProfile.isNext = false; - - if (type === "NONE") { - salaryProfile.amountSpecial = 0; - salaryProfile.amountUse = 0; - salaryProfile.positionSalaryAmount = salaryProfile.amount ?? 0; - } else if (type === "PENDING") { - salaryProfile.amountSpecial = 0; - salaryProfile.amountUse = 0; - salaryProfile.positionSalaryAmount = 0; - } else if (type === "HAFT") { - salaryProfile.amountSpecial = salaryRanks?.salaryHalfSpecial ?? 0; - salaryProfile.amountUse = - salaryRanks && salaryRanks.salaryHalf && salaryProfile.amount - ? salaryRanks.salaryHalf - salaryProfile.amount - : 0; - salaryProfile.positionSalaryAmount = salaryRanks?.salaryHalf ?? 0; - salaryProfile.isNext = salaryRanks?.isNext ?? false; - } else if (type === "FULL") { - salaryProfile.amountSpecial = salaryRanks?.salaryFullSpecial ?? 0; - salaryProfile.amountUse = - salaryRanks && salaryRanks.salaryFull && salaryProfile.amount - ? salaryRanks.salaryFull - salaryProfile.amount - : 0; - salaryProfile.positionSalaryAmount = salaryRanks?.salaryFull ?? 0; - salaryProfile.isNext = salaryRanks?.isNext ?? false; - } else if (type === "FULLHAFT") { - salaryProfile.amountSpecial = salaryRanks?.salaryFullHalfSpecial ?? 0; - salaryProfile.amountUse = - salaryRanks && salaryRanks.salaryFullHalf && salaryProfile.amount - ? salaryRanks.salaryFullHalf - salaryProfile.amount - : 0; - salaryProfile.positionSalaryAmount = salaryRanks?.salaryFullHalf ?? 0; - salaryProfile.isNext = salaryRanks?.isNext ?? false; - } else { - throw new HttpError(HttpStatusCode.NOT_FOUND, "ประเภทการเลื่อนขึ้นเงินเดือนไม่ถูกต้อง"); - } - - salaryProfile.lastUpdateUserId = req.user.sub; - salaryProfile.lastUpdateFullName = req.user.name; - salaryProfile.lastUpdatedAt = new Date(); - - const before = structuredClone(salaryProfile); - await this.salaryProfileRepository.save(salaryProfile, { data: req }); - setLogDataDiff(req, { before, after: salaryProfile }); - - affectedOrgIds.add(salaryProfile.salaryOrg.id); - } - - @Post("change/type-multi") - async changeTypeMulti( - @Body() - body: { - profileId: string[]; - type: string; - isReserve: boolean; - remark?: string | null; - }, - @Request() req: RequestWithUser, - ) { - await new permission().PermissionCreate(req, "SYS_SALARY_OFFICER"); - - body.type = body.type.toUpperCase(); - - const BATCH_SIZE = 20; - const batches = this.chunkArray(body.profileId, BATCH_SIZE); - - // cache - const posTypeCache = new Map(); - const posLevelCache = new Map(); - const salaryCache = new Map(); - - // org ที่ต้อง recalc - const affectedOrgIds = new Set(); - - for (const batch of batches) { - await Promise.all( - batch.map(profileId => - this.processSingleProfile( - profileId, - body, - req, - posTypeCache, - posLevelCache, - salaryCache, - affectedOrgIds, - ), - ), - ); - } - - // recalc salaryOrg ทีเดียวต่อ org - for (const orgId of affectedOrgIds) { - await this.recalculateSalaryOrg(orgId, req); - } - - return new HttpSuccess(); - } - /** * API รายการอัตราเงินเดือน * @@ -2300,7 +1652,6 @@ export class SalaryPeriodController extends Controller { _data.child1 != undefined && _data.child1 != null ? _data.child1[0] != null ? `child1Id IN (:...child1)` - // : `child1Id is ${_data.privilege == "PARENT" ? "not null" : "null"}` : `child1Id is null` : "1=1", { @@ -2336,25 +1687,27 @@ export class SalaryPeriodController extends Controller { { child4: _data.child4, }, - ); + ) - if (body.sortBy) { - if (body.sortBy === "posExecutive") { - query = query - .orderBy(`profile.posExecutive`, body.descending ? "DESC" : "ASC") - .addOrderBy(`profile.positionExecutiveField`, body.descending ? "DESC" : "ASC"); - } else { - query = query.orderBy(`profile.${body.sortBy}`, body.descending ? "DESC" : "ASC"); - } - } else { - query = query - .orderBy("profile.rootOrder", "ASC") + if (body.sortBy) { + if(body.sortBy === "posExecutive"){ + query = query + .orderBy( `profile.posExecutive`,body.descending ? "DESC" : "ASC") + .addOrderBy( `profile.positionExecutiveField`,body.descending ? "DESC" : "ASC"); + }else{ + query = query.orderBy( + `profile.${body.sortBy}`, + body.descending ? "DESC" : "ASC" + ); + } + }else{ + query = query.orderBy("profile.rootOrder", "ASC") .addOrderBy("profile.child1Order", "ASC") .addOrderBy("profile.child2Order", "ASC") .addOrderBy("profile.child3Order", "ASC") .addOrderBy("profile.child4Order", "ASC") - .addOrderBy("profile.posMasterNo", "ASC"); - } + .addOrderBy("profile.posMasterNo", "ASC") + } const [salaryProfile, total] = await query .skip((body.page - 1) * body.pageSize) @@ -2474,7 +1827,7 @@ export class SalaryPeriodController extends Controller { const posLevel = await this.posLevelRepository.findOne({ where: { posTypeId: posType.id, - posLevelName: salaryProfile.posLevel?.split("/")[0], + posLevelName: salaryProfile.posLevel, }, }); if (!posLevel) { @@ -2493,7 +1846,7 @@ export class SalaryPeriodController extends Controller { throw new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบระดับตำแหน่ง"); } let type = salaryProfile.type; - //SalaryRanks + //SalaryRank let salaryRanks: any = null; if (salaryProfile.amount != null) { salaryRanks = await this.salaryRankRepository.findOne({ @@ -2587,9 +1940,9 @@ export class SalaryPeriodController extends Controller { : salaryRanks.salaryHalfSpecial; salaryProfile.amountUse = salaryRanks == null || - salaryProfile == null || - salaryRanks.salaryHalf == null || - salaryProfile.amount == null + salaryProfile == null || + salaryRanks.salaryHalf == null || + salaryProfile.amount == null ? 0 : salaryRanks.salaryHalf - salaryProfile.amount; salaryProfile.positionSalaryAmount = @@ -2602,9 +1955,9 @@ export class SalaryPeriodController extends Controller { : salaryRanks.salaryFullSpecial; salaryProfile.amountUse = salaryRanks == null || - salaryProfile == null || - salaryRanks.salaryFull == null || - salaryProfile.amount == null + salaryProfile == null || + salaryRanks.salaryFull == null || + salaryProfile.amount == null ? 0 : salaryRanks.salaryFull - salaryProfile.amount; salaryProfile.positionSalaryAmount = @@ -2617,9 +1970,9 @@ export class SalaryPeriodController extends Controller { : salaryRanks.salaryFullHalfSpecial; salaryProfile.amountUse = salaryRanks == null || - salaryProfile == null || - salaryRanks.salaryFullHalf == null || - salaryProfile.amount == null + salaryProfile == null || + salaryRanks.salaryFullHalf == null || + salaryProfile.amount == null ? 0 : salaryRanks.salaryFullHalf - salaryProfile.amount; salaryProfile.positionSalaryAmount = @@ -3182,17 +2535,19 @@ export class SalaryPeriodController extends Controller { "salaryPeriod.status", "salaryPeriod.year", "salaryPeriod.revisionId", - ]); + ]) - if (sortBy) { - query = query.orderBy(`salaryPeriod.${sortBy}`, descending ? "DESC" : "ASC"); - } else { - query = query - .orderBy("salaryPeriod.year", "DESC") - .addOrderBy("salaryPeriod.effectiveDate", "DESC"); - } + if (sortBy) { + query = query.orderBy( + `salaryPeriod.${sortBy}`, + descending ? "DESC" : "ASC" + ); + }else{ + query = query.orderBy("salaryPeriod.year", "DESC") + .addOrderBy("salaryPeriod.effectiveDate", "DESC") + } - const [salaryPeriod, total] = await query + const [salaryPeriod, total] = await query .skip((page - 1) * pageSize) .take(pageSize) .getManyAndCount(); @@ -3439,27 +2794,27 @@ export class SalaryPeriodController extends Controller { const [salaryProfileCount, salaryProfileEmployeeCount] = await Promise.all([ salaryOrgCount > 0 ? AppDataSource.query( - ` + ` SELECT COUNT(*) as count FROM salaryProfile WHERE salaryOrgId IN ( SELECT id FROM salaryOrg WHERE salaryPeriodId = ? AND snapshot = ? ) `, - [salaryPeriod.id, snapshot], - ).then((result) => result[0]?.count || 0) + [salaryPeriod.id, snapshot], + ).then((result) => result[0]?.count || 0) : Promise.resolve(0), salaryOrgEmployeeCount > 0 ? AppDataSource.query( - ` + ` SELECT COUNT(*) as count FROM salaryProfileEmployee WHERE salaryOrgId IN ( SELECT id FROM salaryOrgEmployee WHERE salaryPeriodId = ? AND snapshot = ? ) `, - [salaryPeriod.id, snapshot], - ).then((result) => result[0]?.count || 0) + [salaryPeriod.id, snapshot], + ).then((result) => result[0]?.count || 0) : Promise.resolve(0), ]); @@ -3472,27 +2827,27 @@ export class SalaryPeriodController extends Controller { await Promise.all([ salaryOrgCount > 0 ? AppDataSource.query( - ` + ` DELETE FROM salaryProfile WHERE salaryOrgId IN ( SELECT id FROM salaryOrg WHERE salaryPeriodId = ? AND snapshot = ? ) `, - [salaryPeriod.id, snapshot], - ) + [salaryPeriod.id, snapshot], + ) : Promise.resolve(), salaryOrgEmployeeCount > 0 ? AppDataSource.query( - ` + ` DELETE FROM salaryProfileEmployee WHERE salaryOrgId IN ( SELECT id FROM salaryOrgEmployee WHERE salaryPeriodId = ? AND snapshot = ? ) `, - [salaryPeriod.id, snapshot], - ) + [salaryPeriod.id, snapshot], + ) : Promise.resolve(), ]); @@ -4468,27 +3823,27 @@ export class SalaryPeriodController extends Controller { const [salaryProfileCount, salaryProfileEmployeeCount] = await Promise.all([ salaryOrgCount > 0 ? AppDataSource.query( - ` + ` SELECT COUNT(*) as count FROM salaryProfile WHERE salaryOrgId IN ( SELECT id FROM salaryOrg WHERE salaryPeriodId = ? AND snapshot = ? ) `, - [salaryPeriod.id, snapshot], - ).then((result) => result[0]?.count || 0) + [salaryPeriod.id, snapshot], + ).then((result) => result[0]?.count || 0) : Promise.resolve(0), salaryOrgEmployeeCount > 0 ? AppDataSource.query( - ` + ` SELECT COUNT(*) as count FROM salaryProfileEmployee WHERE salaryOrgId IN ( SELECT id FROM salaryOrgEmployee WHERE salaryPeriodId = ? AND snapshot = ? ) `, - [salaryPeriod.id, snapshot], - ).then((result) => result[0]?.count || 0) + [salaryPeriod.id, snapshot], + ).then((result) => result[0]?.count || 0) : Promise.resolve(0), ]); @@ -4501,27 +3856,27 @@ export class SalaryPeriodController extends Controller { await Promise.all([ salaryOrgCount > 0 ? AppDataSource.query( - ` + ` DELETE FROM salaryProfile WHERE salaryOrgId IN ( SELECT id FROM salaryOrg WHERE salaryPeriodId = ? AND snapshot = ? ) `, - [salaryPeriod.id, snapshot], - ) + [salaryPeriod.id, snapshot], + ) : Promise.resolve(), salaryOrgEmployeeCount > 0 ? AppDataSource.query( - ` + ` DELETE FROM salaryProfileEmployee WHERE salaryOrgId IN ( SELECT id FROM salaryOrgEmployee WHERE salaryPeriodId = ? AND snapshot = ? ) `, - [salaryPeriod.id, snapshot], - ) + [salaryPeriod.id, snapshot], + ) : Promise.resolve(), ]); @@ -4573,40 +3928,22 @@ export class SalaryPeriodController extends Controller { // Parallel loading of initial data and API calls console.time("⏱ API: Load initial data"); const [orgs, revisionId, _orgProfiles, _orgProfileEmployees] = await Promise.all([ - new CallAPI().GetData( - { headers: { authorization: request } }, - "/org/unauthorize/active/root/id", - false, - ), - new CallAPI().GetData( - { headers: { authorization: request } }, - "/org/unauthorize/revision/latest", - false, - ), - new CallAPI().PostData( - { headers: { authorization: request } }, - "/org/unauthorize/new-salary/gen", - { - page: 1, - pageSize: 1000, - keyword: "", - year: salaryPeriod.year, - period: salaryPeriod.period, - }, - false, - ), - new CallAPI().PostData( - { headers: { authorization: request } }, - "/org/unauthorize/new-salary/employee/gen", - { - page: 1, - pageSize: 1000, - keyword: "", - year: salaryPeriod.year, - period: salaryPeriod.period, - }, - false, - ), + new CallAPI().GetData({ headers: { authorization: request } }, "/org/unauthorize/active/root/id",false), + new CallAPI().GetData({ headers: { authorization: request } }, "/org/unauthorize/revision/latest",false), + new CallAPI().PostData({ headers: { authorization: request } }, "/org/unauthorize/new-salary/gen", { + page: 1, + pageSize: 1000, + keyword: "", + year: salaryPeriod.year, + period: salaryPeriod.period, + },false), + new CallAPI().PostData({ headers: { authorization: request } }, "/org/unauthorize/new-salary/employee/gen", { + page: 1, + pageSize: 1000, + keyword: "", + year: salaryPeriod.year, + period: salaryPeriod.period, + },false), ]); console.timeEnd("⏱ API: Load initial data"); @@ -4621,18 +3958,13 @@ export class SalaryPeriodController extends Controller { const promises = []; for (let index = 2; index <= page; index++) { promises.push( - new CallAPI().PostData( - { headers: { authorization: request } }, - "/org/unauthorize/new-salary/gen", - { - page: index, - pageSize: 1000, - keyword: "", - year: salaryPeriod.year, - period: salaryPeriod.period, - }, - false, - ), + new CallAPI().PostData({ headers: { authorization: request } }, "/org/unauthorize/new-salary/gen", { + page: index, + pageSize: 1000, + keyword: "", + year: salaryPeriod.year, + period: salaryPeriod.period, + },false), ); } const results = await Promise.all(promises); @@ -4651,18 +3983,13 @@ export class SalaryPeriodController extends Controller { const promises = []; for (let index = 2; index <= page; index++) { promises.push( - new CallAPI().PostData( - { headers: { authorization: request } }, - "/org/unauthorize/new-salary/employee/gen", - { - page: index, - pageSize: 1000, - keyword: "", - year: salaryPeriod.year, - period: salaryPeriod.period, - }, - false, - ), + new CallAPI().PostData({ headers: { authorization: request } }, "/org/unauthorize/new-salary/employee/gen", { + page: index, + pageSize: 1000, + keyword: "", + year: salaryPeriod.year, + period: salaryPeriod.period, + },false), ); } const results = await Promise.all(promises); @@ -5248,9 +4575,9 @@ export class SalaryPeriodController extends Controller { _salaryOrg.remainQuota = Math.floor((_salaryOrg.salaryProfiles.length * 15) / 100); } - _salaryOrg.createdUserId = ""; + _salaryOrg.createdUserId = "" _salaryOrg.createdFullName = "System Administrator"; - _salaryOrg.lastUpdateUserId = ""; + _salaryOrg.lastUpdateUserId = "" _salaryOrg.lastUpdateFullName = "System Administrator"; _salaryOrg.createdAt = new Date(); _salaryOrg.lastUpdatedAt = new Date(); @@ -5386,10 +4713,9 @@ export class SalaryPeriodController extends Controller { }, }, ); - let request: any = response.data.access_token; - - if (current.getDate() == 1 && current.getMonth() == 2) { - //snap1 วันที่ 1 มีนา + let request:any = response.data.access_token; + + if (current.getDate() == 1 && current.getMonth() == 2) { //snap1 วันที่ 1 มีนา salaryPeriod = await this.salaryPeriodRepository.findOne({ where: { year: current.getFullYear(), @@ -5400,8 +4726,7 @@ export class SalaryPeriodController extends Controller { if (salaryPeriod) { this.performSnapshotOperationForCronjob("SNAP1", salaryPeriod.id, request); } - } else if (current.getDate() == 1 && current.getMonth() == 3) { - //snap2 วันที่ 1 เมษา + } else if (current.getDate() == 1 && current.getMonth() == 3) { //snap2 วันที่ 1 เมษา salaryPeriod = await this.salaryPeriodRepository.findOne({ where: { year: current.getFullYear(), @@ -5412,8 +4737,7 @@ export class SalaryPeriodController extends Controller { if (salaryPeriod) { this.performSnapshotOperationForCronjob("SNAP2", salaryPeriod.id, request); } - } else if (current.getDate() == 1 && current.getMonth() == 8) { - //snap1 วันที่ 1 กันยา + } else if (current.getDate() == 1 && current.getMonth() == 8) { //snap1 วันที่ 1 กันยา salaryPeriod = await this.salaryPeriodRepository.findOne({ where: { year: current.getFullYear(), @@ -5424,8 +4748,7 @@ export class SalaryPeriodController extends Controller { if (salaryPeriod) { this.performSnapshotOperationForCronjob("SNAP1", salaryPeriod.id, request); } - } else if (current.getDate() == 1 && current.getMonth() == 9) { - //snap2 วันที่ 1 ตุลา + } else if (current.getDate() == 1 && current.getMonth() == 9) { //snap2 วันที่ 1 ตุลา salaryPeriod = await this.salaryPeriodRepository.findOne({ where: { year: current.getFullYear(), diff --git a/src/controllers/SalaryPeriodEmployeeController.ts b/src/controllers/SalaryPeriodEmployeeController.ts index 1f96f04..86e9702 100644 --- a/src/controllers/SalaryPeriodEmployeeController.ts +++ b/src/controllers/SalaryPeriodEmployeeController.ts @@ -70,68 +70,68 @@ export class SalaryPeriodEmployeeController extends Controller { const data = { group1id: salaryPeriod.salaryOrgEmployees && - salaryPeriod.salaryOrgEmployees.find( - (x) => - x.group == "GROUP1" && - x.rootId == body.rootId && - x.snapshot == body.snapshot.toLocaleUpperCase(), - ) == null + salaryPeriod.salaryOrgEmployees.find( + (x) => + x.group == "GROUP1" && + x.rootId == body.rootId && + x.snapshot == body.snapshot.toLocaleUpperCase(), + ) == null ? null : salaryPeriod.salaryOrgEmployees && - salaryPeriod.salaryOrgEmployees.find( - (x) => - x.group == "GROUP1" && - x.rootId == body.rootId && - x.snapshot == body.snapshot.toLocaleUpperCase(), - )?.id, + salaryPeriod.salaryOrgEmployees.find( + (x) => + x.group == "GROUP1" && + x.rootId == body.rootId && + x.snapshot == body.snapshot.toLocaleUpperCase(), + )?.id, group1IsClose: salaryPeriod.salaryOrgEmployees && - salaryPeriod.salaryOrgEmployees.find( - (x) => - x.group == "GROUP1" && - x.rootId == body.rootId && - x.snapshot == body.snapshot.toLocaleUpperCase(), - ) == null + salaryPeriod.salaryOrgEmployees.find( + (x) => + x.group == "GROUP1" && + x.rootId == body.rootId && + x.snapshot == body.snapshot.toLocaleUpperCase(), + ) == null ? null : salaryPeriod.salaryOrgEmployees && - salaryPeriod.salaryOrgEmployees.find( - (x) => - x.group == "GROUP1" && - x.rootId == body.rootId && - x.snapshot == body.snapshot.toLocaleUpperCase(), - )?.isClose, + salaryPeriod.salaryOrgEmployees.find( + (x) => + x.group == "GROUP1" && + x.rootId == body.rootId && + x.snapshot == body.snapshot.toLocaleUpperCase(), + )?.isClose, group2id: salaryPeriod.salaryOrgEmployees && - salaryPeriod.salaryOrgEmployees.find( - (x) => - x.group == "GROUP2" && - x.rootId == body.rootId && - x.snapshot == body.snapshot.toLocaleUpperCase(), - ) == null + salaryPeriod.salaryOrgEmployees.find( + (x) => + x.group == "GROUP2" && + x.rootId == body.rootId && + x.snapshot == body.snapshot.toLocaleUpperCase(), + ) == null ? null : salaryPeriod.salaryOrgEmployees && - salaryPeriod.salaryOrgEmployees.find( - (x) => - x.group == "GROUP2" && - x.rootId == body.rootId && - x.snapshot == body.snapshot.toLocaleUpperCase(), - )?.id, + salaryPeriod.salaryOrgEmployees.find( + (x) => + x.group == "GROUP2" && + x.rootId == body.rootId && + x.snapshot == body.snapshot.toLocaleUpperCase(), + )?.id, group2IsClose: salaryPeriod.salaryOrgEmployees && - salaryPeriod.salaryOrgEmployees.find( - (x) => - x.group == "GROUP2" && - x.rootId == body.rootId && - x.snapshot == body.snapshot.toLocaleUpperCase(), - ) == null + salaryPeriod.salaryOrgEmployees.find( + (x) => + x.group == "GROUP2" && + x.rootId == body.rootId && + x.snapshot == body.snapshot.toLocaleUpperCase(), + ) == null ? null : salaryPeriod.salaryOrgEmployees && - salaryPeriod.salaryOrgEmployees.find( - (x) => - x.group == "GROUP2" && - x.rootId == body.rootId && - x.snapshot == body.snapshot.toLocaleUpperCase(), - )?.isClose, + salaryPeriod.salaryOrgEmployees.find( + (x) => + x.group == "GROUP2" && + x.rootId == body.rootId && + x.snapshot == body.snapshot.toLocaleUpperCase(), + )?.isClose, effectiveDate: salaryPeriod.effectiveDate, period: salaryPeriod.period, }; @@ -430,7 +430,7 @@ export class SalaryPeriodEmployeeController extends Controller { const Level = await this.posLevelRepository.findOne({ where: { posTypeId: Type.id, - posLevelName: salaryProfile.posLevel?.split("/")[0], + posLevelName: salaryProfile.posLevel, }, }); if (!Level) { @@ -750,7 +750,7 @@ export class SalaryPeriodEmployeeController extends Controller { const Level = await this.posLevelRepository.findOne({ where: { posTypeId: Type.id, - posLevelName: salaryProfile.posLevel?.split("/")[0], + posLevelName: salaryProfile.posLevel, }, }); if (!Level) { @@ -762,7 +762,7 @@ export class SalaryPeriodEmployeeController extends Controller { let type = salaryProfile.type; salaryProfile = await this.calSalaryNew(type, salaryProfile); - + salaryProfile.lastUpdateUserId = req.user.sub; salaryProfile.lastUpdateFullName = req.user.name; salaryProfile.lastUpdatedAt = new Date(); @@ -879,8 +879,8 @@ export class SalaryPeriodEmployeeController extends Controller { * @param {string} id profile Id * @param {string} type ประเภทการเลื่อน NONE->ไม่ได้เลื่อน HAFT->ครึ่งขั้น FULL->1ขั้น FULLHAFT->1.5ขั้น */ - @Post("oldchange/type-multi") - async oldchangeTypeMulti( + @Post("change/type-multi") + async changeTypeMulti( @Body() body: { profileId: string[]; type: string; isReserve: boolean; remark?: string | null }, @Request() req: RequestWithUser, ) { @@ -941,7 +941,7 @@ export class SalaryPeriodEmployeeController extends Controller { const Level = await this.posLevelRepository.findOne({ where: { posTypeId: Type.id, - posLevelName: salaryProfile.posLevel?.split("/")[0], + posLevelName: salaryProfile.posLevel, }, }); if (!Level) { @@ -952,7 +952,7 @@ export class SalaryPeriodEmployeeController extends Controller { salaryProfile.remark = body.remark == null ? _null : body.remark; let type = salaryProfile.type; - salaryProfile = await this.calSalaryNew(type, salaryProfile); + salaryProfile = await this.calSalary(type, salaryProfile); salaryProfile.lastUpdateUserId = req.user.sub; salaryProfile.lastUpdateFullName = req.user.name; @@ -1063,264 +1063,6 @@ export class SalaryPeriodEmployeeController extends Controller { return new HttpSuccess(); } - private chunkArray(arr: T[], size: number): T[][] { - const result: T[][] = []; - for (let i = 0; i < arr.length; i += size) { - result.push(arr.slice(i, i + size)); - } - return result; - } - - private async processEmployeeProfile( - profileId: string, - body: any, - req: RequestWithUser, - affectedOrgIds: Set, - ) { - let salaryProfile = await this.salaryProfileRepository.findOne({ - relations: ["salaryOrg", "salaryOrg.salaryPeriod"], - where: { id: profileId }, - }); - - if (!salaryProfile) { - throw new HttpError( - HttpStatusCode.NOT_FOUND, - "ไม่พบข้อมูลการขอเงินเดือนผู้ใช้งานนี้ในระบบ", - ); - } - - // ===== FULLHAFT CHECK (เดิม) ===== - if (body.type === "FULLHAFT") { - if (salaryProfile.salaryOrg.salaryPeriod.period === "OCT") { - const checkPreviousType = - await this.salaryProfileRepository.findOne({ - relations: ["salaryOrg", "salaryOrg.salaryPeriod"], - where: { - citizenId: salaryProfile.citizenId, - salaryOrg: { - salaryPeriod: { - period: "APR", - year: salaryProfile.salaryOrg.salaryPeriod.year, - }, - snapshot: "SNAP2", - }, - type: "FULL", - }, - }); - - if (checkPreviousType) { - throw new HttpError( - HttpStatusCode.NOT_FOUND, - "ไม่สามารถเลื่อนขั้นบุคลากรเกิน 2 ขั้นต่อปีได้", - ); - } - } - } - - // ===== isReserve (เดิม) ===== - salaryProfile.isReserve = - body.type === "FULL" ? body.isReserve : false; - - // ===== Type & Level check (เดิม) ===== - const Type = await this.posTypeRepository.findOne({ - where: { posTypeName: salaryProfile.posType }, - }); - if (!Type) { - throw new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบประเภทตำแหน่ง"); - } - - const Level = await this.posLevelRepository.findOne({ - where: { - posTypeId: Type.id, - posLevelName: salaryProfile.posLevel?.split("/")[0], - }, - }); - if (!Level) { - throw new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบระดับตำแหน่ง"); - } - - // ===== type / remark ===== - salaryProfile.type = body.type; - salaryProfile.remark = body.remark == null ? null : body.remark; - - // ===== CALC SALARY (เดิม 100%) ===== - salaryProfile = await this.calSalaryNew( - salaryProfile.type, - salaryProfile, - ); - - // ===== audit ===== - salaryProfile.lastUpdateUserId = req.user.sub; - salaryProfile.lastUpdateFullName = req.user.name; - salaryProfile.lastUpdatedAt = new Date(); - - const before = structuredClone(salaryProfile); - await this.salaryProfileRepository.save(salaryProfile, { - data: req, - }); - setLogDataDiff(req, { before, after: salaryProfile }); - - affectedOrgIds.add(salaryProfile.salaryOrg.id); - } - - private async recalculateSalaryOrgEmployee( - orgId: string, - req: RequestWithUser, - ) { - const salaryOrg = await this.salaryOrgRepository.findOne({ - where: { id: orgId }, - relations: ["salaryProfiles", "salaryPeriod"], - }); - - if (!salaryOrg) return; - if (salaryOrg.snapshot !== "SNAP1") return; - - // ===================== APR ===================== - if (salaryOrg.salaryPeriod.period === "APR") { - const amountFullType = - await this.salaryProfileRepository.count({ - where: { - salaryOrgId: salaryOrg.id, - type: "FULL", - }, - }); - - salaryOrg.total = salaryOrg.salaryProfiles.length; - salaryOrg.fifteenPercent = Math.floor( - (salaryOrg.total * 15) / 100, - ); - salaryOrg.fifteenPoint = (salaryOrg.total * 15) % 100; - salaryOrg.quantityUsed = amountFullType; - salaryOrg.remainQuota = - salaryOrg.fifteenPercent - amountFullType; - - salaryOrg.lastUpdateUserId = req.user.sub; - salaryOrg.lastUpdateFullName = req.user.name; - salaryOrg.lastUpdatedAt = new Date(); - - await this.salaryOrgRepository.save(salaryOrg, { data: req }); - return; - } - - // ===================== OCT ===================== - if (salaryOrg.salaryPeriod.period === "OCT") { - const totalProfileAmount = Extension.sumObjectValues( - salaryOrg.salaryProfiles, - "amount", - ); - - salaryOrg.currentAmount = totalProfileAmount; - salaryOrg.total = salaryOrg.salaryProfiles.length; - salaryOrg.sixPercentAmount = totalProfileAmount * 0.06; - - // ===== APR SNAP2 spentAmount ===== - let totalAmount = 0; - - const salaryPeriodAPROld = - await this.salaryPeriodRepository.findOne({ - where: { - year: salaryOrg.salaryPeriod.year, - period: "APR", - }, - }); - - if (salaryPeriodAPROld) { - const salaryOrgSnap2Old = - await this.salaryOrgRepository.findOne({ - where: { - salaryPeriodId: salaryPeriodAPROld.id, - rootId: salaryOrg.rootId, - group: salaryOrg.group, - snapshot: "SNAP2", - }, - relations: ["salaryProfiles"], - }); - - totalAmount = - salaryOrgSnap2Old == null - ? 0 - : Extension.sumObjectValues( - salaryOrgSnap2Old.salaryProfiles, - "amountUse", - ); - } - - salaryOrg.spentAmount = totalAmount; - - // ===== sumAmountUse (current OCT SNAP1) ===== - const sumAmountUse = - await AppDataSource.getRepository( - SalaryProfileEmployee, - ) - .createQueryBuilder("salaryProfileEmployee") - .select( - "SUM(salaryProfileEmployee.amountUse)", - "totalAmount", - ) - .where({ - salaryOrgId: salaryOrg.id, - type: In(["HAFT", "FULL", "FULLHAFT"]), - }) - .getRawOne(); - - salaryOrg.useAmount = - sumAmountUse?.totalAmount ?? 0; - - salaryOrg.remainingAmount = - salaryOrg.sixPercentAmount - - salaryOrg.useAmount - - salaryOrg.spentAmount; - - salaryOrg.lastUpdateUserId = req.user.sub; - salaryOrg.lastUpdateFullName = req.user.name; - salaryOrg.lastUpdatedAt = new Date(); - - await this.salaryOrgRepository.save(salaryOrg, { data: req }); - return; - } - } - - @Post("change/type-multi") - async changeTypeMulti( - @Body() - body: { - profileId: string[]; - type: string; - isReserve: boolean; - remark?: string | null; - }, - @Request() req: RequestWithUser, - ) { - await new permission().PermissionCreate(req, "SYS_WAGE"); - - body.type = body.type.toUpperCase(); - - const BATCH_SIZE = 20; - const affectedOrgIds = new Set(); - - const batches = this.chunkArray(body.profileId, BATCH_SIZE); - - for (const batch of batches) { - await Promise.all( - batch.map(profileId => - this.processEmployeeProfile( - profileId, - body, - req, - affectedOrgIds, - ), - ), - ); - } - - // === recalc salaryOrg ทีเดียวต่อ org === - for (const orgId of affectedOrgIds) { - await this.recalculateSalaryOrgEmployee(orgId, req); - } - - return new HttpSuccess(); - } - /** * API รายการอัตราเงินเดือน * @@ -1417,21 +1159,21 @@ export class SalaryPeriodEmployeeController extends Controller { }), ) - if (body.sortBy) { - if (body.sortBy === "posLevel") { - query = query - .orderBy(`profile.posTypeShort`, body.descending ? "DESC" : "ASC") - .addOrderBy(`profile.posLevel`, body.descending ? "DESC" : "ASC"); - } else { - query = query.orderBy( - `profile.${body.sortBy}`, - body.descending ? "DESC" : "ASC" - ); + if (body.sortBy) { + if(body.sortBy === "posLevel"){ + query = query + .orderBy( `profile.posTypeShort`,body.descending ? "DESC" : "ASC") + .addOrderBy( `profile.posLevel`,body.descending ? "DESC" : "ASC"); + }else{ + query = query.orderBy( + `profile.${body.sortBy}`, + body.descending ? "DESC" : "ASC" + ); + } + }else{ + query = query.orderBy("profile.citizenId", "ASC") + .addOrderBy("profile.isReserve", "ASC") } - } else { - query = query.orderBy("profile.citizenId", "ASC") - .addOrderBy("profile.isReserve", "ASC") - } const [salaryProfile, total] = await query .skip((body.page - 1) * body.pageSize) @@ -1597,7 +1339,7 @@ export class SalaryPeriodEmployeeController extends Controller { const posLevel = await this.posLevelRepository.findOne({ where: { posTypeId: posType.id, - posLevelName: salaryProfile.posLevel?.split("/")[0], + posLevelName: salaryProfile.posLevel, }, }); if (!posLevel) { @@ -2507,7 +2249,7 @@ export class SalaryPeriodEmployeeController extends Controller { salaryProfile.positionSalaryAmountPer = 0; salaryProfile.amountSpecial = 0; } - } else { + } else { if ( salaryFormula != null && salaryFormula.salaryMax != null && @@ -2709,29 +2451,29 @@ export class SalaryPeriodEmployeeController extends Controller { }, relations: ["salaryEmployee_"], }); - + if (!salaryCurrentRanks) { salaryCurrentRanks = await this.salaryRankRepository.findOne({ where: { salaryMonth: MoreThanOrEqual(salaryProfile.amount), }, order: { - salaryMonth: "ASC", + salaryMonth: "ASC", }, relations: ["salaryEmployee_"], }); } - + if (salaryCurrentRanks) { group = salaryCurrentRanks.salaryEmployee_.group; step = salaryCurrentRanks.step; } } - //console.log("group", group); - //console.log("step", step); - - + //console.log("group", group); + //console.log("step", step); + + if (type == "HAFT") { step = step + 0.5; stepUp = 0.5; @@ -2742,7 +2484,7 @@ export class SalaryPeriodEmployeeController extends Controller { step = step + 1.5; stepUp = 1.5; } - //console.log("step+type", step); + //console.log("step+type", step); //หาขั้นสูงสุดในกลุ่มนั้น let salaryRankMax = await this.salaryRankRepository.findOne({ where: { @@ -2754,13 +2496,13 @@ export class SalaryPeriodEmployeeController extends Controller { order: { step: "DESC" }, }); - //console.log("salaryRankMax.step", salaryRankMax?.step); - //console.log("salaryProfile.amount", salaryProfile.amount); - //console.log("salaryFormula.salaryMax", salaryFormula?.salaryMax); + //console.log("salaryRankMax.step", salaryRankMax?.step); + //console.log("salaryProfile.amount", salaryProfile.amount); + //console.log("salaryFormula.salaryMax", salaryFormula?.salaryMax); //เงินเดือนเกินตาราง //****หา shot ที่ +ขั้น แล้วแก้เป็นหาเงินเดือนที่ใกล้เคียงกับขั้นผังเก่าก่อนแล้วค่อย +ขั้นที่เลื่อนเข้าไป ex.เงินตันที่ 20000 ไปหาผังใหม่ได้ใกล้เคียง 20100 ยึดตัวเลขนี้ไว้แล้วค่อย +ขั้นในผังใหม่ขึ้นไป if ( - salaryRankMax != null && + salaryRankMax != null && step > salaryRankMax.step && (salaryFormula == null || (salaryFormula != null && @@ -2768,8 +2510,8 @@ export class SalaryPeriodEmployeeController extends Controller { salaryFormula.salaryMax != null && salaryFormula.salaryMax > salaryProfile.amount)) ) { - //console.log("in function เกินตาราง"); - + //console.log("in function เกินตาราง"); + group = group + 1; //เงินเดือนในกลุ่มต่อไป let salaryRankAmount = await this.salaryRankRepository.findOne({ @@ -2791,7 +2533,7 @@ export class SalaryPeriodEmployeeController extends Controller { // (step - (salaryRankMax == null ? 0 : salaryRankMax.step) - 0.5); step = (salaryRankAmount == null ? 1 : salaryRankAmount.step) + stepUp; //****หาขั้นของผังใหม่แล้ว + ด้วยขั้นที่ได้เลื่อน - //console.log("step in if", step); + //console.log("step in if", step); } let whereCondition: any = { diff --git a/src/entities/EmployeePosLevel.ts b/src/entities/EmployeePosLevel.ts index d618164..fd2c681 100644 --- a/src/entities/EmployeePosLevel.ts +++ b/src/entities/EmployeePosLevel.ts @@ -12,9 +12,9 @@ enum EmployeePosLevelAuthoritys { export class EmployeePosLevel extends EntityBase { @Column({ comment: "ชื่อระดับชั้นงาน", - length: 255, + type: "int", }) - posLevelName: string; + posLevelName: number; @Column({ comment: "ระดับของระดับชั้นงาน", diff --git a/src/entities/SalaryProfileEmployee.ts b/src/entities/SalaryProfileEmployee.ts index b8f8091..0f98cec 100644 --- a/src/entities/SalaryProfileEmployee.ts +++ b/src/entities/SalaryProfileEmployee.ts @@ -134,7 +134,7 @@ export class SalaryProfileEmployee extends EntityBase { comment: "ระดับตำแหน่ง", default: null, }) - posLevel: string; + posLevel: number; @Column({ type: "double", @@ -482,7 +482,7 @@ export class CreateSalaryProfileEmployee { posType: string | null; @Column() - posLevel: string | null; + posLevel: number | null; @Column() group: number | null; diff --git a/src/interfaces/permission.ts b/src/interfaces/permission.ts index a870688..6ff8977 100644 --- a/src/interfaces/permission.ts +++ b/src/interfaces/permission.ts @@ -94,17 +94,6 @@ class CheckAuth { child4: null, privilege: "ROOT", }; - } else if (privilege == "PARENT") { - data = { - // root: [x.orgRootId], - // child1: [null], - root: null, - child1: null, - child2: null, - child3: null, - child4: null, - privilege: "PARENT", - }; } else if (privilege == "CHILD") { data = { root: node >= 0 ? [x.orgRootId] : null, @@ -114,15 +103,6 @@ class CheckAuth { child4: node >= 4 ? [x.orgChild4Id] : null, privilege: "CHILD", }; - } else if (privilege == "BROTHER") { - data = { - // root: node >= 0 ? [x.orgRootId] : null, - root: node >= 0 ? [x.orgRootId] : null, - child1: node >= 2 ? [x.orgChild1Id] : null, - child2: node >= 3 ? [x.orgChild2Id] : null, - child3: node >= 4 ? [x.orgChild3Id] : null, - privilege: "BROTHER", - }; } else if (privilege == "NORMAL") { data = { root: [x.orgRootId], @@ -205,70 +185,38 @@ class CheckAuth { } public async checkOrg(token: any, keycloakId: string) { const redisClient = await this.redis.createClient({ - host: process.env.REDIS_HOST, - port: process.env.REDIS_PORT, - }); - const getAsync = promisify(redisClient.get).bind(redisClient); - try { - let reply = await getAsync("org_" + keycloakId); - if (reply != null) { - reply = JSON.parse(reply); - } else { - if (!keycloakId) throw new Error("No KeycloakId provided"); - const x = await new CallAPI().GetData( - { - headers: { authorization: token }, - }, - `/org/permission/checkOrg/${keycloakId}`, - false, - ); + host: process.env.REDIS_HOST, + port: process.env.REDIS_PORT, + }) + const getAsync = promisify(redisClient.get).bind(redisClient) + try { + let reply = await getAsync("org_" + keycloakId) + if (reply != null) { + reply = JSON.parse(reply) + } else { + if (!keycloakId) throw new Error("No KeycloakId provided") + const x = await new CallAPI().GetData( + { + headers: { authorization: token }, + }, + `/org/permission/checkOrg/${keycloakId}`, + false + ) - const data = { - orgRootId: x.orgRootId, - orgChild1Id: x.orgChild1Id, - orgChild2Id: x.orgChild2Id, - orgChild3Id: x.orgChild3Id, - orgChild4Id: x.orgChild4Id, - }; + const data = { + orgRootId: x.orgRootId, + orgChild1Id: x.orgChild1Id, + orgChild2Id: x.orgChild2Id, + orgChild3Id: x.orgChild3Id, + orgChild4Id: x.orgChild4Id, + } - return data; - } - } catch (error) { - console.error("Error calling API:", error); - throw error; - } - } - - public async checkRootDna(token: any, keycloakId: string) { - const redisClient = await this.redis.createClient({ - host: process.env.REDIS_HOST, - port: process.env.REDIS_PORT, - }); - const getAsync = promisify(redisClient.get).bind(redisClient); - try { - let reply = await getAsync("org_" + keycloakId); - if (reply != null) { - reply = JSON.parse(reply); - } else { - if (!keycloakId) throw new Error("No KeycloakId provided"); - const x = await new CallAPI().GetData( - { - headers: { authorization: token }, - }, - `/org/dotnet/user-logs/${keycloakId}`, - false, - ); - - const data = { - rootDnaId: x.rootDnaId, - }; - - return data; - } - } catch (error) { - console.error("Error calling API:", error); - throw error; - } + return data + } + } catch (error) { + console.error("Error calling API:", error) + throw error + } } public async PermissionCreate(req: RequestWithUser, system: string) { return await this.Permission(req, system, "CREATE"); diff --git a/src/middlewares/auth.ts b/src/middlewares/auth.ts index c358b82..de43a0c 100644 --- a/src/middlewares/auth.ts +++ b/src/middlewares/auth.ts @@ -67,16 +67,6 @@ export async function expressAuthentication( request.app.locals.logData.userName = payload.name; request.app.locals.logData.user = payload.preferred_username; - // เก็บค่า profileId และ orgRootDnaId จาก token (ใช้ค่าว่างถ้าไม่มี) - request.app.locals.logData.profileId = payload.profileId ?? ""; - request.app.locals.logData.orgRootDnaId = payload.orgRootDnaId ?? ""; - request.app.locals.logData.orgChild1DnaId = payload.orgChild1DnaId ?? ""; - request.app.locals.logData.orgChild2DnaId = payload.orgChild2DnaId ?? ""; - request.app.locals.logData.orgChild3DnaId = payload.orgChild3DnaId ?? ""; - request.app.locals.logData.orgChild4DnaId = payload.orgChild4DnaId ?? ""; - request.app.locals.logData.empType = payload.empType ?? ""; - request.app.locals.logData.prefix = payload.prefix ?? ""; - return payload; } diff --git a/src/middlewares/logs.ts b/src/middlewares/logs.ts index 5d18799..f5efaa5 100644 --- a/src/middlewares/logs.ts +++ b/src/middlewares/logs.ts @@ -45,35 +45,21 @@ async function logMiddleware(req: Request, res: Response, next: NextFunction) { if (level === 2 && res.statusCode < 400) return; if (level === 3 && res.statusCode < 200) return; - // const token = req.headers["authorization"]; - // let rootId = null; + const token = req.headers["authorization"]; + let rootId = null; - // try { - // rootId = token - // ? await new permission().checkRootDna(token, req.app.locals.logData.userId) - // : null; - // } catch (err) { - // console.warn("Error fetching rootId:", err); - // } - - // Get rootId from token - const rootId = req.app.locals.logData?.orgRootDnaId; - - let _msg = data?.message; - if (!_msg) { - if (res.statusCode >= 500) { - _msg = "ไม่สำเร็จ"; - } else if (res.statusCode >= 400) { - _msg = "พบข้อผิดพลาด"; - } else if (res.statusCode >= 200) { - _msg = "สำเร็จ"; - } + try { + rootId = token + ? await new permission().checkOrg(token, req.app.locals.logData.userId) + : null; + } catch (err) { + console.warn("Error fetching rootId:", err); } const obj = { logType: res.statusCode >= 500 ? "error" : res.statusCode >= 400 ? "warning" : "info", ip: req.ip, - rootId: rootId ?? null, + rootId: rootId?.orgRootId ?? null, systemName: "salary", startTimeStamp: timestamp, endTimeStamp: new Date().toISOString(), @@ -82,8 +68,7 @@ async function logMiddleware(req: Request, res: Response, next: NextFunction) { method: req.method, endpoint: req.url, responseCode: String(res.statusCode === 304 ? 200 : res.statusCode), - // responseDescription: data?.message, - responseDescription: _msg, + responseDescription: data?.message, input: level === 4 ? JSON.stringify(req.body, null, 2) : undefined, output: level === 4 ? JSON.stringify(data, null, 2) : undefined, ...req.app.locals.logData, diff --git a/src/migration/20260407112136-update_table_posLevel_to_string.ts b/src/migration/20260407112136-update_table_posLevel_to_string.ts deleted file mode 100644 index d5ffc28..0000000 --- a/src/migration/20260407112136-update_table_posLevel_to_string.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { MigrationInterface, QueryRunner } from "typeorm"; - -export class UpdateTablePosLevelToString20260407112136 implements MigrationInterface { - name = 'UpdateTablePosLevelToString20260407112136' - - public async up(queryRunner: QueryRunner): Promise { - await queryRunner.query(`ALTER TABLE \`salaryProfileEmployee\` CHANGE \`posLevel\` \`posLevel\` varchar(255) NULL COMMENT 'ระดับตำแหน่ง'`); - await queryRunner.query(`ALTER TABLE \`employeePosLevel\` CHANGE \`posLevelName\` \`posLevelName\` varchar(255) NOT NULL COMMENT 'ชื่อระดับชั้นงาน'`); - } - - public async down(queryRunner: QueryRunner): Promise { - await queryRunner.query(`ALTER TABLE \`salaryProfileEmployee\` CHANGE \`posLevel\` \`posLevel\` double NULL COMMENT 'ระดับตำแหน่ง'`); - await queryRunner.query(`ALTER TABLE \`employeePosLevel\` CHANGE \`posLevelName\` \`posLevelName\` int NOT NULL COMMENT 'ชื่อระดับชั้นงาน'`); - } - -} diff --git a/src/services/minio.ts b/src/services/minio.ts index 940d5ca..48bd91f 100644 --- a/src/services/minio.ts +++ b/src/services/minio.ts @@ -16,26 +16,6 @@ const minio = new Client({ export default minio; -/** - * ตัดชื่อไฟล์ถ้ายาวเกิน limit - * @param fileName - ชื่อไฟล์เดิม - * @param maxLength - ความยาวสูงสุดของชื่อไฟล์ (default: 50 ตัวอักษร ไม่รวมนามสกุล) - * @returns ชื่อไฟล์ที่ตัดแล้ว - */ -export function truncateFileName(fileName: string, maxLength: number = 50): string { - const dot = fileName.lastIndexOf("."); - const name = dot !== -1 && !fileName.startsWith(".") ? fileName.slice(0, dot) : fileName; - const ext = dot !== -1 && !fileName.startsWith(".") ? fileName.slice(dot) : ""; - - if (name.length <= maxLength) { - return fileName; - } - - // ตัดชื่อและเติม "..." ตามด้วย 5 ตัวสุดท้ายของชื่อเดิม - const truncated = name.slice(0, maxLength - 8) + "..." + name.slice(-5); - return truncated + ext; -} - function exception(e: any): never { console.error(e); throw new Error("เกิดข้อผิดพลาดกับระบบจัดการไฟล์"); @@ -94,11 +74,6 @@ export async function s3DeleteFolder(path: string) { export async function s3UploadFile(replace: boolean, ...pathname: string[]) { if (!pathname.length) return; - // ตัดชื่อไฟล์ถ้ายาวเกิน - const originalFileName = pathname.at(-1) as string; - const truncatedFileName = truncateFileName(originalFileName); - pathname = [...pathname.slice(0, -1), truncatedFileName]; - if (!replace) { const list = await s3ListFile(...pathname.slice(0, -1)).catch(exception); @@ -171,7 +146,7 @@ export async function s3DownloadFile(...pathname: string[]) { } export async function duplicateAvatarFile(refId: string, prefix: string, fileName: string) { - try { + try { await minio.statObject(MINIO_BUCKET, refId); const avatar = `${prefix}/${fileName}`; await minio.copyObject( @@ -184,4 +159,4 @@ export async function duplicateAvatarFile(refId: string, prefix: string, fileNam } } -export const fileLocation = (...path: string[]) => path.join("/"); \ No newline at end of file +export const fileLocation = (...path: string[]) => path.join("/");