Compare commits
No commits in common. "dev" and "version-1.0.185" have entirely different histories.
dev
...
version-1.
14 changed files with 320 additions and 1537 deletions
|
|
@ -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
|
|
||||||
|
|
@ -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 }}
|
|
||||||
|
|
@ -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 }}"
|
|
||||||
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -131,5 +131,3 @@ dist
|
||||||
.yarn/build-state.yml
|
.yarn/build-state.yml
|
||||||
.yarn/install-state.gz
|
.yarn/install-state.gz
|
||||||
.pnp.*
|
.pnp.*
|
||||||
|
|
||||||
.claude
|
|
||||||
|
|
@ -22,7 +22,7 @@ import {
|
||||||
listFile,
|
listFile,
|
||||||
updateFile,
|
updateFile,
|
||||||
} from "../services/edm";
|
} 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}")
|
@Route("api/v1/salary/file/{name}/{group}")
|
||||||
@Security("bearerAuth")
|
@Security("bearerAuth")
|
||||||
|
|
@ -257,7 +257,7 @@ export class FileController extends Controller {
|
||||||
|
|
||||||
const map = fileList.map(async ({ fileName, ...props }) => [
|
const map = fileList.map(async ({ fileName, ...props }) => [
|
||||||
fileName,
|
fileName,
|
||||||
await createFile(path, truncateFileName(fileName), props),
|
await createFile(path, fileName, props),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const result = await Promise.all(map).catch((e) =>
|
const result = await Promise.all(map).catch((e) =>
|
||||||
|
|
@ -607,7 +607,7 @@ export class SubFileController extends Controller {
|
||||||
|
|
||||||
const map = fileList.map(async ({ fileName, ...props }) => [
|
const map = fileList.map(async ({ fileName, ...props }) => [
|
||||||
fileName,
|
fileName,
|
||||||
await createFile(path, truncateFileName(fileName), props),
|
await createFile(path, fileName, props),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const result = await Promise.all(map).catch((e) =>
|
const result = await Promise.all(map).catch((e) =>
|
||||||
|
|
|
||||||
|
|
@ -470,7 +470,7 @@ export class SalaryPeriodController extends Controller {
|
||||||
const Level = await this.posLevelRepository.findOne({
|
const Level = await this.posLevelRepository.findOne({
|
||||||
where: {
|
where: {
|
||||||
posTypeId: Type.id,
|
posTypeId: Type.id,
|
||||||
posLevelName: salaryProfile.posLevel?.split("/")[0],
|
posLevelName: salaryProfile.posLevel,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
if (!Level) {
|
if (!Level) {
|
||||||
|
|
@ -489,7 +489,7 @@ export class SalaryPeriodController extends Controller {
|
||||||
throw new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบระดับตำแหน่ง");
|
throw new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบระดับตำแหน่ง");
|
||||||
}
|
}
|
||||||
let type = salaryProfile.type;
|
let type = salaryProfile.type;
|
||||||
//SalaryRanks
|
//SalaryRank
|
||||||
let salaryRanks: any = null;
|
let salaryRanks: any = null;
|
||||||
if (salaryProfile.amount != null) {
|
if (salaryProfile.amount != null) {
|
||||||
salaryRanks = await this.salaryRankRepository.findOne({
|
salaryRanks = await this.salaryRankRepository.findOne({
|
||||||
|
|
@ -935,7 +935,7 @@ export class SalaryPeriodController extends Controller {
|
||||||
const Level = await this.posLevelRepository.findOne({
|
const Level = await this.posLevelRepository.findOne({
|
||||||
where: {
|
where: {
|
||||||
posTypeId: Type.id,
|
posTypeId: Type.id,
|
||||||
posLevelName: salaryProfile.posLevel?.split("/")[0],
|
posLevelName: salaryProfile.posLevel,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
if (!Level) {
|
if (!Level) {
|
||||||
|
|
@ -957,7 +957,7 @@ export class SalaryPeriodController extends Controller {
|
||||||
let _null: any = null;
|
let _null: any = null;
|
||||||
salaryProfile.remark = body.remark == null ? _null : body.remark;
|
salaryProfile.remark = body.remark == null ? _null : body.remark;
|
||||||
let type = salaryProfile.type;
|
let type = salaryProfile.type;
|
||||||
//SalaryRanks
|
//SalaryRank
|
||||||
let salaryRanks: any = null;
|
let salaryRanks: any = null;
|
||||||
if (salaryProfile.amount != null) {
|
if (salaryProfile.amount != null) {
|
||||||
salaryRanks = await this.salaryRankRepository.findOne({
|
salaryRanks = await this.salaryRankRepository.findOne({
|
||||||
|
|
@ -1200,7 +1200,6 @@ export class SalaryPeriodController extends Controller {
|
||||||
return new HttpSuccess();
|
return new HttpSuccess();
|
||||||
}
|
}
|
||||||
|
|
||||||
//OLD CHANGE TYPE-MULTI
|
|
||||||
/**
|
/**
|
||||||
* API แก้ไขขั้น
|
* API แก้ไขขั้น
|
||||||
*
|
*
|
||||||
|
|
@ -1209,8 +1208,8 @@ export class SalaryPeriodController extends Controller {
|
||||||
* @param {string} id profile Id
|
* @param {string} id profile Id
|
||||||
* @param {string} type ประเภทการเลื่อน NONE->ไม่ได้เลื่อน HAFT->ครึ่งขั้น FULL->1ขั้น FULLHAFT->1.5ขั้น
|
* @param {string} type ประเภทการเลื่อน NONE->ไม่ได้เลื่อน HAFT->ครึ่งขั้น FULL->1ขั้น FULLHAFT->1.5ขั้น
|
||||||
*/
|
*/
|
||||||
@Post("oldchange/type-multi")
|
@Post("change/type-multi")
|
||||||
async oldchangeTypeMulti(
|
async changeTypeMulti(
|
||||||
@Body() body: { profileId: string[]; type: string; isReserve: boolean; remark?: string | null },
|
@Body() body: { profileId: string[]; type: string; isReserve: boolean; remark?: string | null },
|
||||||
@Request() req: RequestWithUser,
|
@Request() req: RequestWithUser,
|
||||||
) {
|
) {
|
||||||
|
|
@ -1272,7 +1271,7 @@ export class SalaryPeriodController extends Controller {
|
||||||
const Level = await this.posLevelRepository.findOne({
|
const Level = await this.posLevelRepository.findOne({
|
||||||
where: {
|
where: {
|
||||||
posTypeId: Type.id,
|
posTypeId: Type.id,
|
||||||
posLevelName: salaryProfile.posLevel?.split("/")[0],
|
posLevelName: salaryProfile.posLevel,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
if (!Level) {
|
if (!Level) {
|
||||||
|
|
@ -1294,7 +1293,7 @@ export class SalaryPeriodController extends Controller {
|
||||||
let _null: any = null;
|
let _null: any = null;
|
||||||
salaryProfile.remark = body.remark == null ? _null : body.remark;
|
salaryProfile.remark = body.remark == null ? _null : body.remark;
|
||||||
let type = salaryProfile.type;
|
let type = salaryProfile.type;
|
||||||
//SalaryRanks
|
//SalaryRank
|
||||||
let salaryRanks: any = null;
|
let salaryRanks: any = null;
|
||||||
if (salaryProfile.amount != null) {
|
if (salaryProfile.amount != null) {
|
||||||
salaryRanks = await this.salaryRankRepository.findOne({
|
salaryRanks = await this.salaryRankRepository.findOne({
|
||||||
|
|
@ -1544,653 +1543,6 @@ export class SalaryPeriodController extends Controller {
|
||||||
return new HttpSuccess();
|
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<number, SalaryRanks[]>,
|
|
||||||
// );
|
|
||||||
|
|
||||||
// const profilesToSave: SalaryProfile[] = [];
|
|
||||||
// const orgNeedRecalc = new Set<string>();
|
|
||||||
|
|
||||||
// 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<T>(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<string, any>,
|
|
||||||
posLevelCache: Map<string, any>,
|
|
||||||
salaryCache: Map<string, any>,
|
|
||||||
affectedOrgIds: Set<string>,
|
|
||||||
) {
|
|
||||||
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<string, any>();
|
|
||||||
const posLevelCache = new Map<string, any>();
|
|
||||||
const salaryCache = new Map<string, any>();
|
|
||||||
|
|
||||||
// org ที่ต้อง recalc
|
|
||||||
const affectedOrgIds = new Set<string>();
|
|
||||||
|
|
||||||
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 รายการอัตราเงินเดือน
|
* API รายการอัตราเงินเดือน
|
||||||
*
|
*
|
||||||
|
|
@ -2300,7 +1652,6 @@ export class SalaryPeriodController extends Controller {
|
||||||
_data.child1 != undefined && _data.child1 != null
|
_data.child1 != undefined && _data.child1 != null
|
||||||
? _data.child1[0] != null
|
? _data.child1[0] != null
|
||||||
? `child1Id IN (:...child1)`
|
? `child1Id IN (:...child1)`
|
||||||
// : `child1Id is ${_data.privilege == "PARENT" ? "not null" : "null"}`
|
|
||||||
: `child1Id is null`
|
: `child1Id is null`
|
||||||
: "1=1",
|
: "1=1",
|
||||||
{
|
{
|
||||||
|
|
@ -2336,7 +1687,7 @@ export class SalaryPeriodController extends Controller {
|
||||||
{
|
{
|
||||||
child4: _data.child4,
|
child4: _data.child4,
|
||||||
},
|
},
|
||||||
);
|
)
|
||||||
|
|
||||||
if (body.sortBy) {
|
if (body.sortBy) {
|
||||||
if(body.sortBy === "posExecutive"){
|
if(body.sortBy === "posExecutive"){
|
||||||
|
|
@ -2344,16 +1695,18 @@ export class SalaryPeriodController extends Controller {
|
||||||
.orderBy( `profile.posExecutive`,body.descending ? "DESC" : "ASC")
|
.orderBy( `profile.posExecutive`,body.descending ? "DESC" : "ASC")
|
||||||
.addOrderBy( `profile.positionExecutiveField`,body.descending ? "DESC" : "ASC");
|
.addOrderBy( `profile.positionExecutiveField`,body.descending ? "DESC" : "ASC");
|
||||||
}else{
|
}else{
|
||||||
query = query.orderBy(`profile.${body.sortBy}`, body.descending ? "DESC" : "ASC");
|
query = query.orderBy(
|
||||||
|
`profile.${body.sortBy}`,
|
||||||
|
body.descending ? "DESC" : "ASC"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}else{
|
}else{
|
||||||
query = query
|
query = query.orderBy("profile.rootOrder", "ASC")
|
||||||
.orderBy("profile.rootOrder", "ASC")
|
|
||||||
.addOrderBy("profile.child1Order", "ASC")
|
.addOrderBy("profile.child1Order", "ASC")
|
||||||
.addOrderBy("profile.child2Order", "ASC")
|
.addOrderBy("profile.child2Order", "ASC")
|
||||||
.addOrderBy("profile.child3Order", "ASC")
|
.addOrderBy("profile.child3Order", "ASC")
|
||||||
.addOrderBy("profile.child4Order", "ASC")
|
.addOrderBy("profile.child4Order", "ASC")
|
||||||
.addOrderBy("profile.posMasterNo", "ASC");
|
.addOrderBy("profile.posMasterNo", "ASC")
|
||||||
}
|
}
|
||||||
|
|
||||||
const [salaryProfile, total] = await query
|
const [salaryProfile, total] = await query
|
||||||
|
|
@ -2474,7 +1827,7 @@ export class SalaryPeriodController extends Controller {
|
||||||
const posLevel = await this.posLevelRepository.findOne({
|
const posLevel = await this.posLevelRepository.findOne({
|
||||||
where: {
|
where: {
|
||||||
posTypeId: posType.id,
|
posTypeId: posType.id,
|
||||||
posLevelName: salaryProfile.posLevel?.split("/")[0],
|
posLevelName: salaryProfile.posLevel,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
if (!posLevel) {
|
if (!posLevel) {
|
||||||
|
|
@ -2493,7 +1846,7 @@ export class SalaryPeriodController extends Controller {
|
||||||
throw new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบระดับตำแหน่ง");
|
throw new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบระดับตำแหน่ง");
|
||||||
}
|
}
|
||||||
let type = salaryProfile.type;
|
let type = salaryProfile.type;
|
||||||
//SalaryRanks
|
//SalaryRank
|
||||||
let salaryRanks: any = null;
|
let salaryRanks: any = null;
|
||||||
if (salaryProfile.amount != null) {
|
if (salaryProfile.amount != null) {
|
||||||
salaryRanks = await this.salaryRankRepository.findOne({
|
salaryRanks = await this.salaryRankRepository.findOne({
|
||||||
|
|
@ -3182,14 +2535,16 @@ export class SalaryPeriodController extends Controller {
|
||||||
"salaryPeriod.status",
|
"salaryPeriod.status",
|
||||||
"salaryPeriod.year",
|
"salaryPeriod.year",
|
||||||
"salaryPeriod.revisionId",
|
"salaryPeriod.revisionId",
|
||||||
]);
|
])
|
||||||
|
|
||||||
if (sortBy) {
|
if (sortBy) {
|
||||||
query = query.orderBy(`salaryPeriod.${sortBy}`, descending ? "DESC" : "ASC");
|
query = query.orderBy(
|
||||||
|
`salaryPeriod.${sortBy}`,
|
||||||
|
descending ? "DESC" : "ASC"
|
||||||
|
);
|
||||||
}else{
|
}else{
|
||||||
query = query
|
query = query.orderBy("salaryPeriod.year", "DESC")
|
||||||
.orderBy("salaryPeriod.year", "DESC")
|
.addOrderBy("salaryPeriod.effectiveDate", "DESC")
|
||||||
.addOrderBy("salaryPeriod.effectiveDate", "DESC");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const [salaryPeriod, total] = await query
|
const [salaryPeriod, total] = await query
|
||||||
|
|
@ -4573,40 +3928,22 @@ export class SalaryPeriodController extends Controller {
|
||||||
// Parallel loading of initial data and API calls
|
// Parallel loading of initial data and API calls
|
||||||
console.time("⏱ API: Load initial data");
|
console.time("⏱ API: Load initial data");
|
||||||
const [orgs, revisionId, _orgProfiles, _orgProfileEmployees] = await Promise.all([
|
const [orgs, revisionId, _orgProfiles, _orgProfileEmployees] = await Promise.all([
|
||||||
new CallAPI().GetData(
|
new CallAPI().GetData({ headers: { authorization: request } }, "/org/unauthorize/active/root/id",false),
|
||||||
{ headers: { authorization: request } },
|
new CallAPI().GetData({ headers: { authorization: request } }, "/org/unauthorize/revision/latest",false),
|
||||||
"/org/unauthorize/active/root/id",
|
new CallAPI().PostData({ headers: { authorization: request } }, "/org/unauthorize/new-salary/gen", {
|
||||||
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,
|
page: 1,
|
||||||
pageSize: 1000,
|
pageSize: 1000,
|
||||||
keyword: "",
|
keyword: "",
|
||||||
year: salaryPeriod.year,
|
year: salaryPeriod.year,
|
||||||
period: salaryPeriod.period,
|
period: salaryPeriod.period,
|
||||||
},
|
},false),
|
||||||
false,
|
new CallAPI().PostData({ headers: { authorization: request } }, "/org/unauthorize/new-salary/employee/gen", {
|
||||||
),
|
|
||||||
new CallAPI().PostData(
|
|
||||||
{ headers: { authorization: request } },
|
|
||||||
"/org/unauthorize/new-salary/employee/gen",
|
|
||||||
{
|
|
||||||
page: 1,
|
page: 1,
|
||||||
pageSize: 1000,
|
pageSize: 1000,
|
||||||
keyword: "",
|
keyword: "",
|
||||||
year: salaryPeriod.year,
|
year: salaryPeriod.year,
|
||||||
period: salaryPeriod.period,
|
period: salaryPeriod.period,
|
||||||
},
|
},false),
|
||||||
false,
|
|
||||||
),
|
|
||||||
]);
|
]);
|
||||||
console.timeEnd("⏱ API: Load initial data");
|
console.timeEnd("⏱ API: Load initial data");
|
||||||
|
|
||||||
|
|
@ -4621,18 +3958,13 @@ export class SalaryPeriodController extends Controller {
|
||||||
const promises = [];
|
const promises = [];
|
||||||
for (let index = 2; index <= page; index++) {
|
for (let index = 2; index <= page; index++) {
|
||||||
promises.push(
|
promises.push(
|
||||||
new CallAPI().PostData(
|
new CallAPI().PostData({ headers: { authorization: request } }, "/org/unauthorize/new-salary/gen", {
|
||||||
{ headers: { authorization: request } },
|
|
||||||
"/org/unauthorize/new-salary/gen",
|
|
||||||
{
|
|
||||||
page: index,
|
page: index,
|
||||||
pageSize: 1000,
|
pageSize: 1000,
|
||||||
keyword: "",
|
keyword: "",
|
||||||
year: salaryPeriod.year,
|
year: salaryPeriod.year,
|
||||||
period: salaryPeriod.period,
|
period: salaryPeriod.period,
|
||||||
},
|
},false),
|
||||||
false,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
const results = await Promise.all(promises);
|
const results = await Promise.all(promises);
|
||||||
|
|
@ -4651,18 +3983,13 @@ export class SalaryPeriodController extends Controller {
|
||||||
const promises = [];
|
const promises = [];
|
||||||
for (let index = 2; index <= page; index++) {
|
for (let index = 2; index <= page; index++) {
|
||||||
promises.push(
|
promises.push(
|
||||||
new CallAPI().PostData(
|
new CallAPI().PostData({ headers: { authorization: request } }, "/org/unauthorize/new-salary/employee/gen", {
|
||||||
{ headers: { authorization: request } },
|
|
||||||
"/org/unauthorize/new-salary/employee/gen",
|
|
||||||
{
|
|
||||||
page: index,
|
page: index,
|
||||||
pageSize: 1000,
|
pageSize: 1000,
|
||||||
keyword: "",
|
keyword: "",
|
||||||
year: salaryPeriod.year,
|
year: salaryPeriod.year,
|
||||||
period: salaryPeriod.period,
|
period: salaryPeriod.period,
|
||||||
},
|
},false),
|
||||||
false,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
const results = await Promise.all(promises);
|
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.remainQuota = Math.floor((_salaryOrg.salaryProfiles.length * 15) / 100);
|
||||||
}
|
}
|
||||||
|
|
||||||
_salaryOrg.createdUserId = "";
|
_salaryOrg.createdUserId = ""
|
||||||
_salaryOrg.createdFullName = "System Administrator";
|
_salaryOrg.createdFullName = "System Administrator";
|
||||||
_salaryOrg.lastUpdateUserId = "";
|
_salaryOrg.lastUpdateUserId = ""
|
||||||
_salaryOrg.lastUpdateFullName = "System Administrator";
|
_salaryOrg.lastUpdateFullName = "System Administrator";
|
||||||
_salaryOrg.createdAt = new Date();
|
_salaryOrg.createdAt = new Date();
|
||||||
_salaryOrg.lastUpdatedAt = new Date();
|
_salaryOrg.lastUpdatedAt = new Date();
|
||||||
|
|
@ -5388,8 +4715,7 @@ export class SalaryPeriodController extends Controller {
|
||||||
);
|
);
|
||||||
let request:any = response.data.access_token;
|
let request:any = response.data.access_token;
|
||||||
|
|
||||||
if (current.getDate() == 1 && current.getMonth() == 2) {
|
if (current.getDate() == 1 && current.getMonth() == 2) { //snap1 วันที่ 1 มีนา
|
||||||
//snap1 วันที่ 1 มีนา
|
|
||||||
salaryPeriod = await this.salaryPeriodRepository.findOne({
|
salaryPeriod = await this.salaryPeriodRepository.findOne({
|
||||||
where: {
|
where: {
|
||||||
year: current.getFullYear(),
|
year: current.getFullYear(),
|
||||||
|
|
@ -5400,8 +4726,7 @@ export class SalaryPeriodController extends Controller {
|
||||||
if (salaryPeriod) {
|
if (salaryPeriod) {
|
||||||
this.performSnapshotOperationForCronjob("SNAP1", salaryPeriod.id, request);
|
this.performSnapshotOperationForCronjob("SNAP1", salaryPeriod.id, request);
|
||||||
}
|
}
|
||||||
} else if (current.getDate() == 1 && current.getMonth() == 3) {
|
} else if (current.getDate() == 1 && current.getMonth() == 3) { //snap2 วันที่ 1 เมษา
|
||||||
//snap2 วันที่ 1 เมษา
|
|
||||||
salaryPeriod = await this.salaryPeriodRepository.findOne({
|
salaryPeriod = await this.salaryPeriodRepository.findOne({
|
||||||
where: {
|
where: {
|
||||||
year: current.getFullYear(),
|
year: current.getFullYear(),
|
||||||
|
|
@ -5412,8 +4737,7 @@ export class SalaryPeriodController extends Controller {
|
||||||
if (salaryPeriod) {
|
if (salaryPeriod) {
|
||||||
this.performSnapshotOperationForCronjob("SNAP2", salaryPeriod.id, request);
|
this.performSnapshotOperationForCronjob("SNAP2", salaryPeriod.id, request);
|
||||||
}
|
}
|
||||||
} else if (current.getDate() == 1 && current.getMonth() == 8) {
|
} else if (current.getDate() == 1 && current.getMonth() == 8) { //snap1 วันที่ 1 กันยา
|
||||||
//snap1 วันที่ 1 กันยา
|
|
||||||
salaryPeriod = await this.salaryPeriodRepository.findOne({
|
salaryPeriod = await this.salaryPeriodRepository.findOne({
|
||||||
where: {
|
where: {
|
||||||
year: current.getFullYear(),
|
year: current.getFullYear(),
|
||||||
|
|
@ -5424,8 +4748,7 @@ export class SalaryPeriodController extends Controller {
|
||||||
if (salaryPeriod) {
|
if (salaryPeriod) {
|
||||||
this.performSnapshotOperationForCronjob("SNAP1", salaryPeriod.id, request);
|
this.performSnapshotOperationForCronjob("SNAP1", salaryPeriod.id, request);
|
||||||
}
|
}
|
||||||
} else if (current.getDate() == 1 && current.getMonth() == 9) {
|
} else if (current.getDate() == 1 && current.getMonth() == 9) { //snap2 วันที่ 1 ตุลา
|
||||||
//snap2 วันที่ 1 ตุลา
|
|
||||||
salaryPeriod = await this.salaryPeriodRepository.findOne({
|
salaryPeriod = await this.salaryPeriodRepository.findOne({
|
||||||
where: {
|
where: {
|
||||||
year: current.getFullYear(),
|
year: current.getFullYear(),
|
||||||
|
|
|
||||||
|
|
@ -430,7 +430,7 @@ export class SalaryPeriodEmployeeController extends Controller {
|
||||||
const Level = await this.posLevelRepository.findOne({
|
const Level = await this.posLevelRepository.findOne({
|
||||||
where: {
|
where: {
|
||||||
posTypeId: Type.id,
|
posTypeId: Type.id,
|
||||||
posLevelName: salaryProfile.posLevel?.split("/")[0],
|
posLevelName: salaryProfile.posLevel,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
if (!Level) {
|
if (!Level) {
|
||||||
|
|
@ -750,7 +750,7 @@ export class SalaryPeriodEmployeeController extends Controller {
|
||||||
const Level = await this.posLevelRepository.findOne({
|
const Level = await this.posLevelRepository.findOne({
|
||||||
where: {
|
where: {
|
||||||
posTypeId: Type.id,
|
posTypeId: Type.id,
|
||||||
posLevelName: salaryProfile.posLevel?.split("/")[0],
|
posLevelName: salaryProfile.posLevel,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
if (!Level) {
|
if (!Level) {
|
||||||
|
|
@ -879,8 +879,8 @@ export class SalaryPeriodEmployeeController extends Controller {
|
||||||
* @param {string} id profile Id
|
* @param {string} id profile Id
|
||||||
* @param {string} type ประเภทการเลื่อน NONE->ไม่ได้เลื่อน HAFT->ครึ่งขั้น FULL->1ขั้น FULLHAFT->1.5ขั้น
|
* @param {string} type ประเภทการเลื่อน NONE->ไม่ได้เลื่อน HAFT->ครึ่งขั้น FULL->1ขั้น FULLHAFT->1.5ขั้น
|
||||||
*/
|
*/
|
||||||
@Post("oldchange/type-multi")
|
@Post("change/type-multi")
|
||||||
async oldchangeTypeMulti(
|
async changeTypeMulti(
|
||||||
@Body() body: { profileId: string[]; type: string; isReserve: boolean; remark?: string | null },
|
@Body() body: { profileId: string[]; type: string; isReserve: boolean; remark?: string | null },
|
||||||
@Request() req: RequestWithUser,
|
@Request() req: RequestWithUser,
|
||||||
) {
|
) {
|
||||||
|
|
@ -941,7 +941,7 @@ export class SalaryPeriodEmployeeController extends Controller {
|
||||||
const Level = await this.posLevelRepository.findOne({
|
const Level = await this.posLevelRepository.findOne({
|
||||||
where: {
|
where: {
|
||||||
posTypeId: Type.id,
|
posTypeId: Type.id,
|
||||||
posLevelName: salaryProfile.posLevel?.split("/")[0],
|
posLevelName: salaryProfile.posLevel,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
if (!Level) {
|
if (!Level) {
|
||||||
|
|
@ -952,7 +952,7 @@ export class SalaryPeriodEmployeeController extends Controller {
|
||||||
salaryProfile.remark = body.remark == null ? _null : body.remark;
|
salaryProfile.remark = body.remark == null ? _null : body.remark;
|
||||||
let type = salaryProfile.type;
|
let type = salaryProfile.type;
|
||||||
|
|
||||||
salaryProfile = await this.calSalaryNew(type, salaryProfile);
|
salaryProfile = await this.calSalary(type, salaryProfile);
|
||||||
|
|
||||||
salaryProfile.lastUpdateUserId = req.user.sub;
|
salaryProfile.lastUpdateUserId = req.user.sub;
|
||||||
salaryProfile.lastUpdateFullName = req.user.name;
|
salaryProfile.lastUpdateFullName = req.user.name;
|
||||||
|
|
@ -1063,264 +1063,6 @@ export class SalaryPeriodEmployeeController extends Controller {
|
||||||
return new HttpSuccess();
|
return new HttpSuccess();
|
||||||
}
|
}
|
||||||
|
|
||||||
private chunkArray<T>(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<string>,
|
|
||||||
) {
|
|
||||||
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<string>();
|
|
||||||
|
|
||||||
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 รายการอัตราเงินเดือน
|
* API รายการอัตราเงินเดือน
|
||||||
*
|
*
|
||||||
|
|
@ -1597,7 +1339,7 @@ export class SalaryPeriodEmployeeController extends Controller {
|
||||||
const posLevel = await this.posLevelRepository.findOne({
|
const posLevel = await this.posLevelRepository.findOne({
|
||||||
where: {
|
where: {
|
||||||
posTypeId: posType.id,
|
posTypeId: posType.id,
|
||||||
posLevelName: salaryProfile.posLevel?.split("/")[0],
|
posLevelName: salaryProfile.posLevel,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
if (!posLevel) {
|
if (!posLevel) {
|
||||||
|
|
|
||||||
|
|
@ -12,9 +12,9 @@ enum EmployeePosLevelAuthoritys {
|
||||||
export class EmployeePosLevel extends EntityBase {
|
export class EmployeePosLevel extends EntityBase {
|
||||||
@Column({
|
@Column({
|
||||||
comment: "ชื่อระดับชั้นงาน",
|
comment: "ชื่อระดับชั้นงาน",
|
||||||
length: 255,
|
type: "int",
|
||||||
})
|
})
|
||||||
posLevelName: string;
|
posLevelName: number;
|
||||||
|
|
||||||
@Column({
|
@Column({
|
||||||
comment: "ระดับของระดับชั้นงาน",
|
comment: "ระดับของระดับชั้นงาน",
|
||||||
|
|
|
||||||
|
|
@ -134,7 +134,7 @@ export class SalaryProfileEmployee extends EntityBase {
|
||||||
comment: "ระดับตำแหน่ง",
|
comment: "ระดับตำแหน่ง",
|
||||||
default: null,
|
default: null,
|
||||||
})
|
})
|
||||||
posLevel: string;
|
posLevel: number;
|
||||||
|
|
||||||
@Column({
|
@Column({
|
||||||
type: "double",
|
type: "double",
|
||||||
|
|
@ -482,7 +482,7 @@ export class CreateSalaryProfileEmployee {
|
||||||
posType: string | null;
|
posType: string | null;
|
||||||
|
|
||||||
@Column()
|
@Column()
|
||||||
posLevel: string | null;
|
posLevel: number | null;
|
||||||
|
|
||||||
@Column()
|
@Column()
|
||||||
group: number | null;
|
group: number | null;
|
||||||
|
|
|
||||||
|
|
@ -94,17 +94,6 @@ class CheckAuth {
|
||||||
child4: null,
|
child4: null,
|
||||||
privilege: "ROOT",
|
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") {
|
} else if (privilege == "CHILD") {
|
||||||
data = {
|
data = {
|
||||||
root: node >= 0 ? [x.orgRootId] : null,
|
root: node >= 0 ? [x.orgRootId] : null,
|
||||||
|
|
@ -114,15 +103,6 @@ class CheckAuth {
|
||||||
child4: node >= 4 ? [x.orgChild4Id] : null,
|
child4: node >= 4 ? [x.orgChild4Id] : null,
|
||||||
privilege: "CHILD",
|
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") {
|
} else if (privilege == "NORMAL") {
|
||||||
data = {
|
data = {
|
||||||
root: [x.orgRootId],
|
root: [x.orgRootId],
|
||||||
|
|
@ -207,21 +187,21 @@ class CheckAuth {
|
||||||
const redisClient = await this.redis.createClient({
|
const redisClient = await this.redis.createClient({
|
||||||
host: process.env.REDIS_HOST,
|
host: process.env.REDIS_HOST,
|
||||||
port: process.env.REDIS_PORT,
|
port: process.env.REDIS_PORT,
|
||||||
});
|
})
|
||||||
const getAsync = promisify(redisClient.get).bind(redisClient);
|
const getAsync = promisify(redisClient.get).bind(redisClient)
|
||||||
try {
|
try {
|
||||||
let reply = await getAsync("org_" + keycloakId);
|
let reply = await getAsync("org_" + keycloakId)
|
||||||
if (reply != null) {
|
if (reply != null) {
|
||||||
reply = JSON.parse(reply);
|
reply = JSON.parse(reply)
|
||||||
} else {
|
} else {
|
||||||
if (!keycloakId) throw new Error("No KeycloakId provided");
|
if (!keycloakId) throw new Error("No KeycloakId provided")
|
||||||
const x = await new CallAPI().GetData(
|
const x = await new CallAPI().GetData(
|
||||||
{
|
{
|
||||||
headers: { authorization: token },
|
headers: { authorization: token },
|
||||||
},
|
},
|
||||||
`/org/permission/checkOrg/${keycloakId}`,
|
`/org/permission/checkOrg/${keycloakId}`,
|
||||||
false,
|
false
|
||||||
);
|
)
|
||||||
|
|
||||||
const data = {
|
const data = {
|
||||||
orgRootId: x.orgRootId,
|
orgRootId: x.orgRootId,
|
||||||
|
|
@ -229,45 +209,13 @@ class CheckAuth {
|
||||||
orgChild2Id: x.orgChild2Id,
|
orgChild2Id: x.orgChild2Id,
|
||||||
orgChild3Id: x.orgChild3Id,
|
orgChild3Id: x.orgChild3Id,
|
||||||
orgChild4Id: x.orgChild4Id,
|
orgChild4Id: x.orgChild4Id,
|
||||||
};
|
}
|
||||||
|
|
||||||
return data;
|
return data
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error calling API:", error);
|
console.error("Error calling API:", error)
|
||||||
throw 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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public async PermissionCreate(req: RequestWithUser, system: string) {
|
public async PermissionCreate(req: RequestWithUser, system: string) {
|
||||||
|
|
|
||||||
|
|
@ -67,16 +67,6 @@ export async function expressAuthentication(
|
||||||
request.app.locals.logData.userName = payload.name;
|
request.app.locals.logData.userName = payload.name;
|
||||||
request.app.locals.logData.user = payload.preferred_username;
|
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;
|
return payload;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -45,35 +45,21 @@ async function logMiddleware(req: Request, res: Response, next: NextFunction) {
|
||||||
if (level === 2 && res.statusCode < 400) return;
|
if (level === 2 && res.statusCode < 400) return;
|
||||||
if (level === 3 && res.statusCode < 200) return;
|
if (level === 3 && res.statusCode < 200) return;
|
||||||
|
|
||||||
// const token = req.headers["authorization"];
|
const token = req.headers["authorization"];
|
||||||
// let rootId = null;
|
let rootId = null;
|
||||||
|
|
||||||
// try {
|
try {
|
||||||
// rootId = token
|
rootId = token
|
||||||
// ? await new permission().checkRootDna(token, req.app.locals.logData.userId)
|
? await new permission().checkOrg(token, req.app.locals.logData.userId)
|
||||||
// : null;
|
: null;
|
||||||
// } catch (err) {
|
} catch (err) {
|
||||||
// console.warn("Error fetching rootId:", 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 = "สำเร็จ";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const obj = {
|
const obj = {
|
||||||
logType: res.statusCode >= 500 ? "error" : res.statusCode >= 400 ? "warning" : "info",
|
logType: res.statusCode >= 500 ? "error" : res.statusCode >= 400 ? "warning" : "info",
|
||||||
ip: req.ip,
|
ip: req.ip,
|
||||||
rootId: rootId ?? null,
|
rootId: rootId?.orgRootId ?? null,
|
||||||
systemName: "salary",
|
systemName: "salary",
|
||||||
startTimeStamp: timestamp,
|
startTimeStamp: timestamp,
|
||||||
endTimeStamp: new Date().toISOString(),
|
endTimeStamp: new Date().toISOString(),
|
||||||
|
|
@ -82,8 +68,7 @@ async function logMiddleware(req: Request, res: Response, next: NextFunction) {
|
||||||
method: req.method,
|
method: req.method,
|
||||||
endpoint: req.url,
|
endpoint: req.url,
|
||||||
responseCode: String(res.statusCode === 304 ? 200 : res.statusCode),
|
responseCode: String(res.statusCode === 304 ? 200 : res.statusCode),
|
||||||
// responseDescription: data?.message,
|
responseDescription: data?.message,
|
||||||
responseDescription: _msg,
|
|
||||||
input: level === 4 ? JSON.stringify(req.body, null, 2) : undefined,
|
input: level === 4 ? JSON.stringify(req.body, null, 2) : undefined,
|
||||||
output: level === 4 ? JSON.stringify(data, null, 2) : undefined,
|
output: level === 4 ? JSON.stringify(data, null, 2) : undefined,
|
||||||
...req.app.locals.logData,
|
...req.app.locals.logData,
|
||||||
|
|
|
||||||
|
|
@ -1,16 +0,0 @@
|
||||||
import { MigrationInterface, QueryRunner } from "typeorm";
|
|
||||||
|
|
||||||
export class UpdateTablePosLevelToString20260407112136 implements MigrationInterface {
|
|
||||||
name = 'UpdateTablePosLevelToString20260407112136'
|
|
||||||
|
|
||||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
|
||||||
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<void> {
|
|
||||||
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 'ชื่อระดับชั้นงาน'`);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -16,26 +16,6 @@ const minio = new Client({
|
||||||
|
|
||||||
export default minio;
|
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 {
|
function exception(e: any): never {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
throw new Error("เกิดข้อผิดพลาดกับระบบจัดการไฟล์");
|
throw new Error("เกิดข้อผิดพลาดกับระบบจัดการไฟล์");
|
||||||
|
|
@ -94,11 +74,6 @@ export async function s3DeleteFolder(path: string) {
|
||||||
export async function s3UploadFile(replace: boolean, ...pathname: string[]) {
|
export async function s3UploadFile(replace: boolean, ...pathname: string[]) {
|
||||||
if (!pathname.length) return;
|
if (!pathname.length) return;
|
||||||
|
|
||||||
// ตัดชื่อไฟล์ถ้ายาวเกิน
|
|
||||||
const originalFileName = pathname.at(-1) as string;
|
|
||||||
const truncatedFileName = truncateFileName(originalFileName);
|
|
||||||
pathname = [...pathname.slice(0, -1), truncatedFileName];
|
|
||||||
|
|
||||||
if (!replace) {
|
if (!replace) {
|
||||||
const list = await s3ListFile(...pathname.slice(0, -1)).catch(exception);
|
const list = await s3ListFile(...pathname.slice(0, -1)).catch(exception);
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue