Compare commits
52 commits
version-1.
...
dev
| Author | SHA1 | Date | |
|---|---|---|---|
| 5727c79cc2 | |||
| 2bcd7506a4 | |||
|
|
31c16befa7 | ||
|
|
e6779f8ec7 | ||
|
|
ce4a8aa94a | ||
|
|
e4e7edff3a | ||
|
|
9b01ff3a04 | ||
|
|
e0fae329ee | ||
|
|
523bc0aa81 | ||
|
|
6c60c3636c | ||
|
|
161e3ba71c | ||
|
|
a22d4cf515 | ||
|
|
f5766f302d | ||
| fc0304c221 | |||
| d45980e434 | |||
|
|
a45faaff8f | ||
|
|
4f841566d7 | ||
| ab5e915a59 | |||
| 7fd6531f53 | |||
| 418cad4817 | |||
| 8ee823a4d8 | |||
| 001a026e43 | |||
| 1fec0fcc8c | |||
| 83964e1198 | |||
| 1682f8ec5a | |||
| b47c37e325 | |||
|
|
7bc7645443 | ||
|
|
cbf5f2599d | ||
| 10ae9c0d13 | |||
| dfb3e82676 | |||
| 3aeb893d55 | |||
| aeecbd8ae9 | |||
| 0d639a0f8a | |||
| cbcdae7b71 | |||
| 72916bd518 | |||
| 508cb56430 | |||
| cd862df3b1 | |||
| 6c7b7d333e | |||
| 0446269014 | |||
| ae36f73ebe | |||
| 3d0f228b8e | |||
| d8be027583 | |||
| f4a2993a32 | |||
| d5e7a721ac | |||
|
|
d299f86f86 | ||
| 6479cbe331 | |||
| 1130b31ee5 | |||
| ffe9e03a91 | |||
|
|
592a40ae5c | ||
|
|
261d05f88d | ||
| 6ec0085e31 | |||
|
|
248a38a610 |
14 changed files with 1537 additions and 320 deletions
50
.forgejo/workflows/build.yml
Normal file
50
.forgejo/workflows/build.yml
Normal file
|
|
@ -0,0 +1,50 @@
|
||||||
|
# /.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
|
||||||
83
.forgejo/workflows/ci-cd.yml
Normal file
83
.forgejo/workflows/ci-cd.yml
Normal file
|
|
@ -0,0 +1,83 @@
|
||||||
|
# /.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 }}
|
||||||
29
.forgejo/workflows/deploy.yml
Normal file
29
.forgejo/workflows/deploy.yml
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
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,3 +131,5 @@ 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 } from "../services/minio";
|
import { s3DeleteFile, s3DownloadFile, s3ListFile, s3UploadFile, truncateFileName } 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, fileName, props),
|
await createFile(path, truncateFileName(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, fileName, props),
|
await createFile(path, truncateFileName(fileName), props),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const result = await Promise.all(map).catch((e) =>
|
const result = await Promise.all(map).catch((e) =>
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -70,68 +70,68 @@ export class SalaryPeriodEmployeeController extends Controller {
|
||||||
const data = {
|
const data = {
|
||||||
group1id:
|
group1id:
|
||||||
salaryPeriod.salaryOrgEmployees &&
|
salaryPeriod.salaryOrgEmployees &&
|
||||||
salaryPeriod.salaryOrgEmployees.find(
|
salaryPeriod.salaryOrgEmployees.find(
|
||||||
(x) =>
|
(x) =>
|
||||||
x.group == "GROUP1" &&
|
x.group == "GROUP1" &&
|
||||||
x.rootId == body.rootId &&
|
x.rootId == body.rootId &&
|
||||||
x.snapshot == body.snapshot.toLocaleUpperCase(),
|
x.snapshot == body.snapshot.toLocaleUpperCase(),
|
||||||
) == null
|
) == null
|
||||||
? null
|
? null
|
||||||
: salaryPeriod.salaryOrgEmployees &&
|
: salaryPeriod.salaryOrgEmployees &&
|
||||||
salaryPeriod.salaryOrgEmployees.find(
|
salaryPeriod.salaryOrgEmployees.find(
|
||||||
(x) =>
|
(x) =>
|
||||||
x.group == "GROUP1" &&
|
x.group == "GROUP1" &&
|
||||||
x.rootId == body.rootId &&
|
x.rootId == body.rootId &&
|
||||||
x.snapshot == body.snapshot.toLocaleUpperCase(),
|
x.snapshot == body.snapshot.toLocaleUpperCase(),
|
||||||
)?.id,
|
)?.id,
|
||||||
group1IsClose:
|
group1IsClose:
|
||||||
salaryPeriod.salaryOrgEmployees &&
|
salaryPeriod.salaryOrgEmployees &&
|
||||||
salaryPeriod.salaryOrgEmployees.find(
|
salaryPeriod.salaryOrgEmployees.find(
|
||||||
(x) =>
|
(x) =>
|
||||||
x.group == "GROUP1" &&
|
x.group == "GROUP1" &&
|
||||||
x.rootId == body.rootId &&
|
x.rootId == body.rootId &&
|
||||||
x.snapshot == body.snapshot.toLocaleUpperCase(),
|
x.snapshot == body.snapshot.toLocaleUpperCase(),
|
||||||
) == null
|
) == null
|
||||||
? null
|
? null
|
||||||
: salaryPeriod.salaryOrgEmployees &&
|
: salaryPeriod.salaryOrgEmployees &&
|
||||||
salaryPeriod.salaryOrgEmployees.find(
|
salaryPeriod.salaryOrgEmployees.find(
|
||||||
(x) =>
|
(x) =>
|
||||||
x.group == "GROUP1" &&
|
x.group == "GROUP1" &&
|
||||||
x.rootId == body.rootId &&
|
x.rootId == body.rootId &&
|
||||||
x.snapshot == body.snapshot.toLocaleUpperCase(),
|
x.snapshot == body.snapshot.toLocaleUpperCase(),
|
||||||
)?.isClose,
|
)?.isClose,
|
||||||
group2id:
|
group2id:
|
||||||
salaryPeriod.salaryOrgEmployees &&
|
salaryPeriod.salaryOrgEmployees &&
|
||||||
salaryPeriod.salaryOrgEmployees.find(
|
salaryPeriod.salaryOrgEmployees.find(
|
||||||
(x) =>
|
(x) =>
|
||||||
x.group == "GROUP2" &&
|
x.group == "GROUP2" &&
|
||||||
x.rootId == body.rootId &&
|
x.rootId == body.rootId &&
|
||||||
x.snapshot == body.snapshot.toLocaleUpperCase(),
|
x.snapshot == body.snapshot.toLocaleUpperCase(),
|
||||||
) == null
|
) == null
|
||||||
? null
|
? null
|
||||||
: salaryPeriod.salaryOrgEmployees &&
|
: salaryPeriod.salaryOrgEmployees &&
|
||||||
salaryPeriod.salaryOrgEmployees.find(
|
salaryPeriod.salaryOrgEmployees.find(
|
||||||
(x) =>
|
(x) =>
|
||||||
x.group == "GROUP2" &&
|
x.group == "GROUP2" &&
|
||||||
x.rootId == body.rootId &&
|
x.rootId == body.rootId &&
|
||||||
x.snapshot == body.snapshot.toLocaleUpperCase(),
|
x.snapshot == body.snapshot.toLocaleUpperCase(),
|
||||||
)?.id,
|
)?.id,
|
||||||
group2IsClose:
|
group2IsClose:
|
||||||
salaryPeriod.salaryOrgEmployees &&
|
salaryPeriod.salaryOrgEmployees &&
|
||||||
salaryPeriod.salaryOrgEmployees.find(
|
salaryPeriod.salaryOrgEmployees.find(
|
||||||
(x) =>
|
(x) =>
|
||||||
x.group == "GROUP2" &&
|
x.group == "GROUP2" &&
|
||||||
x.rootId == body.rootId &&
|
x.rootId == body.rootId &&
|
||||||
x.snapshot == body.snapshot.toLocaleUpperCase(),
|
x.snapshot == body.snapshot.toLocaleUpperCase(),
|
||||||
) == null
|
) == null
|
||||||
? null
|
? null
|
||||||
: salaryPeriod.salaryOrgEmployees &&
|
: salaryPeriod.salaryOrgEmployees &&
|
||||||
salaryPeriod.salaryOrgEmployees.find(
|
salaryPeriod.salaryOrgEmployees.find(
|
||||||
(x) =>
|
(x) =>
|
||||||
x.group == "GROUP2" &&
|
x.group == "GROUP2" &&
|
||||||
x.rootId == body.rootId &&
|
x.rootId == body.rootId &&
|
||||||
x.snapshot == body.snapshot.toLocaleUpperCase(),
|
x.snapshot == body.snapshot.toLocaleUpperCase(),
|
||||||
)?.isClose,
|
)?.isClose,
|
||||||
effectiveDate: salaryPeriod.effectiveDate,
|
effectiveDate: salaryPeriod.effectiveDate,
|
||||||
period: salaryPeriod.period,
|
period: salaryPeriod.period,
|
||||||
};
|
};
|
||||||
|
|
@ -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,
|
posLevelName: salaryProfile.posLevel?.split("/")[0],
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
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,
|
posLevelName: salaryProfile.posLevel?.split("/")[0],
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
if (!Level) {
|
if (!Level) {
|
||||||
|
|
@ -762,7 +762,7 @@ export class SalaryPeriodEmployeeController extends Controller {
|
||||||
let type = salaryProfile.type;
|
let type = salaryProfile.type;
|
||||||
|
|
||||||
salaryProfile = await this.calSalaryNew(type, salaryProfile);
|
salaryProfile = await this.calSalaryNew(type, salaryProfile);
|
||||||
|
|
||||||
salaryProfile.lastUpdateUserId = req.user.sub;
|
salaryProfile.lastUpdateUserId = req.user.sub;
|
||||||
salaryProfile.lastUpdateFullName = req.user.name;
|
salaryProfile.lastUpdateFullName = req.user.name;
|
||||||
salaryProfile.lastUpdatedAt = new Date();
|
salaryProfile.lastUpdatedAt = new Date();
|
||||||
|
|
@ -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("change/type-multi")
|
@Post("oldchange/type-multi")
|
||||||
async changeTypeMulti(
|
async oldchangeTypeMulti(
|
||||||
@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,
|
posLevelName: salaryProfile.posLevel?.split("/")[0],
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
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.calSalary(type, salaryProfile);
|
salaryProfile = await this.calSalaryNew(type, salaryProfile);
|
||||||
|
|
||||||
salaryProfile.lastUpdateUserId = req.user.sub;
|
salaryProfile.lastUpdateUserId = req.user.sub;
|
||||||
salaryProfile.lastUpdateFullName = req.user.name;
|
salaryProfile.lastUpdateFullName = req.user.name;
|
||||||
|
|
@ -1063,6 +1063,264 @@ 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 รายการอัตราเงินเดือน
|
||||||
*
|
*
|
||||||
|
|
@ -1159,21 +1417,21 @@ export class SalaryPeriodEmployeeController extends Controller {
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
if (body.sortBy) {
|
if (body.sortBy) {
|
||||||
if(body.sortBy === "posLevel"){
|
if (body.sortBy === "posLevel") {
|
||||||
query = query
|
query = query
|
||||||
.orderBy( `profile.posTypeShort`,body.descending ? "DESC" : "ASC")
|
.orderBy(`profile.posTypeShort`, body.descending ? "DESC" : "ASC")
|
||||||
.addOrderBy( `profile.posLevel`,body.descending ? "DESC" : "ASC");
|
.addOrderBy(`profile.posLevel`, body.descending ? "DESC" : "ASC");
|
||||||
}else{
|
} else {
|
||||||
query = query.orderBy(
|
query = query.orderBy(
|
||||||
`profile.${body.sortBy}`,
|
`profile.${body.sortBy}`,
|
||||||
body.descending ? "DESC" : "ASC"
|
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
|
const [salaryProfile, total] = await query
|
||||||
.skip((body.page - 1) * body.pageSize)
|
.skip((body.page - 1) * body.pageSize)
|
||||||
|
|
@ -1339,7 +1597,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,
|
posLevelName: salaryProfile.posLevel?.split("/")[0],
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
if (!posLevel) {
|
if (!posLevel) {
|
||||||
|
|
@ -2249,7 +2507,7 @@ export class SalaryPeriodEmployeeController extends Controller {
|
||||||
salaryProfile.positionSalaryAmountPer = 0;
|
salaryProfile.positionSalaryAmountPer = 0;
|
||||||
salaryProfile.amountSpecial = 0;
|
salaryProfile.amountSpecial = 0;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (
|
if (
|
||||||
salaryFormula != null &&
|
salaryFormula != null &&
|
||||||
salaryFormula.salaryMax != null &&
|
salaryFormula.salaryMax != null &&
|
||||||
|
|
@ -2451,29 +2709,29 @@ export class SalaryPeriodEmployeeController extends Controller {
|
||||||
},
|
},
|
||||||
relations: ["salaryEmployee_"],
|
relations: ["salaryEmployee_"],
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!salaryCurrentRanks) {
|
if (!salaryCurrentRanks) {
|
||||||
salaryCurrentRanks = await this.salaryRankRepository.findOne({
|
salaryCurrentRanks = await this.salaryRankRepository.findOne({
|
||||||
where: {
|
where: {
|
||||||
salaryMonth: MoreThanOrEqual(salaryProfile.amount),
|
salaryMonth: MoreThanOrEqual(salaryProfile.amount),
|
||||||
},
|
},
|
||||||
order: {
|
order: {
|
||||||
salaryMonth: "ASC",
|
salaryMonth: "ASC",
|
||||||
},
|
},
|
||||||
relations: ["salaryEmployee_"],
|
relations: ["salaryEmployee_"],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (salaryCurrentRanks) {
|
if (salaryCurrentRanks) {
|
||||||
group = salaryCurrentRanks.salaryEmployee_.group;
|
group = salaryCurrentRanks.salaryEmployee_.group;
|
||||||
step = salaryCurrentRanks.step;
|
step = salaryCurrentRanks.step;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//console.log("group", group);
|
//console.log("group", group);
|
||||||
//console.log("step", step);
|
//console.log("step", step);
|
||||||
|
|
||||||
|
|
||||||
if (type == "HAFT") {
|
if (type == "HAFT") {
|
||||||
step = step + 0.5;
|
step = step + 0.5;
|
||||||
stepUp = 0.5;
|
stepUp = 0.5;
|
||||||
|
|
@ -2484,7 +2742,7 @@ export class SalaryPeriodEmployeeController extends Controller {
|
||||||
step = step + 1.5;
|
step = step + 1.5;
|
||||||
stepUp = 1.5;
|
stepUp = 1.5;
|
||||||
}
|
}
|
||||||
//console.log("step+type", step);
|
//console.log("step+type", step);
|
||||||
//หาขั้นสูงสุดในกลุ่มนั้น
|
//หาขั้นสูงสุดในกลุ่มนั้น
|
||||||
let salaryRankMax = await this.salaryRankRepository.findOne({
|
let salaryRankMax = await this.salaryRankRepository.findOne({
|
||||||
where: {
|
where: {
|
||||||
|
|
@ -2496,13 +2754,13 @@ export class SalaryPeriodEmployeeController extends Controller {
|
||||||
order: { step: "DESC" },
|
order: { step: "DESC" },
|
||||||
});
|
});
|
||||||
|
|
||||||
//console.log("salaryRankMax.step", salaryRankMax?.step);
|
//console.log("salaryRankMax.step", salaryRankMax?.step);
|
||||||
//console.log("salaryProfile.amount", salaryProfile.amount);
|
//console.log("salaryProfile.amount", salaryProfile.amount);
|
||||||
//console.log("salaryFormula.salaryMax", salaryFormula?.salaryMax);
|
//console.log("salaryFormula.salaryMax", salaryFormula?.salaryMax);
|
||||||
//เงินเดือนเกินตาราง
|
//เงินเดือนเกินตาราง
|
||||||
//****หา shot ที่ +ขั้น แล้วแก้เป็นหาเงินเดือนที่ใกล้เคียงกับขั้นผังเก่าก่อนแล้วค่อย +ขั้นที่เลื่อนเข้าไป ex.เงินตันที่ 20000 ไปหาผังใหม่ได้ใกล้เคียง 20100 ยึดตัวเลขนี้ไว้แล้วค่อย +ขั้นในผังใหม่ขึ้นไป
|
//****หา shot ที่ +ขั้น แล้วแก้เป็นหาเงินเดือนที่ใกล้เคียงกับขั้นผังเก่าก่อนแล้วค่อย +ขั้นที่เลื่อนเข้าไป ex.เงินตันที่ 20000 ไปหาผังใหม่ได้ใกล้เคียง 20100 ยึดตัวเลขนี้ไว้แล้วค่อย +ขั้นในผังใหม่ขึ้นไป
|
||||||
if (
|
if (
|
||||||
salaryRankMax != null &&
|
salaryRankMax != null &&
|
||||||
step > salaryRankMax.step &&
|
step > salaryRankMax.step &&
|
||||||
(salaryFormula == null ||
|
(salaryFormula == null ||
|
||||||
(salaryFormula != null &&
|
(salaryFormula != null &&
|
||||||
|
|
@ -2510,8 +2768,8 @@ export class SalaryPeriodEmployeeController extends Controller {
|
||||||
salaryFormula.salaryMax != null &&
|
salaryFormula.salaryMax != null &&
|
||||||
salaryFormula.salaryMax > salaryProfile.amount))
|
salaryFormula.salaryMax > salaryProfile.amount))
|
||||||
) {
|
) {
|
||||||
//console.log("in function เกินตาราง");
|
//console.log("in function เกินตาราง");
|
||||||
|
|
||||||
group = group + 1;
|
group = group + 1;
|
||||||
//เงินเดือนในกลุ่มต่อไป
|
//เงินเดือนในกลุ่มต่อไป
|
||||||
let salaryRankAmount = await this.salaryRankRepository.findOne({
|
let salaryRankAmount = await this.salaryRankRepository.findOne({
|
||||||
|
|
@ -2533,7 +2791,7 @@ export class SalaryPeriodEmployeeController extends Controller {
|
||||||
// (step - (salaryRankMax == null ? 0 : salaryRankMax.step) - 0.5);
|
// (step - (salaryRankMax == null ? 0 : salaryRankMax.step) - 0.5);
|
||||||
|
|
||||||
step = (salaryRankAmount == null ? 1 : salaryRankAmount.step) + stepUp; //****หาขั้นของผังใหม่แล้ว + ด้วยขั้นที่ได้เลื่อน
|
step = (salaryRankAmount == null ? 1 : salaryRankAmount.step) + stepUp; //****หาขั้นของผังใหม่แล้ว + ด้วยขั้นที่ได้เลื่อน
|
||||||
//console.log("step in if", step);
|
//console.log("step in if", step);
|
||||||
}
|
}
|
||||||
|
|
||||||
let whereCondition: any = {
|
let whereCondition: any = {
|
||||||
|
|
|
||||||
|
|
@ -12,9 +12,9 @@ enum EmployeePosLevelAuthoritys {
|
||||||
export class EmployeePosLevel extends EntityBase {
|
export class EmployeePosLevel extends EntityBase {
|
||||||
@Column({
|
@Column({
|
||||||
comment: "ชื่อระดับชั้นงาน",
|
comment: "ชื่อระดับชั้นงาน",
|
||||||
type: "int",
|
length: 255,
|
||||||
})
|
})
|
||||||
posLevelName: number;
|
posLevelName: string;
|
||||||
|
|
||||||
@Column({
|
@Column({
|
||||||
comment: "ระดับของระดับชั้นงาน",
|
comment: "ระดับของระดับชั้นงาน",
|
||||||
|
|
|
||||||
|
|
@ -134,7 +134,7 @@ export class SalaryProfileEmployee extends EntityBase {
|
||||||
comment: "ระดับตำแหน่ง",
|
comment: "ระดับตำแหน่ง",
|
||||||
default: null,
|
default: null,
|
||||||
})
|
})
|
||||||
posLevel: number;
|
posLevel: string;
|
||||||
|
|
||||||
@Column({
|
@Column({
|
||||||
type: "double",
|
type: "double",
|
||||||
|
|
@ -482,7 +482,7 @@ export class CreateSalaryProfileEmployee {
|
||||||
posType: string | null;
|
posType: string | null;
|
||||||
|
|
||||||
@Column()
|
@Column()
|
||||||
posLevel: number | null;
|
posLevel: string | null;
|
||||||
|
|
||||||
@Column()
|
@Column()
|
||||||
group: number | null;
|
group: number | null;
|
||||||
|
|
|
||||||
|
|
@ -94,6 +94,17 @@ 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,
|
||||||
|
|
@ -103,6 +114,15 @@ 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],
|
||||||
|
|
@ -185,38 +205,70 @@ class CheckAuth {
|
||||||
}
|
}
|
||||||
public async checkOrg(token: any, keycloakId: string) {
|
public async checkOrg(token: any, keycloakId: string) {
|
||||||
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,
|
||||||
orgChild1Id: x.orgChild1Id,
|
orgChild1Id: x.orgChild1Id,
|
||||||
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) {
|
||||||
return await this.Permission(req, system, "CREATE");
|
return await this.Permission(req, system, "CREATE");
|
||||||
|
|
|
||||||
|
|
@ -67,6 +67,16 @@ 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,21 +45,35 @@ 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().checkOrg(token, req.app.locals.logData.userId)
|
// ? await new permission().checkRootDna(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?.orgRootId ?? null,
|
rootId: rootId ?? null,
|
||||||
systemName: "salary",
|
systemName: "salary",
|
||||||
startTimeStamp: timestamp,
|
startTimeStamp: timestamp,
|
||||||
endTimeStamp: new Date().toISOString(),
|
endTimeStamp: new Date().toISOString(),
|
||||||
|
|
@ -68,7 +82,8 @@ 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,
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
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,6 +16,26 @@ 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("เกิดข้อผิดพลาดกับระบบจัดการไฟล์");
|
||||||
|
|
@ -74,6 +94,11 @@ 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);
|
||||||
|
|
||||||
|
|
@ -146,7 +171,7 @@ export async function s3DownloadFile(...pathname: string[]) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function duplicateAvatarFile(refId: string, prefix: string, fileName: string) {
|
export async function duplicateAvatarFile(refId: string, prefix: string, fileName: string) {
|
||||||
try {
|
try {
|
||||||
await minio.statObject(MINIO_BUCKET, refId);
|
await minio.statObject(MINIO_BUCKET, refId);
|
||||||
const avatar = `${prefix}/${fileName}`;
|
const avatar = `${prefix}/${fileName}`;
|
||||||
await minio.copyObject(
|
await minio.copyObject(
|
||||||
|
|
@ -159,4 +184,4 @@ export async function duplicateAvatarFile(refId: string, prefix: string, fileNam
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const fileLocation = (...path: string[]) => path.join("/");
|
export const fileLocation = (...path: string[]) => path.join("/");
|
||||||
Loading…
Add table
Add a link
Reference in a new issue