start project

This commit is contained in:
Warunee Tamkoo 2024-09-05 13:59:43 +07:00
commit 0703810fa3
62 changed files with 12665 additions and 0 deletions

7
.dockerignore Normal file
View file

@ -0,0 +1,7 @@
.DS_Store
node_modules
/dist
.env
.env.*
!.env.example

12
.env.example Normal file
View file

@ -0,0 +1,12 @@
AUTH_REALM_URL=http://192.168.1.200:8080/realms/dev
AUTH_PUBLIC_KEY=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1/QAH75nWgiRzWCTrGJv8q2A7z0qggC2IQ9Sva/Ok1RxeGE/ED2m4ELbF5B9MtugyXYGMUBXaKhooMpTE3wyR1OwsUlv/GtYSmMuKUnsSEXklsP8nIq8gZkCvISOVdvIC4ng5aZ5nBcp9cQ3eVbz4dfZcbLzcqLIIkxQmFBK0m1eFL5IdNj8Ac7U4eH4ylOckOu174f35NnFH6wDva6Iic3EXapMcE2BnXXCTajk2dmlWAzH13ybQBgHDfrOtulrmn1CzQxe9WUJes4qX5z72N05KsHvtUObaeN6cb+mIeH36GdysqgAdd2hhKkgUFXwtLPzldtrEc7xVyf3OLEg1QIDAQAB
AUTH_PREFERRED_MODE=online
APP_HOST=0.0.0.0
APP_PORT=3000
DB_HOST=192.168.1.200
DB_PORT=3306
DB_USERNAME=root
DB_PASSWORD=
DB_NAME=dev

86
.github/workflows/release.yaml vendored Normal file
View file

@ -0,0 +1,86 @@
name: release-test
run-name: release-test ${{ github.actor }}
on:
push:
tags:
- "version-[0-9]+.[0-9]+.[0-9]+"
workflow_dispatch:
env:
REGISTRY: docker.frappet.com
IMAGE_NAME: ehr/bma-ehr-node-service
DEPLOY_HOST: frappet.com
# COMPOSE_PATH: /home/frappet/docker/bma-ehr
COMPOSE_PATH: /home/frappet/docker/bma/bma-ehr-probation
jobs:
# act workflow_dispatch -W .github/workflows/release.yaml --input IMAGE_VER=test-v1 -s DOCKER_USER=sorawit -s DOCKER_PASS=P@ssword -s SSH_PASSWORD=P@ssw0rd
release-test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
# skip Set up QEMU because it fail on act and container
# Gen Version try to get version from tag or inut
- name: Set output tags
id: vars
run: echo "tag=${GITHUB_REF#refs/*/}" >> $GITHUB_OUTPUT
- name: Gen Version
id: gen_ver
run: |
if [[ $GITHUB_REF == 'refs/tags/'* ]]; then
IMAGE_VER=${{ steps.vars.outputs.tag }}
else
IMAGE_VER=${{ github.event.inputs.IMAGE_VER }}
fi
if [[ $IMAGE_VER == '' ]]; then
IMAGE_VER='test-vBeta'
fi
echo '::set-output name=image_ver::'$IMAGE_VER
- name: Check Version
run: |
echo $GITHUB_REF
echo ${{ steps.gen_ver.outputs.image_ver }}
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Login in to registry
uses: docker/login-action@v2
with:
registry: ${{env.REGISTRY}}
username: ${{secrets.DOCKER_USER}}
password: ${{secrets.DOCKER_PASS}}
- name: Build and push docker image
uses: docker/build-push-action@v3
with:
context: .
platforms: linux/amd64
push: true
tags: ${{env.REGISTRY}}/${{env.IMAGE_NAME}}:${{ steps.gen_ver.outputs.image_ver }},${{env.REGISTRY}}/${{env.IMAGE_NAME}}:latest
- name: Remote Deployment
uses: appleboy/ssh-action@v0.1.8
with:
host: ${{env.DEPLOY_HOST}}
username: frappet
password: ${{ secrets.SSH_PASSWORD }}
port: 10102
script: |
cd "${{env.COMPOSE_PATH}}"
docker compose pull
docker compose up -d
echo "${{ steps.gen_ver.outputs.image_ver }}"> success
- uses: snow-actions/line-notify@v1.1.0
if: success()
with:
access_token: ${{ secrets.TOKEN_LINE }}
message: |
-Success✅✅✅
Image: ${{env.IMAGE_NAME}}
Version: ${{ steps.gen_ver.outputs.IMAGE_VER }}
By: ${{github.actor}}
- uses: snow-actions/line-notify@v1.1.0
if: failure()
with:
access_token: ${{ secrets.TOKEN_LINE }}
message: |
-Failure❌❌❌
Image: ${{env.IMAGE_NAME}}
Version: ${{ steps.gen_ver.outputs.IMAGE_VER }}
By: ${{github.actor}}

133
.gitignore vendored Normal file
View file

@ -0,0 +1,133 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
.pnpm-debug.log*
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
*.lcov
# nyc test coverage
.nyc_output
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# Snowpack dependency directory (https://snowpack.dev/)
web_modules/
# TypeScript cache
*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional stylelint cache
.stylelintcache
# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variable files
.env
.env.development.local
.env.test.local
.env.production.local
.env.local
[Ss]rc/swagger.json
[Ss]rc/routes.ts
# parcel-bundler cache (https://parceljs.org/)
.cache
.parcel-cache
# Next.js build output
.next
out
# Nuxt.js build / generate output
.nuxt
dist
# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and not Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public
# vuepress build output
.vuepress/dist
# vuepress v2.x temp and cache directory
.temp
.cache
# Docusaurus cache and generated files
.docusaurus
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# TernJS port file
.tern-port
# Stores VSCode versions used for testing VSCode extensions
.vscode-test
# yarn v2
.yarn/cache
.yarn/unplugged
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.*

18
.prettierignore Normal file
View file

@ -0,0 +1,18 @@
.DS_Store
node_modules
.env
.env.*
!.env.example
/dist
/static
/src/routes.ts
/src/swagger.json
# Any log
*.log
# Ignore files for PNPM, NPM and YARN
pnpm-lock.yaml
package-lock.json
yarn.lock

35
Dockerfile Normal file
View file

@ -0,0 +1,35 @@
FROM node:18-alpine as builder
# Create app directory
WORKDIR /app
# Install app dependencies
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
FROM node:18-alpine
ENV NODE_ENV production
USER node
# Create app directory
WORKDIR /app
# Install app dependencies
COPY package*.json ./
# COPY .env ./
RUN npm ci --production
COPY --from=builder /app/dist ./dist
# COPY entrypoint.sh /usr/local/bin/entrypoint.sh
# RUN chmod u+x /usr/local/bin/entrypoint.sh
# ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]
CMD [ "node", "dist/app.js" ]

21
README.md Normal file
View file

@ -0,0 +1,21 @@
# bma-ehr-probation
Envelopment
- Node 20.x
- Express
- Mysql
Run Project
```
> npm install
> npm run dev
```
Migration
1. สร้าง entities
2. สร้างไฟล์ migration จาก entities และรัน update เข้า database
> npm run migration:generate src/migration/xxx (xxx คือชื่อไฟล์ migrate)
> npm run migration:run

6
nodemon.json Normal file
View file

@ -0,0 +1,6 @@
{
"exec": "tsoa spec-and-routes && ts-node src/app.ts",
"ext": "ts",
"watch": ["src"],
"ignore": ["src/routes.ts"]
}

49
package.json Normal file
View file

@ -0,0 +1,49 @@
{
"name": "template",
"version": "1.0.0",
"description": "",
"main": "src/app.js",
"scripts": {
"dev": "nodemon",
"check": "tsc --noEmit",
"start": "node ./dist/app.js",
"format": "prettier --write .",
"build": "tsoa spec-and-routes && tsc",
"migration:generate": "typeorm-ts-node-commonjs migration:generate -d src/database/data-source.ts",
"migration:run": "typeorm-ts-node-commonjs migration:run -d src/database/data-source.ts"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"@types/cors": "^2.8.17",
"@types/express": "^4.17.21",
"@types/node": "^20.11.5",
"@types/node-cron": "^3.0.11",
"@types/swagger-ui-express": "^4.1.6",
"nodemon": "^3.0.3",
"prettier": "^3.2.2",
"ts-node": "^10.9.2",
"typescript": "^5.3.3"
},
"dependencies": {
"@elastic/elasticsearch": "^8.14.0",
"@nestjs/platform-express": "^10.3.7",
"@tsoa/runtime": "^6.0.0",
"axios": "^1.6.8",
"cors": "^2.8.5",
"dotenv": "^16.3.1",
"express": "^4.18.2",
"fast-jwt": "^3.3.2",
"multer": "^1.4.5-lts.1",
"mysql2": "^3.9.1",
"node-cron": "^3.0.3",
"promise.any": "^2.0.6",
"reflect-metadata": "^0.2.1",
"swagger-ui-express": "^5.0.0",
"tsoa": "^6.0.1",
"typeorm": "^0.3.19",
"typeorm-cli": "^1.0.7",
"xlsx": "^0.18.5"
}
}

47
src/app.ts Normal file
View file

@ -0,0 +1,47 @@
import "dotenv/config";
import "reflect-metadata";
import cors from "cors";
import express from "express";
import swaggerUi from "swagger-ui-express";
import swaggerDocument from "./swagger.json";
import * as cron from "node-cron";
import error from "./middlewares/error";
import { AppDataSource } from "./database/data-source";
import { RegisterRoutes } from "./routes";
// import logMiddleware from "./middlewares/logs";
async function main() {
await AppDataSource.initialize();
const app = express();
app.use(
cors({
origin: "*",
})
);
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
// app.use(logMiddleware);
app.use("/", express.static("static"));
app.use("/api-docs", swaggerUi.serve, swaggerUi.setup(swaggerDocument));
RegisterRoutes(app);
app.use(error);
const APP_HOST = process.env.APP_HOST || "0.0.0.0";
const APP_PORT = +(process.env.APP_PORT || 3000);
app.listen(
APP_PORT,
APP_HOST,
() => (
console.log(
`[APP] Application is running on: http://localhost:${APP_PORT}`
),
console.log(`[APP] Swagger on: http://localhost:${APP_PORT}/api-docs`)
)
);
}
main();

View file

@ -0,0 +1,761 @@
import {
Controller,
Post,
Put,
Route,
Security,
Tags,
Body,
Request,
SuccessResponse,
Response,
Get,
Query,
} from "tsoa";
import { AppDataSource } from "../database/data-source";
import HttpSuccess from "../interfaces/http-success";
import HttpStatusCode from "../interfaces/http-status";
import HttpError from "../interfaces/http-error";
import { RequestWithUser } from "../middlewares/user";
import { findEndDate, setLogDataDiff } from "../interfaces/utils";
import { Personal } from "../entities/Personal";
import permission from "../interfaces/permission";
import { Assign, CreateAssign } from "../entities/Assign";
import {
AssignDirector,
CreateAssignDirector,
} from "../entities/AssignDirector";
import { AssignJob, CreateAssignJob } from "../entities/AssignJob";
import {
AssignKnowledge,
CreateAssignKnowledge,
} from "../entities/AssignKnowledge";
import { AssignLaw, CreateAssignLaw } from "../entities/AssignLaw";
import { AssignSkill, CreateAssignSkill } from "../entities/AssignSkill";
import {
AssignCompetency,
CreateAssignCompetency,
} from "../entities/AssignCompetency";
import {
AssignCompetencyGroup,
CreateAssignCompetencyGroup,
} from "../entities/AssignCompetencyGroup";
import { AssignOutput, CreateAssignOutput } from "../entities/AssignOutput";
import { Law } from "../entities/Law";
import CallAPI from "../interfaces/call-api";
@Route("api/v1/assign")
@Tags("ฟอร์มมอบหมายงาน")
@Security("bearerAuth")
@Response(
HttpStatusCode.INTERNAL_SERVER_ERROR,
"เกิดข้อผิดพลาด ไม่สามารถแสดงรายการได้ กรุณาลองใหม่ในภายหลัง"
)
@SuccessResponse(HttpStatusCode.OK, "สำเร็จ")
export class AssignController extends Controller {
private assignRepository = AppDataSource.getRepository(Assign);
private personalRepository = AppDataSource.getRepository(Personal);
private assignDirectorRepository =
AppDataSource.getRepository(AssignDirector);
private assignJobRepository = AppDataSource.getRepository(AssignJob);
private assignKnowledgeRepository =
AppDataSource.getRepository(AssignKnowledge);
private assignLawRepository = AppDataSource.getRepository(AssignLaw);
private assignSkillRepository = AppDataSource.getRepository(AssignSkill);
private assignCompetencyRepository =
AppDataSource.getRepository(AssignCompetency);
private assignCompetencyGroupRepository = AppDataSource.getRepository(
AssignCompetencyGroup
);
private assignOutputRepository = AppDataSource.getRepository(AssignOutput);
private lawsRepository = AppDataSource.getRepository(Law);
/**
* API
*
* @summary
*
*/
@Post("")
async AddAssign(
@Request() request: RequestWithUser,
@Body() requestBody: CreateAssign
) {
await new permission().PermissionUpdate(request, "SYS_PROBATION");
const person = await this.personalRepository.findOne({
where: {
personal_id: requestBody.personal_id,
},
});
if (!person) {
return new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบข้อมูลบุคคล");
}
const roundNo = await this.assignRepository.count({
where: {
active: 1,
personal_id: requestBody.personal_id,
},
});
const data: any = {
...requestBody,
round_no: roundNo + 1,
personal_id: requestBody.personal_id,
createdUserId: request.user.sub,
createdFullName: request.user.name,
updateUserId: request.user.sub,
updateFullName: request.user.name,
};
const before = null;
const assign = await this.assignRepository.save(data, { data: request });
setLogDataDiff(request, { before, after: data });
const jobs = await requestBody.assign_jobs.map(
(x: CreateAssignJob, index: number) => ({
...x,
id: index + 1,
assign_id: assign.id,
createdUserId: request.user.sub,
createdFullName: request.user.name,
updateUserId: request.user.sub,
updateFullName: request.user.name,
})
);
await this.assignJobRepository.save(jobs, { data: request });
setLogDataDiff(request, { before, after: jobs });
const knowledges = await requestBody.assign_knowledges.map(
(x: CreateAssignKnowledge, index: number) => ({
knowledge_level: x.level,
knowledge_id: x.id,
assign_id: assign.id,
createdUserId: request.user.sub,
createdFullName: request.user.name,
updateUserId: request.user.sub,
updateFullName: request.user.name,
})
);
await this.assignKnowledgeRepository.save(knowledges, { data: request });
setLogDataDiff(request, { before, after: knowledges });
const laws = await requestBody.assign_law.map(
(x: CreateAssignLaw, index: number) => ({
ordering: index + 1,
law_id: x.id,
assign_id: assign.id,
createdUserId: request.user.sub,
createdFullName: request.user.name,
updateUserId: request.user.sub,
updateFullName: request.user.name,
})
);
await this.assignLawRepository.save(laws, { data: request });
setLogDataDiff(request, { before, after: laws });
const skills = await requestBody.assign_skill.map(
(x: CreateAssignSkill, index: number) => ({
skill_id: index + 1,
skill_level: x.level,
assign_id: assign.id,
createdUserId: request.user.sub,
createdFullName: request.user.name,
updateUserId: request.user.sub,
updateFullName: request.user.name,
})
);
await this.assignSkillRepository.save(skills, { data: request });
setLogDataDiff(request, { before, after: skills });
const competencise = await requestBody.assign_competency.map(
(x: CreateAssignCompetency, index: number) => ({
competency_id: x.id,
competency_level: x.level,
competency_name: x.name,
competency_description: x.description,
assign_id: assign.id,
createdUserId: request.user.sub,
createdFullName: request.user.name,
updateUserId: request.user.sub,
updateFullName: request.user.name,
})
);
await this.assignCompetencyRepository.save(competencise, { data: request });
setLogDataDiff(request, { before, after: competencise });
const competencyGroups = await requestBody.assign_competency_group.map(
(x: CreateAssignCompetencyGroup, index: number) => ({
competency_group_id: x.id,
competency_group_level: x.level,
competency_group_name: x.name,
competency_group_description: x.description,
assign_id: assign.id,
createdUserId: request.user.sub,
createdFullName: request.user.name,
updateUserId: request.user.sub,
updateFullName: request.user.name,
})
);
await this.assignCompetencyGroupRepository.save(competencyGroups, {
data: request,
});
setLogDataDiff(request, { before, after: competencyGroups });
const outputs = await requestBody.assign_outputs.map(
(x: CreateAssignOutput, index: number) => ({
...x,
id: index + 1,
assign_id: assign.id,
createdUserId: request.user.sub,
createdFullName: request.user.name,
updateUserId: request.user.sub,
updateFullName: request.user.name,
})
);
await this.assignOutputRepository.save(outputs, { data: request });
setLogDataDiff(request, { before, after: outputs });
const directors = await requestBody.assign_director.map(
(x: CreateAssignDirector, index: number) => ({
...x,
assign_id: assign.id,
fullname: x.name,
ordering: index + 1,
createdUserId: request.user.sub,
createdFullName: request.user.name,
updateUserId: request.user.sub,
updateFullName: request.user.name,
})
);
await this.assignDirectorRepository.save(directors, { data: request });
setLogDataDiff(request, { before, after: directors });
return new HttpSuccess();
}
/**
* API
*
* @summary
*
*/
@Put("")
async EditAssign(
@Query() assign_id: string,
@Request() request: RequestWithUser,
@Body() requestBody: CreateAssign
) {
await new permission().PermissionUpdate(request, "SYS_PROBATION");
const assign = await this.assignRepository.findOne({
where: { id: assign_id },
});
let before = assign;
if (!assign) {
return new HttpError(
HttpStatusCode.NOT_FOUND,
"ไม่พบข้อมูลแบบมอบหมายงาน"
);
}
const person = await this.personalRepository.findOne({
where: {
personal_id: requestBody.personal_id,
},
});
if (!person) {
return new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบข้อมูลบุคคล");
}
const data: any = {
...requestBody,
id: assign_id,
updateUserId: request.user.sub,
updateFullName: request.user.name,
};
await this.assignJobRepository.delete({ assign_id });
const jobs = await requestBody.assign_jobs.map(
(x: CreateAssignJob, index: number) => ({
...x,
id: index + 1,
assign_id: assign.id,
createdUserId: request.user.sub,
createdFullName: request.user.name,
updateUserId: request.user.sub,
updateFullName: request.user.name,
})
);
await this.assignJobRepository.save(jobs);
await this.assignKnowledgeRepository.delete({ assign_id });
const knowledges = await requestBody.assign_knowledges.map(
(x: CreateAssignKnowledge, index: number) => ({
knowledge_level: x.level,
knowledge_id: x.id,
assign_id: assign.id,
createdUserId: request.user.sub,
createdFullName: request.user.name,
updateUserId: request.user.sub,
updateFullName: request.user.name,
})
);
await this.assignKnowledgeRepository.save(knowledges);
await this.assignLawRepository.delete({ assign_id });
const laws = await requestBody.assign_law.map(
(x: CreateAssignLaw, index: number) => ({
ordering: index + 1,
law_id: x.id,
assign_id: assign.id,
createdUserId: request.user.sub,
createdFullName: request.user.name,
updateUserId: request.user.sub,
updateFullName: request.user.name,
})
);
await this.assignLawRepository.save(laws);
await this.assignSkillRepository.delete({ assign_id });
const skills = await requestBody.assign_skill.map(
(x: CreateAssignSkill, index: number) => ({
skill_id: index + 1,
skill_level: x.level,
assign_id: assign.id,
createdUserId: request.user.sub,
createdFullName: request.user.name,
updateUserId: request.user.sub,
updateFullName: request.user.name,
})
);
await this.assignSkillRepository.save(skills);
await this.assignCompetencyRepository.delete({ assign_id });
const competencise = await requestBody.assign_competency.map(
(x: CreateAssignCompetency, index: number) => ({
competency_id: x.id,
competency_level: x.level,
competency_name: x.name,
competency_description: x.description,
assign_id: assign.id,
createdUserId: request.user.sub,
createdFullName: request.user.name,
updateUserId: request.user.sub,
updateFullName: request.user.name,
})
);
await this.assignCompetencyRepository.save(competencise);
await this.assignCompetencyGroupRepository.delete({ assign_id });
const competencyGroups = await requestBody.assign_competency_group.map(
(x: CreateAssignCompetencyGroup, index: number) => ({
competency_group_id: x.id,
competency_group_level: x.level,
competency_group_name: x.name,
competency_group_description: x.description,
assign_id: assign.id,
createdUserId: request.user.sub,
createdFullName: request.user.name,
updateUserId: request.user.sub,
updateFullName: request.user.name,
})
);
await this.assignCompetencyGroupRepository.save(competencyGroups);
await this.assignOutputRepository.delete({ assign_id });
const outputs = await requestBody.assign_outputs.map(
(x: CreateAssignOutput, index: number) => ({
...x,
id: index + 1,
assign_id: assign.id,
createdUserId: request.user.sub,
createdFullName: request.user.name,
updateUserId: request.user.sub,
updateFullName: request.user.name,
})
);
await this.assignOutputRepository.save(outputs);
await this.assignDirectorRepository.delete({ assign_id });
const directors = await requestBody.assign_director.map(
(x: CreateAssignDirector, index: number) => ({
...x,
assign_id: assign.id,
fullname: x.name,
ordering: index + 1,
createdUserId: request.user.sub,
createdFullName: request.user.name,
updateUserId: request.user.sub,
updateFullName: request.user.name,
})
);
await this.assignDirectorRepository.save(directors);
await this.assignRepository.save(data, { data: request });
setLogDataDiff(request, { before, after: data });
// #noted cronjob
// แจ้งผู้ดูแลและผู้บังคับบัญชาเข้ามาบันทึกผลทุก 2 เดือน
const dateSaveForm = await findEndDate(2, requestBody.date_start);
requestBody.assign_director
.filter((x) => x.role == "mentor" || x.role == "commander")
.map(async (director) => {
await new CallAPI()
.PostData(request, "/placement/noti", {
subject: `ถึงกำหนดบันทึกผลการทดลองปฏิบัติหน้าที่ราชการครั้งที่ 1 ${requestBody.fullname}`,
body: `ถึงกำหนดบันทึกผลการทดลองปฏิบัติหน้าที่ราชการครั้งที่ 1 ${requestBody.fullname}`,
receiverUserId: director.personal_id,
payload: "",
isSendMail: true,
isSendInbox: true,
receiveDate: dateSaveForm,
})
.catch((error) => {
console.error("Error calling API:", error);
});
});
// แจ้งผู้บังคับบัญชา และคณะกรรมการเข้ามาประเมินทุก 3 เดือน
const dateEvaluate = await findEndDate(3, requestBody.date_start);
requestBody.assign_director
.filter((x) => x.role == "commander" || x.role == "chairman")
.map(async (director) => {
await new CallAPI()
.PostData(request, "/placement/noti", {
subject: `ถึงกำหนดประเมินผลการทดลองปฏิบัติหน้าที่ราชการครั้งที่ 1 ${requestBody.fullname}`,
body: `ถึงกำหนดประเมินผลการทดลองปฏิบัติหน้าที่ราชการครั้งที่ 1 ${requestBody.fullname}`,
receiverUserId: director.personal_id,
payload: "",
isSendMail: true,
isSendInbox: true,
receiveDate: dateEvaluate,
})
.catch((error) => {
console.error("Error calling API:", error);
});
});
// แจ้งประธาน 6 เดือน
const dateResult = await findEndDate(6, requestBody.date_start);
requestBody.assign_director
.filter((x) => x.role == "chairman")
.map(async (director) => {
await new CallAPI()
.PostData(request, "/placement/noti", {
subject: `ถึงกำหนดรายงานการประเมินผลการทดลองปฏิบัติหน้าที่ราชการ ${requestBody.fullname}`,
body: `ถึงกำหนดรายงานการประเมินผลการทดลองปฏิบัติหน้าที่ราชการ ${requestBody.fullname}`,
receiverUserId: director.personal_id,
payload: "",
isSendMail: true,
isSendInbox: true,
// isSendNotification: true
receiveDate: dateResult,
})
.catch((error) => {
console.error("Error calling API:", error);
});
});
return new HttpSuccess();
}
/**
* API
*
* @summary
*
*/
@Get("assign-list")
async ListPersonal(
@Request() request: RequestWithUser,
@Query() personal_id: string
) {
await new permission().PermissionGet(request, "SYS_PROBATION");
const lists = await this.assignRepository.find({
select: ["id", "round_no", "date_start", "date_finish"],
where: { personal_id },
order: { round_no: "ASC" },
});
let result: any = [];
await Promise.all(
lists.map(async (item) => {
const director = await this.assignDirectorRepository.find({
where: { assign_id: item.id },
});
let mentors = "";
const mentorList = await director.filter((x) => x.role == "mentor");
if (mentorList.length > 0) {
for (let index = 0; index < mentorList.length; index++) {
const e = await mentorList[index];
mentors += e.fullname;
if (index < mentorList.length - 1) {
mentors += ", ";
}
}
}
const commanderData = await (director.find(
(x) => x.role == "commander"
) ?? null);
const commander = commanderData ? commanderData.fullname : null;
const chairmanData = await (director.find(
(x) => x.role == "chairman"
) ?? null);
const chairman = chairmanData ? chairmanData.fullname : null;
await result.push({
...item,
mentors: mentors,
commander: commander,
chairman: chairman,
});
})
);
return new HttpSuccess(result);
}
/**
* API
*
* @summary
*
*/
@Get("")
async GetAssign(
@Query() assign_id: string,
@Request() request: RequestWithUser
) {
await new permission().PermissionUpdate(request, "SYS_PROBATION");
const assign = await this.assignRepository.findOne({
select: [
"id",
"personal_id",
"round_no",
"date_start",
"date_finish",
"other4_desc",
"other5_no1_desc",
"experimenter_dated",
"active",
"createdAt",
"updatedAt",
],
where: { id: assign_id },
});
if (!assign) {
return new HttpError(
HttpStatusCode.NOT_FOUND,
"ไม่พบข้อมูลแบบมอบหมายงาน"
);
}
const profileData = await this.personalRepository.findOne({
select: [
"personal_id",
"prefixName",
"firstName",
"lastName",
"positionName",
"positionLevelName",
"positionLineName",
"orgRootName",
"organization",
],
where: {
personal_id: assign.personal_id,
},
});
if (!profileData) {
return new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบข้อมูลบุคคล");
}
const profile = {
...profileData,
name: `${profileData.prefixName}${profileData.firstName} ${profileData.lastName}`,
Position: profileData.positionName,
Department: "-",
OrganizationOrganization: profileData.orgRootName,
Oc: profileData.organization,
};
const jobs = await this.assignJobRepository.find({
select: ["id", "activity_desc", "goal_desc"],
where: { assign_id },
});
const knowledgeData = await this.assignKnowledgeRepository.find({
relations: ["knowledge"],
where: { assign_id },
});
const knowledges = await knowledgeData.map((x) => ({
id: x.knowledge_id,
level: x.knowledge_level,
title: x.knowledge.title,
description:
x.knowledge_level == 1
? x.knowledge.level1
: x.knowledge_level == 2
? x.knowledge.level2
: x.knowledge_level == 3
? x.knowledge.level3
: x.knowledge_level == 4
? x.knowledge.level4
: x.knowledge_level == 5
? x.knowledge.level5
: "",
}));
const lawData = await this.lawsRepository.find({
where: { active: 1 },
});
const laws = await lawData.map((x) => ({
id: x.id,
selected: x.assignLaw ? 1 : 0,
description: x.description,
status_select: x.status_select,
}));
const skillsData = await this.assignSkillRepository.find({
relations: ["skill"],
where: { assign_id },
});
const skills = await skillsData.map((x) => ({
id: x.skill_id,
level: x.skill_level,
title: x.skill.title,
description:
x.skill_level == 1
? x.skill.level1
: x.skill_level == 2
? x.skill.level2
: x.skill_level == 3
? x.skill.level3
: x.skill_level == 4
? x.skill.level4
: x.skill_level == 5
? x.skill.level5
: "",
}));
const competencyData = await this.assignCompetencyRepository.find({
select: [
"competency_id",
"competency_level",
"competency_name",
"competency_description",
],
where: { assign_id },
});
const competencys = await competencyData.map((x) => ({
id: x.competency_id,
level: x.competency_level,
name: x.competency_name,
description: x.competency_description,
}));
const competencyGroupData = await this.assignCompetencyGroupRepository.find(
{
select: [
"competency_group_id",
"competency_group_level",
"competency_group_name",
"competency_group_description",
],
where: { assign_id },
}
);
const competency_groups = await competencyGroupData.map((x) => ({
id: x.competency_group_id,
level: x.competency_group_level,
name: x.competency_group_name,
description: x.competency_group_description,
}));
const outputs = await this.assignOutputRepository.find({
select: ["id", "output_desc", "indicator_desc"],
where: { assign_id },
});
const director = await this.assignDirectorRepository.find({
where: { assign_id },
});
let mentors = [];
const mentorList = await director.filter((x) => x.role == "mentor");
if (mentorList.length > 0) {
for (let index = 0; index < mentorList.length; index++) {
const e = await mentorList[index];
mentors.push({
...e,
name: e.fullname,
label:
e.fullname +
" " +
(e.position ? `(${e.position}, ${e.posType}: ${e.posLevel})` : ""),
Position: e.position, // report
});
}
}
const commanderData = await (director.find((x) => x.role == "commander") ??
null);
const commander = await (commanderData
? {
...commanderData,
name: commanderData.fullname,
label:
commanderData.fullname +
" " +
(commanderData.position
? `(${commanderData.position}, ${commanderData.posType}: ${commanderData.posLevel})`
: ""),
Position: commanderData.position, // report
}
: null);
const chairmanData = await (director.find((x) => x.role == "chairman") ??
null);
const chairman = await (chairmanData
? {
...chairmanData,
name: chairmanData.fullname,
label:
chairmanData.fullname +
" " +
(chairmanData.position
? `(${chairmanData.position}, ${chairmanData.posType}: ${chairmanData.posLevel})`
: ""),
Position: chairmanData.position, // report
}
: null);
return new HttpSuccess({
assign,
profile,
jobs,
knowledges,
laws,
skills,
competencys,
competency_groups,
outputs,
mentors,
commander,
chairman,
});
}
}

View file

@ -0,0 +1,47 @@
import {
Controller,
Route,
Security,
Tags,
Request,
SuccessResponse,
Response,
Get,
Post,
Body,
} from "tsoa";
import HttpSuccess from "../interfaces/http-success";
import HttpStatusCode from "../interfaces/http-status";
import { RequestWithUser } from "../middlewares/user";
import { findEndDate } from "../interfaces/utils";
@Route("api/v1/calculate")
@Tags("ฟอร์มมอบหมายงาน")
@Security("bearerAuth")
@Response(
HttpStatusCode.INTERNAL_SERVER_ERROR,
"เกิดข้อผิดพลาด ไม่สามารถแสดงรายการได้ กรุณาลองใหม่ในภายหลัง"
)
@SuccessResponse(HttpStatusCode.OK, "สำเร็จ")
export class CalculateController extends Controller {
/**
* API
*
* @summary
*
*/
@Post("assign-finish")
async AssignFinish(
@Body()
requestBody: {
month: number;
start_date: Date;
},
@Request() request: RequestWithUser
) {
const { month, start_date } = requestBody;
const finish_date = findEndDate(month, start_date);
return new HttpSuccess({ finish_date });
}
}

View file

@ -0,0 +1,302 @@
import {
Controller,
Route,
Security,
Tags,
Path,
Request,
SuccessResponse,
Response,
Get,
Query,
} from "tsoa";
import { AppDataSource } from "../database/data-source";
import HttpSuccess from "../interfaces/http-success";
import HttpStatusCode from "../interfaces/http-status";
import HttpError from "../interfaces/http-error";
import { RequestWithUser } from "../middlewares/user";
import { Knowledge, TypeKnowledge } from "../entities/Knowledge";
import { Skill, TypeSkill } from "../entities/Skill";
import { MapKnowledgeSkill } from "../entities/MapKnowledgeSkill";
import { Personal } from "../entities/Personal";
import { Law } from "../entities/Law";
import { Assign } from "../entities/Assign";
@Route("api/v1/data-options")
@Tags("Data Options")
@Security("bearerAuth")
@Response(
HttpStatusCode.INTERNAL_SERVER_ERROR,
"เกิดข้อผิดพลาด ไม่สามารถแสดงรายการได้ กรุณาลองใหม่ในภายหลัง"
)
@SuccessResponse(HttpStatusCode.OK, "สำเร็จ")
export class DataOptionController extends Controller {
private personalRepository = AppDataSource.getRepository(Personal);
private knowledgeRepository = AppDataSource.getRepository(Knowledge);
private mapKnowledgeSkillRepository =
AppDataSource.getRepository(MapKnowledgeSkill);
private skillRepository = AppDataSource.getRepository(Skill);
private lawRepository = AppDataSource.getRepository(Law);
private assignRepository = AppDataSource.getRepository(Assign);
/**
* API list
*
* @summary options
*
*/
@Get("knowledge")
async GetKnowledge(
@Query() personal_id: string,
@Request() request: RequestWithUser
) {
const person = await this.personalRepository.findOne({
where: { personal_id },
});
console.log(person);
if (!person) {
return new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบข้อมูลบุคคล");
}
const result = await this.mapKnowledgeSkillRepository.findOne({
where: {
positionName: person.positionName,
positionLevelName: person.positionLevelName,
active: 1,
},
});
if (!result) {
return new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบข้อมูล");
}
const knowledges = await this.knowledgeRepository.find({
where: { type: TypeKnowledge.PERFORMANCE, active: 1 },
});
const knowledge = knowledges.map((knowledge) => ({
id: knowledge.id,
title: knowledge.title,
description:
result.knowlage_performance_level == 1
? knowledge.level1
: result.knowlage_performance_level == 2
? knowledge.level2
: result.knowlage_performance_level == 3
? knowledge.level3
: result.knowlage_performance_level == 4
? knowledge.level4
: knowledge.level5,
level: result.knowlage_performance_level,
}));
return new HttpSuccess(knowledge);
}
/**
* API
*
* @summary options
*
*/
@Get("skill")
async GetSkill(
@Query() personal_id: string,
@Request() request: RequestWithUser
) {
const person = await this.personalRepository.findOne({
where: { personal_id },
});
if (!person) {
return new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบข้อมูล");
}
const result = await this.mapKnowledgeSkillRepository.findOne({
select: [
"skill_computer_level",
"skill_english_level",
"skill_information_level",
"skill_resourse_level",
],
where: {
positionName: person.positionName,
positionLevelName: person.positionLevelName,
active: 1,
},
});
if (!result) {
return new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบข้อมูล");
}
const skills = await this.skillRepository.find({
where: { type: TypeSkill.COMPUTER, active: 1 },
});
const skill = await skills.map((v) => ({
id: v.id,
title: v.title,
level_description:
result.skill_english_level == 1
? v.level1
: result.skill_english_level == 2
? v.level2
: result.skill_english_level == 3
? v.level3
: result.skill_english_level == 4
? v.level4
: v.level5,
level: result.skill_english_level,
}));
const englishs = await this.skillRepository.find({
where: { type: TypeSkill.ENG, active: 1 },
});
const english = await englishs.map((v) => ({
id: v.id,
title: v.title,
level_description:
result.skill_english_level == 1
? v.level1
: result.skill_english_level == 2
? v.level2
: result.skill_english_level == 3
? v.level3
: result.skill_english_level == 4
? v.level4
: v.level5,
level: result.skill_english_level,
}));
const informations = await this.skillRepository.find({
where: { type: TypeSkill.INFORMATION, active: 1 },
});
const information = await informations.map((v) => ({
id: v.id,
title: v.title,
level_description:
result.skill_information_level == 1
? v.level1
: result.skill_information_level == 2
? v.level2
: result.skill_information_level == 3
? v.level3
: result.skill_information_level == 4
? v.level4
: v.level5,
level: result.skill_information_level,
}));
const resourses = await this.skillRepository.find({
where: { type: TypeSkill.RESOURSE, active: 1 },
});
const resourse = await resourses.map((v) => ({
id: v.id,
title: v.title,
level_description:
result.skill_resourse_level == 1
? v.level1
: result.skill_resourse_level == 2
? v.level2
: result.skill_resourse_level == 3
? v.level3
: result.skill_resourse_level == 4
? v.level4
: v.level5,
level: result.skill_resourse_level,
}));
return new HttpSuccess({
computer: skill,
english: english,
information: information,
resourse: resourse,
});
}
/**
* API list
*
* @summary options
*
*/
@Get("law")
async GetLaw(
@Query() personal_id: string,
@Request() request: RequestWithUser
) {
const results = await this.lawRepository.find({
select: ["id", "parent_id", "description", "status_select"],
where: {
active: 1,
},
});
if (!results) {
return new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบข้อมูล");
}
const result = await results.map((v) => ({
...v,
checked: 0,
}));
return new HttpSuccess(result);
}
/**
* API
*
* @summary
*
*/
@Get("new-assign")
async NewAssign(
@Query() personal_id: string,
@Request() request: RequestWithUser
) {
const person = await this.personalRepository.findOne({
select: [
"personal_id",
"prefixName",
"firstName",
"lastName",
"posNo",
"positionName",
"positionLevelName",
"positionLineName",
"isProbation",
"orgRootName",
"organization",
"createdAt",
"updatedAt",
],
where: { personal_id },
});
if (!person) {
return new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบข้อมูล");
}
const assign = await this.assignRepository.count({
where: {
personal_id,
},
});
const responsePerson = {
id: person.personal_id,
...person,
};
const data = await {
person: responsePerson,
assign_no: assign + 1,
assign_month: 6,
};
return new HttpSuccess({ data: data });
}
}

View file

@ -0,0 +1,444 @@
import {
Controller,
Route,
Security,
Tags,
Request,
SuccessResponse,
Response,
Get,
Post,
Body,
Query,
Put,
} from "tsoa";
import HttpSuccess from "../interfaces/http-success";
import HttpStatusCode from "../interfaces/http-status";
import { RequestWithUser } from "../middlewares/user";
import { findEndDate, setLogDataDiff } from "../interfaces/utils";
import { AppDataSource } from "../database/data-source";
import { AssignDirector } from "../entities/AssignDirector";
import HttpError from "../interfaces/http-error";
import { Assign } from "../entities/Assign";
import { Personal } from "../entities/Personal";
import CallAPI from "../interfaces/call-api";
import {
CreateEvaluateChairman,
EvaluateChairman,
} from "../entities/EvaluateChairman";
@Route("api/v1/evaluate-chairman")
@Tags("แบบประเมินผล (คณะกรรมการ)")
@Security("bearerAuth")
@Response(
HttpStatusCode.INTERNAL_SERVER_ERROR,
"เกิดข้อผิดพลาด ไม่สามารถแสดงรายการได้ กรุณาลองใหม่ในภายหลัง"
)
@SuccessResponse(HttpStatusCode.OK, "สำเร็จ")
export class EvaluateChairmanController extends Controller {
private assignDirectorRepository =
AppDataSource.getRepository(AssignDirector);
private assignRepository = AppDataSource.getRepository(Assign);
private evaluateChairmanRepository =
AppDataSource.getRepository(EvaluateChairman);
private personalRepository = AppDataSource.getRepository(Personal);
/**
* API ()
*
* @summary ()
*
*/
@Get("create")
async CreateEvaluate(
@Query() assign_id: string,
@Request() request: RequestWithUser
) {
const director = await this.assignDirectorRepository.findOne({
select: ["personal_id"],
where: {
assign_id,
role: "chairman",
},
});
if (!director) {
return new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบข้อมูลผู้ดูแล");
}
const director_id = director.personal_id;
const assign = await this.assignRepository.findOne({
relations: ["profile"],
where: { id: assign_id },
});
if (!assign) {
return new HttpError(
HttpStatusCode.NOT_FOUND,
"ไม่พบข้อมูลแบบมอบหมายงาน"
);
}
const profile = await (assign.profile
? {
...assign.profile,
id: assign.profile.personal_id,
name:
assign.profile.prefixName +
assign.profile.firstName +
" " +
assign.profile.lastName,
Oc: assign.profile.organization,
}
: null);
const evaluate_amount = await this.evaluateChairmanRepository.count({
where: {
assign_id,
director_id,
},
});
const evaluate_no = await (evaluate_amount + 1);
const start_date =
evaluate_amount == 0
? assign.date_start
: findEndDate(evaluate_amount * 3, assign.date_start);
const directorData = await this.assignDirectorRepository.find({
where: { assign_id },
});
if (!directorData) {
return new HttpError(
HttpStatusCode.NOT_FOUND,
"ไม่พบข้อมูลผู้ดูแล ผู้บังคับบัญชาและประธาน"
);
}
let mentors = "";
const mentorList = await directorData.filter((x) => x.role == "mentor");
if (mentorList.length > 0) {
for (let index = 0; index < mentorList.length; index++) {
const e = await mentorList[index];
mentors += e.fullname;
if (index < mentorList.length - 1) {
mentors += ", ";
}
}
}
const commanderData = await (directorData.find(
(x) => x.role == "commander"
) ?? null);
const commander = commanderData ? commanderData.fullname : null;
const chairmanData = await (directorData.find(
(x) => x.role == "chairman"
) ?? null);
const chairman = chairmanData ? chairmanData.fullname : null;
return new HttpSuccess({
person: profile ? profile : null,
assign,
evaluate_no: evaluate_no,
start_date: start_date,
end_date: findEndDate(3, start_date),
commander,
mentors,
chairman,
});
}
/**
* API ()
*
* @summary ()
*
*/
@Get("")
async GetEvaluate(@Query() assign_id: string, @Query() evaluate_no?: string) {
// ต้องปรับเป็น id ของคนที่ access เข้ามา
const director = await this.assignDirectorRepository.findOne({
select: ["personal_id"],
where: {
assign_id,
role: "chairman",
},
});
if (!director) {
return new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบข้อมูลผู้ดูแล");
}
const director_id = director.personal_id;
let evaluate: any = null;
if (evaluate_no) {
evaluate = await this.evaluateChairmanRepository.findOne({
where: {
director_id,
assign_id,
no: evaluate_no,
},
});
} else {
evaluate = await this.evaluateChairmanRepository.findOne({
where: {
director_id,
assign_id,
},
});
}
if (!evaluate) {
return new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบข้อมูลแบบประเมิน");
}
const assign = await this.assignRepository.findOne({
where: { id: assign_id },
});
if (!assign) {
return new HttpError(
HttpStatusCode.NOT_FOUND,
"ไม่พบข้อมูลแบบมอบหมายงาน"
);
}
const experimenteeData = await this.personalRepository.find({
select: [
"personal_id",
"prefixName",
"firstName",
"lastName",
"positionName",
"positionLevelName",
"organization",
],
where: { personal_id: assign.personal_id },
});
const experimentee = await experimenteeData.map((element) => ({
...element,
name: element.prefixName + element.firstName + " " + element.lastName,
Oc: element.organization,
}));
const directorData = await this.assignDirectorRepository.find({
where: { assign_id },
});
if (!directorData) {
return new HttpError(
HttpStatusCode.NOT_FOUND,
"ไม่พบข้อมูลผู้ดูแล ผู้บังคับบัญชาและประธาน"
);
}
let mentors = "";
const mentorList = await directorData.filter((x) => x.role == "mentor");
if (mentorList.length > 0) {
for (let index = 0; index < mentorList.length; index++) {
const e = await mentorList[index];
mentors += e.fullname;
if (index < mentorList.length - 1) {
mentors += ", ";
}
}
}
const commanderData = await (directorData.find(
(x) => x.role == "commander"
) ?? null);
const commander = commanderData ? commanderData.fullname : null;
const chairmanData = await (directorData.find(
(x) => x.role == "chairman"
) ?? null);
const chairman = chairmanData ? chairmanData.fullname : null;
return new HttpSuccess({
experimentee: experimentee,
mentors: mentors,
commander: commander,
chairman: chairman,
assign,
evaluate,
});
}
/**
* API ()
*
* @summary ()
*
*/
@Post("")
async PostData(
@Query() assign_id: string,
@Body() requestBody: CreateEvaluateChairman,
@Request() request: RequestWithUser
) {
const director = await this.assignDirectorRepository.findOne({
select: ["personal_id"],
where: {
assign_id,
role: "chairman",
},
});
if (!director) {
return new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบข้อมูลผู้ดูแล");
}
const director_id = director.personal_id;
const assign = await this.assignRepository.findOne({
relations: ["profile"],
where: { id: assign_id },
});
if (!assign) {
return new HttpError(
HttpStatusCode.NOT_FOUND,
"ไม่พบข้อมูลแบบมอบหมายงาน"
);
}
const postData: any = await {
assign_id,
...requestBody,
director_id,
no: requestBody.evaluate_no,
date_start: requestBody.start_date,
personal_id: assign.personal_id,
achievement_other_desc: requestBody.achievement_other
? requestBody.achievement_other.text
: "",
achievement_other_level: requestBody.achievement_other
? Number(requestBody.achievement_other.level)
: 0,
behavior_other_desc: requestBody.behavior_orther.text,
behavior_other_level:
requestBody.behavior_orther.text != ""
? Number(requestBody.behavior_orther.level)
: 0,
createdUserId: request.user.sub,
createdFullName: request.user.name,
updateUserId: request.user.sub,
updateFullName: request.user.name,
};
await this.evaluateChairmanRepository.save(postData, {
data: request,
});
setLogDataDiff(request, { before: null, after: postData });
if (Number(requestBody.evaluate_no) < 2) {
// #noted cronjob
// แจ้งประธานเข้ามาบันทึกผลทุก 3 เดือน 2 ครั้ง
var dateSend = await findEndDate(3, requestBody.start_date);
const nextNo = await (Number(requestBody.evaluate_no) + 1);
await new CallAPI()
.PostData(request, "/placement/noti", {
subject: `ถึงกำหนดประเมินผลการทดลองปฏิบัติหน้าที่ราชการครั้งที่ ${nextNo} ${assign.profile.prefixName}${assign.profile.firstName} ${assign.profile.lastName}`,
body: `ถึงกำหนดประเมินผลการทดลองปฏิบัติหน้าที่ราชการ (สำหรับคณะกรรมการ) ครั้งที่ ${nextNo} ${assign.profile.prefixName}${assign.profile.firstName} ${assign.profile.lastName}`,
receiverUserId: director_id,
payload: "",
isSendMail: true,
isSendInbox: true,
receiveDate: dateSend,
})
.catch((error) => {
console.error("Error calling API:", error);
});
}
return new HttpSuccess();
}
/**
* API ()
*
* @summary ()
*
*/
@Put("")
async UpdateData(
@Query() assign_id: string,
@Query() evaluate_id: string,
@Body() requestBody: CreateEvaluateChairman,
@Request() request: RequestWithUser
) {
let evaluate = await this.evaluateChairmanRepository.findOne({
where: { id: evaluate_id },
});
const before = evaluate;
if (!evaluate) {
return new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบข้อมูลการประเมิน");
}
evaluate.chairman_dated = requestBody.chairman_dated;
evaluate.director1_dated = requestBody.director1_dated;
evaluate.director2_dated = requestBody.director2_dated;
evaluate.knowledge_level = requestBody.knowledge_level;
evaluate.apply_level = requestBody.apply_level;
evaluate.success_level = requestBody.success_level;
evaluate.achievement_other_desc = requestBody.achievement_other
? requestBody.achievement_other.text
: "";
evaluate.achievement_other_level =
requestBody.achievement_other.text != ""
? Number(requestBody.achievement_other.level)
: 0;
evaluate.conduct1_level = requestBody.conduct1_level;
evaluate.conduct2_level = requestBody.conduct2_level;
evaluate.conduct3_level = requestBody.conduct3_level;
evaluate.conduct4_level = requestBody.conduct4_level;
evaluate.moral1_level = requestBody.moral1_level;
evaluate.moral2_level = requestBody.moral2_level;
evaluate.moral3_level = requestBody.moral3_level;
evaluate.discipline1_level = requestBody.discipline1_level;
evaluate.discipline2_level = requestBody.discipline2_level;
evaluate.discipline3_level = requestBody.discipline3_level;
evaluate.discipline4_level = requestBody.discipline4_level;
evaluate.discipline5_level = requestBody.discipline5_level;
evaluate.behavior_other_desc = requestBody.behavior_orther.text;
evaluate.behavior_other_level =
requestBody.behavior_orther.text != ""
? Number(requestBody.behavior_orther.level)
: 0;
evaluate.develop_orientation_score = requestBody.develop_orientation_score;
evaluate.develop_self_learning_score =
requestBody.develop_self_learning_score;
evaluate.develop_training_seminar_score =
requestBody.develop_training_seminar_score;
evaluate.develop_other_training_score =
requestBody.develop_other_training_score;
evaluate.develop_orientation_percent =
requestBody.develop_orientation_percent;
evaluate.develop_self_learning_percent =
requestBody.develop_self_learning_percent;
evaluate.develop_training_seminar_percent =
requestBody.develop_training_seminar_percent;
evaluate.develop_other_training_percent =
requestBody.develop_other_training_percent;
evaluate.develop_result = requestBody.develop_result;
evaluate.achievement_score = requestBody.achievement_score;
evaluate.achievement_score_total = requestBody.achievement_score_total;
evaluate.achievement_percent = requestBody.achievement_percent;
evaluate.achievement_result = requestBody.achievement_result;
evaluate.behavior_score = requestBody.behavior_score;
evaluate.behavior_score_total = requestBody.behavior_score_total;
evaluate.behavior_percent = requestBody.behavior_percent;
evaluate.behavior_result = requestBody.behavior_result;
evaluate.sum_score = requestBody.sum_score;
evaluate.sum_percent = requestBody.sum_percent;
evaluate.evaluate_result = requestBody.evaluate_result;
evaluate.updateUserId = request.user.sub;
evaluate.updateFullName = request.user.name;
await this.evaluateChairmanRepository.save(evaluate, { data: request });
setLogDataDiff(request, { before, after: evaluate });
return new HttpSuccess();
}
}

View file

@ -0,0 +1,414 @@
import {
Controller,
Route,
Security,
Tags,
Request,
SuccessResponse,
Response,
Get,
Post,
Body,
Query,
Put,
} from "tsoa";
import HttpSuccess from "../interfaces/http-success";
import HttpStatusCode from "../interfaces/http-status";
import { RequestWithUser } from "../middlewares/user";
import { findEndDate, setLogDataDiff } from "../interfaces/utils";
import { AppDataSource } from "../database/data-source";
import { AssignDirector } from "../entities/AssignDirector";
import HttpError from "../interfaces/http-error";
import { Assign } from "../entities/Assign";
import {
CreateEvaluateCommander,
EvaluateCommander,
} from "../entities/EvaluateCommander";
import { Personal } from "../entities/Personal";
import CallAPI from "../interfaces/call-api";
@Route("api/v1/evaluate")
@Tags("แบบประเมินผล (ผู้บังคับบัญชา)")
@Security("bearerAuth")
@Response(
HttpStatusCode.INTERNAL_SERVER_ERROR,
"เกิดข้อผิดพลาด ไม่สามารถแสดงรายการได้ กรุณาลองใหม่ในภายหลัง"
)
@SuccessResponse(HttpStatusCode.OK, "สำเร็จ")
export class EvaluateController extends Controller {
private assignDirectorRepository =
AppDataSource.getRepository(AssignDirector);
private assignRepository = AppDataSource.getRepository(Assign);
private evaluateCommanderRepository =
AppDataSource.getRepository(EvaluateCommander);
private personalRepository = AppDataSource.getRepository(Personal);
/**
* API ()
*
* @summary ()
*
*/
@Get("create")
async CreateEvaluate(
@Query() assign_id: string,
@Request() request: RequestWithUser
) {
const director = await this.assignDirectorRepository.findOne({
select: ["personal_id"],
where: {
assign_id,
role: "commander",
},
});
if (!director) {
return new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบข้อมูลผู้ดูแล");
}
const director_id = director.personal_id;
const assign = await this.assignRepository.findOne({
relations: ["profile"],
where: { id: assign_id },
});
if (!assign) {
return new HttpError(
HttpStatusCode.NOT_FOUND,
"ไม่พบข้อมูลแบบมอบหมายงาน"
);
}
const profile = await (assign.profile
? {
...assign.profile,
id: assign.profile.personal_id,
name:
assign.profile.prefixName +
assign.profile.firstName +
" " +
assign.profile.lastName,
Oc: assign.profile.organization,
}
: null);
const evaluate_amount = await this.evaluateCommanderRepository.count({
where: {
assign_id,
director_id,
},
});
const evaluate_no = await (evaluate_amount + 1);
const start_date =
evaluate_amount == 0
? assign.date_start
: findEndDate(evaluate_amount * 3, assign.date_start);
const commanderData = await this.assignDirectorRepository.find({
select: [
"personal_id",
"dated",
"fullname",
"position",
"posType",
"posLevel",
],
where: { personal_id: director_id },
});
const commander = await commanderData.map((element) => ({
...element,
name: element.fullname,
label: `${element.fullname} (${element.position}, ${element.posType}: ${element.posLevel})`,
}));
return new HttpSuccess({
person: profile,
assign,
evaluate_no: evaluate_no,
start_date: start_date,
end_date: findEndDate(3, start_date),
director: commander,
});
}
/**
* API ()
*
* @summary ()
*
*/
@Get("")
async GetEvaluate(@Query() assign_id: string, @Query() evaluate_no?: string) {
// ต้องปรับเป็น id ของคนที่ access เข้ามา
const director = await this.assignDirectorRepository.findOne({
select: ["personal_id"],
where: {
assign_id,
role: "commander",
},
});
if (!director) {
return new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบข้อมูลผู้ดูแล");
}
const director_id = director.personal_id;
let evaluate: any = null;
if (evaluate_no) {
evaluate = await this.evaluateCommanderRepository.findOne({
where: {
director_id,
assign_id,
no: evaluate_no,
},
});
} else {
evaluate = await this.evaluateCommanderRepository.findOne({
where: {
director_id,
assign_id,
},
});
}
if (!evaluate) {
return new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบข้อมูลแบบประเมิน");
}
const assign = await this.assignRepository.findOne({
where: { id: assign_id },
});
if (!assign) {
return new HttpError(
HttpStatusCode.NOT_FOUND,
"ไม่พบข้อมูลแบบมอบหมายงาน"
);
}
// const profile = await (assign.profile
// ? {
// ...assign.profile,
// id: assign.profile.personal_id,
// name:
// assign.profile.prefixName +
// assign.profile.firstName +
// " " +
// assign.profile.lastName,
// Oc: assign.profile.organization,
// }
// : null);
// const evaluate_amount = await this.evaluateCommanderRepository.count({
// where: {
// assign_id,
// director_id,
// },
// });
// const evaluate_no = await (evaluate_amount + 1);
// const start_date =
// evaluate_amount == 0
// ? assign.date_start
// : findEndDate(evaluate_amount * 3, assign.date_start);
const directorData = await this.assignDirectorRepository.find({
select: [
"personal_id",
"dated",
"fullname",
"position",
"posType",
"posLevel",
],
where: { personal_id: director_id },
});
const directors = await directorData.map((element) => ({
...element,
name: element.fullname,
label: `${element.fullname} (${element.position}, ${element.posType}: ${element.posLevel})`,
}));
const experimenteeData = await this.personalRepository.find({
select: [
"personal_id",
"prefixName",
"firstName",
"lastName",
"positionName",
"positionLevelName",
"organization",
],
where: { personal_id: assign.personal_id },
});
const experimentee = await experimenteeData.map((element) => ({
...element,
name: element.prefixName + element.firstName + " " + element.lastName,
Oc: element.organization,
}));
return new HttpSuccess({
experimentee: experimentee,
director: directors ? directors : null,
assign,
evaluate,
});
}
/**
* API ()
*
* @summary ()
*
*/
@Post("")
async PostData(
@Query() assign_id: string,
@Body() requestBody: CreateEvaluateCommander,
@Request() request: RequestWithUser
) {
const director = await this.assignDirectorRepository.findOne({
select: ["personal_id"],
where: {
assign_id,
role: "commander",
},
});
if (!director) {
return new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบข้อมูลผู้ดูแล");
}
const director_id = director.personal_id;
const assign = await this.assignRepository.findOne({
relations: ["profile"],
where: { id: assign_id },
});
if (!assign) {
return new HttpError(
HttpStatusCode.NOT_FOUND,
"ไม่พบข้อมูลแบบมอบหมายงาน"
);
}
const postData: any = await {
assign_id,
...requestBody,
director_id,
no: requestBody.evaluate_no,
date_start: requestBody.start_date,
personal_id: assign.personal_id,
achievement_other_desc: requestBody.achievement_other
? requestBody.achievement_other.text
: "",
achievement_other_level: requestBody.achievement_other
? Number(requestBody.achievement_other.level)
: 0,
behavior_other_desc: requestBody.behavior_orther.text,
behavior_other_level:
requestBody.behavior_orther.text != ""
? Number(requestBody.behavior_orther.level)
: 0,
createdUserId: request.user.sub,
createdFullName: request.user.name,
updateUserId: request.user.sub,
updateFullName: request.user.name,
};
await this.evaluateCommanderRepository.save(postData, {
data: request,
});
setLogDataDiff(request, { before: null, after: postData });
if (Number(requestBody.evaluate_no) < 2) {
// #noted cronjob
// แจ้งผู้บังคับบัญชาเข้ามาบันทึกผลทุก 3 เดือน 2 ครั้ง
var dateSend = await findEndDate(3, requestBody.start_date);
const nextNo = await (Number(requestBody.evaluate_no) + 1);
await new CallAPI()
.PostData(request, "/placement/noti", {
subject: `ถึงกำหนดประเมินผลการทดลองปฏิบัติหน้าที่ราชการครั้งที่ ${nextNo} ${assign.profile.prefixName}${assign.profile.firstName} ${assign.profile.lastName}`,
body: `ถึงกำหนดประเมินผลการทดลองปฏิบัติหน้าที่ราชการ (สำหรับผู้บังคับบัญชา) ครั้งที่ ${nextNo} ${assign.profile.prefixName}${assign.profile.firstName} ${assign.profile.lastName}`,
receiverUserId: director_id,
payload: "",
isSendMail: true,
isSendInbox: true,
receiveDate: dateSend,
})
.catch((error) => {
console.error("Error calling API:", error);
});
}
return new HttpSuccess();
}
/**
* API ()
*
* @summary ()
*
*/
@Put("")
async UpdateData(
@Query() assign_id: string,
@Query() evaluate_id: string,
@Body() requestBody: CreateEvaluateCommander,
@Request() request: RequestWithUser
) {
let evaluate = await this.evaluateCommanderRepository.findOne({
where: { id: evaluate_id },
});
const before = evaluate;
if (!evaluate) {
return new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบข้อมูลการประเมิน");
}
evaluate.commander_dated = requestBody.commander_dated;
evaluate.knowledge_level = requestBody.knowledge_level;
evaluate.skill_level = requestBody.skill_level;
evaluate.competency_level = requestBody.competency_level;
evaluate.learn_level = requestBody.learn_level;
evaluate.apply_level = requestBody.apply_level;
evaluate.success_level = requestBody.success_level;
evaluate.achievement_other_desc = requestBody.achievement_other
? requestBody.achievement_other.text
: "";
evaluate.achievement_other_level =
requestBody.achievement_other.text != ""
? Number(requestBody.achievement_other.level)
: 0;
evaluate.conduct1_level = requestBody.conduct1_level;
evaluate.conduct2_level = requestBody.conduct2_level;
evaluate.conduct3_level = requestBody.conduct3_level;
evaluate.conduct4_level = requestBody.conduct4_level;
evaluate.moral1_level = requestBody.moral1_level;
evaluate.moral2_level = requestBody.moral2_level;
evaluate.moral3_level = requestBody.moral3_level;
evaluate.discipline1_level = requestBody.discipline1_level;
evaluate.discipline2_level = requestBody.discipline2_level;
evaluate.discipline3_level = requestBody.discipline3_level;
evaluate.discipline4_level = requestBody.discipline4_level;
evaluate.discipline5_level = requestBody.discipline5_level;
evaluate.behavior_other_desc = requestBody.behavior_orther.text;
evaluate.behavior_other_level =
requestBody.behavior_orther.text != ""
? Number(requestBody.behavior_orther.level)
: 0;
evaluate.behavior_strength_desc = requestBody.behavior_strength_desc;
evaluate.behavior_improve_desc = requestBody.behavior_improve_desc;
evaluate.orientation = requestBody.orientation;
evaluate.self_learning = requestBody.self_learning;
evaluate.training_seminar = requestBody.training_seminar;
evaluate.other_training = requestBody.other_training;
evaluate.updateUserId = request.user.sub;
evaluate.updateFullName = request.user.name;
await this.evaluateCommanderRepository.save(evaluate, { data: request });
setLogDataDiff(request, { before, after: evaluate });
return new HttpSuccess();
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,444 @@
import {
Controller,
Route,
Security,
Tags,
Request,
SuccessResponse,
Response,
Get,
Post,
Body,
Query,
Put,
} from "tsoa";
import HttpSuccess from "../interfaces/http-success";
import HttpStatusCode from "../interfaces/http-status";
import { RequestWithUser } from "../middlewares/user";
import { setLogDataDiff } from "../interfaces/utils";
import { AppDataSource } from "../database/data-source";
import { AssignDirector } from "../entities/AssignDirector";
import HttpError from "../interfaces/http-error";
import { Assign } from "../entities/Assign";
import {
CreateEvaluateCommander,
EvaluateCommander,
} from "../entities/EvaluateCommander";
import { Personal } from "../entities/Personal";
import CallAPI from "../interfaces/call-api";
import { EvaluateChairman } from "../entities/EvaluateChairman";
import {
CreateEvaluateResult,
EvaluateResult,
} from "../entities/EvaluateResult";
@Route("api/v1/evaluate-result")
@Tags("แบบรายงานการประเมินฯ")
@Security("bearerAuth")
@Response(
HttpStatusCode.INTERNAL_SERVER_ERROR,
"เกิดข้อผิดพลาด ไม่สามารถแสดงรายการได้ กรุณาลองใหม่ในภายหลัง"
)
@SuccessResponse(HttpStatusCode.OK, "สำเร็จ")
export class EvaluateResultController extends Controller {
private assignDirectorRepository =
AppDataSource.getRepository(AssignDirector);
private assignRepository = AppDataSource.getRepository(Assign);
private evaluateChairmanRepository =
AppDataSource.getRepository(EvaluateChairman);
private personalRepository = AppDataSource.getRepository(Personal);
private evaluateResultRepository =
AppDataSource.getRepository(EvaluateResult);
/**
* API
*
* @summary
*
*/
@Get("create")
async CreateEvaluate(
@Query() assign_id: string,
@Request() request: RequestWithUser
) {
const assign = await this.assignRepository.findOne({
relations: ["profile"],
where: { id: assign_id },
});
if (!assign) {
return new HttpError(
HttpStatusCode.NOT_FOUND,
"ไม่พบข้อมูลแบบมอบหมายงาน"
);
}
const profile = await (assign.profile
? {
...assign.profile,
id: assign.profile.personal_id,
name:
assign.profile.prefixName +
assign.profile.firstName +
" " +
assign.profile.lastName,
Oc: assign.profile.organization,
}
: null);
const directorData = await this.assignDirectorRepository.find({
where: { assign_id },
});
if (!directorData) {
return new HttpError(
HttpStatusCode.NOT_FOUND,
"ไม่พบข้อมูลผู้ดูแล ผู้บังคับบัญชาและประธาน"
);
}
let mentors = "";
const mentorList = await directorData.filter((x) => x.role == "mentor");
if (mentorList.length > 0) {
for (let index = 0; index < mentorList.length; index++) {
const e = await mentorList[index];
mentors += e.fullname;
if (index < mentorList.length - 1) {
mentors += ", ";
}
}
}
const commanderData = await (directorData.find(
(x) => x.role == "commander"
) ?? null);
const commander = commanderData ? commanderData.fullname : null;
const chairmanData = await (directorData.find(
(x) => x.role == "chairman"
) ?? null);
const chairman = chairmanData ? chairmanData.fullname : null;
const resultData = await this.evaluateChairmanRepository.findOne({
select: [
"develop_orientation_score",
"develop_self_learning_score",
"develop_training_seminar_score",
"evaluate_result",
],
where: {
assign_id,
},
});
if (!resultData) {
return new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบข้อมูลการประเมินผล");
}
const develop_complete = await (resultData.develop_orientation_score > 0 &&
resultData.develop_self_learning_score > 0 &&
resultData.develop_training_seminar_score > 0
? 1
: 2);
const evaluate_result = await (resultData.evaluate_result == 1 ? 1 : 2);
const result = await {
develop_complete: develop_complete,
evaluate_result: evaluate_result,
};
return new HttpSuccess({
person: profile,
assign,
result,
mentors,
commander,
chairman,
});
}
/**
* API
*
* @summary
*
*/
@Get("")
async GetEvaluate(@Query() assign_id: string, @Query() evaluate_no?: string) {
// ต้องปรับเป็น id ของคนที่ access เข้ามา
const director = await this.assignDirectorRepository.findOne({
select: ["personal_id"],
where: {
assign_id,
role: "chairman",
},
});
if (!director) {
return new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบข้อมูลผู้ดูแล");
}
const director_id = director.personal_id;
const evaluate = await this.evaluateChairmanRepository.findOne({
where: {
director_id,
assign_id,
},
});
if (!evaluate) {
return new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบข้อมูลแบบประเมิน");
}
const assign = await this.assignRepository.findOne({
where: { id: assign_id },
});
if (!assign) {
return new HttpError(
HttpStatusCode.NOT_FOUND,
"ไม่พบข้อมูลแบบมอบหมายงาน"
);
}
const experimenteeData = await this.personalRepository.find({
select: [
"personal_id",
"prefixName",
"firstName",
"lastName",
"positionName",
"positionLevelName",
"organization",
],
where: { personal_id: assign.personal_id },
});
const experimentee = await experimenteeData.map((element) => ({
...element,
name: element.prefixName + element.firstName + " " + element.lastName,
Oc: element.organization,
}));
const directorData = await this.assignDirectorRepository.find({
where: { assign_id },
});
if (!directorData) {
return new HttpError(
HttpStatusCode.NOT_FOUND,
"ไม่พบข้อมูลผู้ดูแล ผู้บังคับบัญชาและประธาน"
);
}
let mentors = "";
const mentorList = await directorData.filter((x) => x.role == "mentor");
if (mentorList.length > 0) {
for (let index = 0; index < mentorList.length; index++) {
const e = await mentorList[index];
mentors += e.fullname;
if (index < mentorList.length - 1) {
mentors += ", ";
}
}
}
const commanderData = await (directorData.find(
(x) => x.role == "commander"
) ?? null);
const commander = commanderData ? commanderData.fullname : null;
const chairmanData = await (directorData.find(
(x) => x.role == "chairman"
) ?? null);
const chairman = chairmanData ? chairmanData.fullname : null;
return new HttpSuccess({
commander,
chairman,
mentors,
experimentee,
assign,
evaluate: evaluate,
});
}
/**
* API
*
* @summary
*
*/
@Post("")
async PostData(
@Query() assign_id: string,
@Body() requestBody: CreateEvaluateResult,
@Request() request: RequestWithUser
) {
const director = await this.assignDirectorRepository.findOne({
select: ["personal_id"],
where: {
assign_id,
role: "chairman",
},
});
if (!director) {
return new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบข้อมูลผู้ดูแล");
}
const director_id = director.personal_id;
const assign = await this.assignRepository.findOne({
relations: ["profile"],
where: { id: assign_id },
});
if (!assign) {
return new HttpError(
HttpStatusCode.NOT_FOUND,
"ไม่พบข้อมูลแบบมอบหมายงาน"
);
}
const postData: any = await {
assign_id,
...requestBody,
director_id,
no: 1,
personal_id: assign.personal_id,
date_start: requestBody.start_date,
expand_month:
requestBody.pass_result == 3 ? Number(requestBody.expand_month) : 0,
createdUserId: request.user.sub,
createdFullName: request.user.name,
updateUserId: request.user.sub,
updateFullName: request.user.name,
};
await this.evaluateResultRepository.save(postData, {
data: request,
});
setLogDataDiff(request, { before: null, after: postData });
const personal = await this.personalRepository.findOne({
where: { personal_id: assign.personal_id },
});
if (!personal) {
return new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบข้อมูลบุคคล");
}
personal.probation_status =
requestBody.pass_result == 1
? 2
: requestBody.pass_result == 2
? 3
: personal.probation_status;
if (requestBody.pass_result == 3) {
personal.probation_status = 7;
// #noti ผลการประเมินการทดลองปฏิบัติหน้าที่ราชการ กรณีขยายระยะเวลา
await new CallAPI()
.PostData(request, "/placement/noti", {
subject: "ผลการประเมินการทดลองปฏิบัติหน้าที่ราชการ",
body: `ผลการประเมินการทดลองปฏิบัติหน้าที่ราชการ เห็นควรให้ขยายระยะเวลาทดลองปฏิบัติหน้าที่ราชการต่อไปอีก ${requestBody.expand_month} เดือน`,
receiverUserId: assign.personal_id,
payload: "",
isSendMail: false,
isSendInbox: true,
isSendNotification: true,
})
.catch((error) => {
console.error("Error calling API:", error);
});
}
await this.personalRepository.save(personal, { data: request });
return new HttpSuccess();
}
/**
* API
*
* @summary
*
*/
@Put("")
async UpdateData(
@Query() assign_id: string,
@Query() evaluate_id: string,
@Body() requestBody: CreateEvaluateResult,
@Request() request: RequestWithUser
) {
let evaluate = await this.evaluateResultRepository.findOne({
where: { id: evaluate_id },
});
const before = evaluate;
if (!evaluate) {
return new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบข้อมูลการประเมิน");
}
evaluate.date_start = requestBody.start_date;
evaluate.date_finish = requestBody.date_finish;
evaluate.develop_complete = requestBody.develop_complete;
evaluate.pass_result = requestBody.pass_result;
evaluate.expand_month =
requestBody.pass_result == 3 ? requestBody.expand_month : 0;
evaluate.reson = requestBody.reson;
evaluate.chairman_dated = requestBody.chairman_dated;
evaluate.director1_dated = requestBody.director1_dated;
evaluate.director2_dated = requestBody.director2_dated;
evaluate.updateUserId = request.user.sub;
evaluate.updateFullName = request.user.name;
await this.evaluateResultRepository.save(evaluate, { data: request });
setLogDataDiff(request, { before, after: evaluate });
const assign = await this.assignRepository.findOne({
relations: ["profile"],
where: { id: assign_id },
});
if (!assign) {
return new HttpError(
HttpStatusCode.NOT_FOUND,
"ไม่พบข้อมูลแบบมอบหมายงาน"
);
}
const personal = await this.personalRepository.findOne({
where: { personal_id: assign.personal_id },
});
if (!personal) {
return new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบข้อมูลบุคคล");
}
personal.probation_status =
requestBody.pass_result == 1
? 2
: requestBody.pass_result == 2
? 3
: personal.probation_status;
if (requestBody.pass_result == 3) {
personal.probation_status = 7;
// #noti ผลการประเมินการทดลองปฏิบัติหน้าที่ราชการ กรณีขยายระยะเวลา
await new CallAPI()
.PostData(request, "/placement/noti", {
subject: "ผลการประเมินการทดลองปฏิบัติหน้าที่ราชการ",
body: `ผลการประเมินการทดลองปฏิบัติหน้าที่ราชการ เห็นควรให้ขยายระยะเวลาทดลองปฏิบัติหน้าที่ราชการต่อไปอีก ${requestBody.expand_month} เดือน`,
receiverUserId: assign.personal_id,
payload: "",
isSendMail: false,
isSendInbox: true,
isSendNotification: true,
})
.catch((error) => {
console.error("Error calling API:", error);
});
}
await this.personalRepository.save(personal, { data: request });
return new HttpSuccess();
}
}

View file

@ -0,0 +1,11 @@
import { Controller, Get, Route, Security, Tags } from "tsoa";
@Route("/hello")
@Tags("Test")
@Security("bearerAuth")
export class AppController extends Controller {
@Get()
public async GET() {
return { message: "Hello Development" };
}
}

View file

@ -0,0 +1,195 @@
import {
Controller,
Post,
Route,
Security,
Tags,
Body,
Request,
SuccessResponse,
Response,
Get,
Query,
} from "tsoa";
import { AppDataSource } from "../database/data-source";
import HttpSuccess from "../interfaces/http-success";
import HttpStatusCode from "../interfaces/http-status";
import HttpError from "../interfaces/http-error";
import { RequestWithUser } from "../middlewares/user";
import { setLogDataDiff } from "../interfaces/utils";
import { Personal, PostPersonal } from "../entities/Personal";
import permission from "../interfaces/permission";
import { Assign } from "../entities/Assign";
@Route("api/v1/personal")
@Tags("Personal")
@Security("bearerAuth")
@Response(
HttpStatusCode.INTERNAL_SERVER_ERROR,
"เกิดข้อผิดพลาด ไม่สามารถแสดงรายการได้ กรุณาลองใหม่ในภายหลัง"
)
@SuccessResponse(HttpStatusCode.OK, "สำเร็จ")
export class PersonalController extends Controller {
private personalRepository = AppDataSource.getRepository(Personal);
private assignRepository = AppDataSource.getRepository(Assign);
/**
* API
*
* @summary
*
*/
@Post("add")
async AddPersonal(
@Body() requestBody: PostPersonal,
@Request() request: RequestWithUser
) {
await new permission().PermissionCreate(request, "SYS_PROBATION");
const checkPersonal: number = await this.personalRepository.count({
where: { personal_id: requestBody.id },
});
if (checkPersonal > 0) {
return new HttpError(
HttpStatusCode.BAD_REQUEST,
"ผู้ทดลองปฏิบัติหน้าที่ราชการนี้มีอยู่แล้ว"
);
}
let organization = await (requestBody.orgChild4Name
? requestBody.orgChild4Name + "/"
: "");
organization += await (requestBody.orgChild3Name
? requestBody.orgChild3Name + "/"
: "");
organization += await (requestBody.orgChild2Name
? requestBody.orgChild2Name + "/"
: "");
organization += await (requestBody.orgChild1Name
? requestBody.orgChild1Name + "/"
: "");
organization += await (requestBody.orgRootName
? requestBody.orgRootName
: "");
const personalData = Object.assign(new Personal());
personalData.personal_id = requestBody.id;
personalData.order_number = requestBody.order_number
? requestBody.order_number
: "";
personalData.probation_status = 1;
personalData.createdUserId = request.user.sub;
personalData.createdFullName = request.user.name;
personalData.updateUserId = request.user.sub;
personalData.updateFullName = request.user.name;
personalData.prefixName = requestBody.prefix;
personalData.firstName = requestBody.firstName;
personalData.lastName = requestBody.lastName;
personalData.isProbation = requestBody.isProbation ? 1 : 0;
personalData.positionLevelName = requestBody.posLevelName
? requestBody.posLevelName
: "";
personalData.positionName = requestBody.position
? requestBody.position
: "";
personalData.positionLineName = requestBody.posLineName;
personalData.positionTypeName = requestBody.posTypeName;
personalData.posNo = requestBody.posNo ? requestBody.posNo : "";
personalData.orgRootName = requestBody.orgRootName;
personalData.organization = organization;
const before = null;
const personal = await this.personalRepository.save(personalData, {
data: request,
});
setLogDataDiff(request, { before, after: personal });
return new HttpSuccess();
}
/**
* API
*
* @summary
*
*/
@Get("list")
async ListPersonal(@Request() request: RequestWithUser) {
await new permission().PermissionList(request, "SYS_PROBATION");
const lists = await this.personalRepository.find({
order: { createdAt: "DESC" },
});
if (!lists) {
return new HttpError(
HttpStatusCode.INTERNAL_SERVER_ERROR,
"ไม่สามารถแสดงข้อมูลได้"
);
}
let result: any = [];
await Promise.all(
lists.map(async (item, index) => {
const probation_no = await this.assignRepository.count({
where: { personal_id: item.personal_id },
});
await result.push({
personal_id: item.personal_id,
ordering: index + 1,
name: item.prefixName + item.firstName + " " + item.lastName,
position_line: item.positionName,
position_level: item.positionLevelName,
position_type: item.positionTypeName,
organization: item.organization,
probation_no: probation_no,
order_number: item.order_number,
probation_status: item.probation_status,
});
})
);
return new HttpSuccess(result);
}
/**
* API
*
* @summary
*
*/
@Get("")
async GetPersonal(
@Request() request: RequestWithUser,
@Query() personal_id: string
) {
await new permission().PermissionList(request, "SYS_PROBATION");
const person = await this.personalRepository.findOne({
where: { personal_id: personal_id },
});
if (!person) {
return new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบข้อมูลบุคคล");
}
const probation_no = await this.assignRepository.count({
where: { personal_id: person.personal_id },
});
const result = await {
personal_id: person.personal_id,
name: person.prefixName + person.firstName + " " + person.lastName,
position_line: person.positionName,
position_level: person.positionLevelName,
position_type: person.positionTypeName,
organization: person.organization,
probation_no: probation_no,
order_number: person.order_number,
probation_status: person.probation_status,
};
return new HttpSuccess(result);
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,70 @@
import {
Controller,
Route,
Security,
Tags,
Request,
SuccessResponse,
Response,
Get,
Post,
Body,
Query,
} from "tsoa";
import HttpSuccess from "../interfaces/http-success";
import HttpStatusCode from "../interfaces/http-status";
import { RequestWithUser } from "../middlewares/user";
import { setLogDataDiff } from "../interfaces/utils";
import { Survey } from "../entities/Survey";
import { AppDataSource } from "../database/data-source";
@Route("api/v1/survey")
@Tags("Survey")
@Security("bearerAuth")
@Response(
HttpStatusCode.INTERNAL_SERVER_ERROR,
"เกิดข้อผิดพลาด ไม่สามารถแสดงรายการได้ กรุณาลองใหม่ในภายหลัง"
)
@SuccessResponse(HttpStatusCode.OK, "สำเร็จ")
export class SurveyController extends Controller {
private surveyRepository = AppDataSource.getRepository(Survey);
/**
* API
*
* @summary
*
*/
@Get("")
async GetSurvey(
@Query() assign_id: string,
@Request() request: RequestWithUser
) {
const result = await this.surveyRepository.findOne({
where: {
assign_id,
},
});
return new HttpSuccess(result);
}
/**
* API
*
* @summary
*
*/
@Post("")
async PostSurvey(
@Query() assign_id: string,
@Body() requestBody: any,
@Request() request: RequestWithUser
) {
const before = null;
const data = await { ...requestBody, personal_id: request.user.sub };
await this.surveyRepository.save(data, { data: request });
setLogDataDiff(request, { before, after: data });
return new HttpSuccess(data);
}
}

View file

@ -0,0 +1,76 @@
import "dotenv/config";
import "reflect-metadata";
import { DataSource, LogLevel, LogMessage } from "typeorm";
import { Logger } from "typeorm";
import { QueryRunner } from "typeorm/browser";
import { RequestWithUser } from "../middlewares/user";
import { addLogSequence } from "../interfaces/utils";
export class MyCustomLogger implements Logger {
log(
level: "log" | "info" | "warn",
message: any,
queryRunner?: QueryRunner
) {}
logQuery(query: string, parameters?: any[], queryRunner?: QueryRunner): void {
const req = queryRunner?.data as RequestWithUser;
if (req?.app?.locals.logData?.sequence) {
addLogSequence(req, {
action: "database",
status: "success",
description: "Query Data.",
query: [
"Query: " +
query +
(parameters ? " - Parameters:" + JSON.stringify(parameters) : ""),
],
});
}
// const req = queryRunner?.data as RequestWithUser | undefined;
// const logData = req?.app?.locals.logData?.sequence?.at(-1);
// if (logData && !logData.query) logData.query = [];
// if (logData) logData.query.push(
// "Query: " + query + (parameters ? (" - Parameters:" + JSON.stringify(parameters)) : '')
// );
}
logMigration(message: string, queryRunner?: QueryRunner) {}
logQueryError(
error: string | Error,
query: string,
parameters?: any[],
queryRunner?: QueryRunner
) {}
logQuerySlow(
time: number,
query: string,
parameters?: any[],
queryRunner?: QueryRunner
) {}
logSchemaBuild(message: string, queryRunner?: QueryRunner) {}
}
export const AppDataSource = new DataSource({
type: "mysql",
database: process.env.DB_NAME,
host: process.env.DB_HOST,
port: +(process.env.DB_PORT || 3306),
username: process.env.DB_USERNAME,
password: process.env.DB_PASSWORD,
connectorPackage: "mysql2",
synchronize: false,
logging: ["query", "error"],
entities:
process.env.NODE_ENV !== "production"
? ["src/entities/*.ts"]
: ["dist/entities/*{.ts,.js}"],
migrations:
process.env.NODE_ENV !== "production"
? ["src/migration/**/*.ts"]
: ["dist/migration/**/*{.ts,.js}"],
subscribers: [],
logger: new MyCustomLogger(),
});

212
src/entities/Assign.ts Normal file
View file

@ -0,0 +1,212 @@
import {
CreateEvaluateCommander,
EvaluateCommander,
} from "./EvaluateCommander";
import {
Entity,
Column,
OneToMany,
JoinColumn,
PrimaryGeneratedColumn,
ManyToOne,
} from "typeorm";
import { EntityBase } from "./base/Base";
import {
AssignCompetencyGroup,
CreateAssignCompetencyGroup,
} from "./AssignCompetencyGroup";
import { CreateSurvel, Survey } from "./Survey";
import { AssignCompetency, CreateAssignCompetency } from "./AssignCompetency";
import { AssignDirector, CreateAssignDirector } from "./AssignDirector";
import { AssignJob, CreateAssignJob } from "./AssignJob";
import { AssignKnowledge, CreateAssignKnowledge } from "./AssignKnowledge";
import { AssignLaw, CreateAssignLaw } from "./AssignLaw";
import { AssignOutput, CreateAssignOutput } from "./AssignOutput";
import { AssignSkill, CreateAssignSkill } from "./AssignSkill";
import {
CreateEvaluateAchievement,
EvaluateAchievement,
} from "./EvaluateAchievement";
import { CreateEvaluateAssessor, EvaluateAssessor } from "./EvaluateAssessor";
import { CreateEvaluateChairman, EvaluateChairman } from "./EvaluateChairman";
import { CreateEvaluateResult, EvaluateResult } from "./EvaluateResult";
import { Personal } from "./Personal";
@Entity("assign")
export class Assign extends EntityBase {
@PrimaryGeneratedColumn("uuid")
id: string;
@Column({
nullable: false,
type: "uuid",
comment: "Id ของบุคคล",
})
personal_id: string;
@Column({
type: "int",
nullable: false,
comment: "ครั้งที่แบบมอบหมายงาน",
default: 0,
})
round_no!: number;
@Column({
type: "date",
nullable: false,
comment: "วันที่เริ่มทดลองงาน",
})
date_start: Date;
@Column({
type: "date",
nullable: false,
comment: "วันที่สิ้นสุดทดลองงาน",
})
date_finish: Date;
@Column({
type: "longtext",
nullable: true,
comment: "อื่นๆ(3.5)",
default: null,
})
other4_desc!: string;
@Column({
type: "longtext",
nullable: true,
comment: "อื่นๆ(5.2)",
default: null,
})
other5_no1_desc!: string;
@Column({
type: "date",
nullable: true,
comment: "ผู้ทดลองปฏิบัติหน้าที่ราชการลงวันที่",
})
experimenter_dated!: Date;
@Column({
nullable: false,
comment: "สถานะการใช้งาน 1 คือใช้งานปกติ, 0 คือไม่ใช้งาน",
default: 1,
})
active: number;
@ManyToOne(() => Personal, (person: Personal) => person.personal_id)
@JoinColumn({ name: "personal_id" })
profile: Personal;
@OneToMany(
() => AssignCompetencyGroup,
(competencyGroup: AssignCompetencyGroup) => competencyGroup.assign
)
@JoinColumn({ name: "id" })
competencyGroups: AssignCompetencyGroup[];
@OneToMany(
() => AssignCompetency,
(competency: AssignCompetency) => competency.assign
)
@JoinColumn({ name: "id" })
competencys: AssignCompetency[];
@OneToMany(
() => AssignDirector,
(director: AssignDirector) => director.assign
)
@JoinColumn({ name: "id" })
directors: AssignDirector[];
@OneToMany(() => AssignJob, (job: AssignJob) => job.assign)
@JoinColumn({ name: "id" })
jobs: AssignJob[];
@OneToMany(
() => AssignKnowledge,
(knowledge: AssignKnowledge) => knowledge.assign
)
@JoinColumn({ name: "id" })
knowledges: AssignKnowledge[];
@OneToMany(() => AssignLaw, (law: AssignLaw) => law.assign)
@JoinColumn({ name: "id" })
laws: AssignLaw[];
@OneToMany(() => AssignOutput, (output: AssignOutput) => output.assign)
@JoinColumn({ name: "id" })
outputs: AssignOutput[];
@OneToMany(() => AssignSkill, (skill: AssignSkill) => skill.assign)
@JoinColumn({ name: "id" })
skills: AssignSkill[];
@OneToMany(() => Survey, (survey: Survey) => survey.assign)
@JoinColumn({ name: "id" })
surveys: Survey;
@OneToMany(
() => EvaluateAchievement,
(evaluateAchievement: EvaluateAchievement) => evaluateAchievement.assign
)
@JoinColumn({ name: "id" })
evaluateAchievements: EvaluateAchievement;
@OneToMany(
() => EvaluateAssessor,
(evaluateAssessor: EvaluateAssessor) => evaluateAssessor.assign
)
@JoinColumn({ name: "id" })
evaluateAssessors: EvaluateAssessor;
@OneToMany(
() => EvaluateChairman,
(evaluateChairman: EvaluateChairman) => evaluateChairman.assign
)
@JoinColumn({ name: "id" })
evaluateChairmans: EvaluateChairman;
@OneToMany(
() => EvaluateCommander,
(evaluateCommander: EvaluateCommander) => evaluateCommander.assign
)
@JoinColumn({ name: "id" })
evaluateCommanders: EvaluateCommander;
@OneToMany(() => EvaluateResult, (result: EvaluateResult) => result.assign)
@JoinColumn({ name: "id" })
evaluateResults: EvaluateResult;
}
export class CreateAssign {
@Column()
date_start: Date;
@Column()
date_finish: Date;
@Column()
other4_desc?: string;
@Column()
other5_no1_desc?: string;
@Column()
experimenter_dated?: Date;
@Column()
personal_id?: string;
fullname?: string;
position?: string;
monthSelect?: number;
other_desc?: string;
assign_competency_group: CreateAssignCompetencyGroup[];
assign_competency: CreateAssignCompetency[];
assign_director: CreateAssignDirector[];
assign_jobs: CreateAssignJob[];
assign_knowledges: CreateAssignKnowledge[];
assign_law: CreateAssignLaw[];
assign_outputs: CreateAssignOutput[];
assign_skill: CreateAssignSkill[];
}
export type UpdateAssign = Partial<CreateAssign>;

View file

@ -0,0 +1,55 @@
import { Entity, Column, ManyToOne, JoinColumn } from "typeorm";
import { EntityBase } from "./base/Base";
import { Assign } from "./Assign";
@Entity("assignCompetency")
export class AssignCompetency extends EntityBase {
@Column({
primary: true,
type: "uuid",
comment: "Id ของแบบมอบหมายงาน",
})
assign_id: string;
@Column({
primary: true,
type: "uuid",
comment: "Id สมรรถนะหลัก",
})
competency_id: string;
@Column({
nullable: false,
comment: "ระดับพฤติกรรม",
default: 0,
})
competency_level: number;
@Column({
nullable: true,
comment: "ชื่อสมรรถนะหลัก",
default: null,
})
competency_name: string;
@Column({
type: "text",
nullable: true,
comment: "รายละเอียดสมรรถนะหลัก",
default: null,
})
competency_description: string;
@ManyToOne(() => Assign, (assign: Assign) => assign.competencys)
@JoinColumn({ name: "assign_id" })
assign: Assign;
}
export class CreateAssignCompetency {
id: string;
level: number;
name: string;
description: string;
}
export type UpdateAssignCompetency = Partial<CreateAssignCompetency>;

View file

@ -0,0 +1,55 @@
import { Entity, Column, ManyToOne, JoinColumn } from "typeorm";
import { EntityBase } from "./base/Base";
import { Assign } from "./Assign";
@Entity("assignCompetencyGroup")
export class AssignCompetencyGroup extends EntityBase {
@Column({
primary: true,
type: "uuid",
comment: "Id ของแบบมอบหมายงาน",
})
assign_id: string;
@Column({
primary: true,
type: "uuid",
comment: "Id สมรรถนะประจำกลุ่มงาน",
})
competency_group_id: string;
@Column({
nullable: false,
comment: "ระดับพฤติกรรม",
default: 0,
})
competency_group_level: number;
@Column({
nullable: true,
comment: "ชื่อสมรรถนะประจำกลุ่มงาน",
default: null,
})
competency_group_name: string;
@Column({
type: "text",
nullable: true,
comment: "รายละเอียดสมรรถนะประจำกลุ่มงาน",
default: null,
})
competency_group_description: string;
@ManyToOne(() => Assign, (assign: Assign) => assign.competencyGroups)
@JoinColumn({ name: "assign_id" })
assign: Assign;
}
export class CreateAssignCompetencyGroup {
id: string;
level: number;
name: string;
description: string;
}
export type UpdateAssignCompetencyGroup = Partial<CreateAssignCompetencyGroup>;

View file

@ -0,0 +1,91 @@
import { Entity, Column, ManyToOne, JoinColumn } from "typeorm";
import { EntityBase } from "./base/Base";
import { Assign } from "./Assign";
@Entity("assignDirector")
export class AssignDirector extends EntityBase {
@Column({
primary: true,
comment: "Id ของแบบมอบหมายงาน",
})
assign_id: string;
@Column({
primary: true,
comment: "Id ผู้ดูแล/ผู้บังคับบัญชา/ประธาน",
})
personal_id: string;
@Column({
type: "enum",
primary: true,
enum: ["mentor", "commander", "chairman"],
comment:
"หน้าที่ mentor = ผู้ดูแล, commander = ผู้บังคับบัญชา, chairman = ประธานกรรมการ",
})
role: string;
@Column({
nullable: false,
comment: "ชื่อ-นามสกุล",
})
fullname: string;
@Column({
nullable: true,
comment: "ตำแหน่งในสายงาน",
default: null,
})
position!: string;
@Column({
nullable: true,
comment: "ประเภทตำแหน่ง",
default: null,
})
posType!: string;
@Column({
nullable: true,
comment: "ระดับตำแหน่ง",
default: null,
})
posLevel!: string;
@Column({
type: "date",
nullable: true,
comment: "วันที่ลงนาม",
default: null,
})
dated!: Date | null;
@Column({
nullable: true,
comment: "ลำดับการแสดงผล",
default: 0,
})
ordering: number;
@ManyToOne(() => Assign, (assign: Assign) => assign.directors)
@JoinColumn({ name: "assign_id" })
assign: Assign;
}
export class CreateAssignDirector {
@Column()
personal_id: string;
@Column()
position?: string;
@Column()
posType?: string;
@Column()
posLevel?: string;
@Column()
role: string;
@Column()
dated?: Date | null;
name?: string;
}
export type UpdateAssignDirector = Partial<CreateAssignDirector>;

44
src/entities/AssignJob.ts Normal file
View file

@ -0,0 +1,44 @@
import { Entity, Column, ManyToOne, JoinColumn } from "typeorm";
import { EntityBase } from "./base/Base";
import { Assign } from "./Assign";
@Entity("assignJob")
export class AssignJob extends EntityBase {
@Column({
primary: true,
type: "uuid",
comment: "Id ของแบบมอบหมายงาน",
})
assign_id: string;
@Column({
primary: true,
comment: "Running number",
})
id: number;
@Column({
type: "text",
nullable: false,
comment: "ผลการปฏิบัติงานกิจกรรม/ขั้นตอนการปฏิบัติ",
})
activity_desc: string;
@Column({
type: "text",
nullable: false,
comment: "ผลการปฏิบัติงานเป้าหมาย",
})
goal_desc: string;
@ManyToOne(() => Assign, (assign: Assign) => assign.jobs)
@JoinColumn({ name: "assign_id" })
assign: Assign;
}
export class CreateAssignJob {
activity_desc: string;
goal_desc: string;
}
export type UpdateAssignJob = Partial<CreateAssignJob>;

View file

@ -0,0 +1,46 @@
import { Entity, Column, ManyToOne, JoinColumn } from "typeorm";
import { EntityBase } from "./base/Base";
import { Assign } from "./Assign";
import { Knowledge } from "./Knowledge";
@Entity("assignKnowledge")
export class AssignKnowledge extends EntityBase {
@Column({
primary: true,
type: "uuid",
comment: "Id ของแบบมอบหมายงาน",
})
assign_id: string;
@Column({
primary: true,
type: "int",
comment: "Runnig number",
})
knowledge_id: number;
@Column({
nullable: false,
comment: "ระดับพฤติกรรม",
default: 0,
})
knowledge_level: number;
@ManyToOne(() => Assign, (assign: Assign) => assign.knowledges)
@JoinColumn({ name: "assign_id" })
assign: Assign;
@ManyToOne(
() => Knowledge,
(knowledge: Knowledge) => knowledge.assignKnowledge
)
@JoinColumn({ name: "knowledge_id" })
knowledge: Knowledge;
}
export class CreateAssignKnowledge {
id: number;
level: number;
}
export type UpdateAssignKnowledge = Partial<CreateAssignKnowledge>;

45
src/entities/AssignLaw.ts Normal file
View file

@ -0,0 +1,45 @@
import { Entity, Column, ManyToOne, JoinColumn, OneToMany } from "typeorm";
import { EntityBase } from "./base/Base";
import { Assign } from "./Assign";
import { Law } from "./Law";
@Entity("assignLaw")
export class AssignLaw extends EntityBase {
@Column({
primary: true,
type: "uuid",
comment: "Id ของแบบมอบหมายงาน",
})
assign_id: string;
@Column({
primary: true,
type: "int",
comment: "Running number",
})
law_id: number;
@Column({
type: "int",
nullable: false,
comment: "ลำดับ",
default: 0,
})
ordering: number;
@ManyToOne(() => Assign, (assign: Assign) => assign.laws)
@JoinColumn({ name: "assign_id" })
assign: Assign;
@OneToMany(() => Law, (law: Law) => law.assignLaw)
@JoinColumn({ name: "law_id" })
law: Law;
}
export class CreateAssignLaw {
level?: number;
id: number;
checked?: number;
}
export type UpdateAssignLaw = Partial<CreateAssignLaw>;

View file

@ -0,0 +1,46 @@
import { Entity, Column, ManyToOne, JoinColumn } from "typeorm";
import { EntityBase } from "./base/Base";
import { Assign } from "./Assign";
@Entity("assignOutput")
export class AssignOutput extends EntityBase {
@Column({
primary: true,
type: "int",
comment: "Runnig number",
})
id: number;
@Column({
primary: true,
nullable: false,
type: "uuid",
comment: "Id ของแบบมอบหมายงาน",
})
assign_id: string;
@Column({
type: "text",
nullable: false,
comment: "ผลผลิตของงานที่คาดหวัง",
})
output_desc: string;
@Column({
type: "text",
nullable: false,
comment: "ตัวชี้วัดความสําเร็จของงาน",
})
indicator_desc: string;
@ManyToOne(() => Assign, (assign: Assign) => assign.outputs)
@JoinColumn({ name: "assign_id" })
assign: Assign;
}
export class CreateAssignOutput {
output_desc: string;
indicator_desc: string;
}
export type UpdateAssignOutput = Partial<CreateAssignOutput>;

View file

@ -0,0 +1,42 @@
import { Entity, Column, ManyToOne, JoinColumn, OneToMany } from "typeorm";
import { EntityBase } from "./base/Base";
import { Assign } from "./Assign";
import { Skill } from "./Skill";
@Entity("assignSkill")
export class AssignSkill extends EntityBase {
@Column({
primary: true,
type: "uuid",
comment: "Id ของแบบมอบหมายงาน",
})
assign_id: string;
@Column({
primary: true,
type: "int",
comment: "Runnig number",
})
skill_id: number;
@Column({
nullable: false,
comment: "ระดับพฤติกรรม",
default: 0,
})
skill_level: number;
@ManyToOne(() => Assign, (assign: Assign) => assign.skills)
@JoinColumn({ name: "assign_id" })
assign: Assign;
@ManyToOne(() => Skill, (skill: Skill) => skill.assignSkill)
@JoinColumn({ name: "skill_id" })
skill: Skill;
}
export class CreateAssignSkill {
level: number;
}
export type UpdateAssignSkill = Partial<CreateAssignSkill>;

View file

@ -0,0 +1,90 @@
import {
Entity,
Column,
ManyToOne,
JoinColumn,
PrimaryGeneratedColumn,
} from "typeorm";
import { EntityBase } from "./base/Base";
import { Assign } from "./Assign";
@Entity("evaluateAchievement")
export class EvaluateAchievement extends EntityBase {
@PrimaryGeneratedColumn("uuid")
id: string;
@Column({
nullable: false,
comment: "Id ของแบบมอบหมายงาน",
})
assign_id: string;
@Column({
nullable: false,
comment: "Id แบบประเมิน",
})
evaluate_id: string;
@Column({
nullable: false,
comment: "Id ผลลัพท์",
})
output_id: string;
@Column({
nullable: false,
type: "uuid",
comment: "Id ผู้ประเมิน",
})
assessor_id: string;
@Column({
nullable: false,
comment: "ระดับคะแนนผลผลิตของงานที่คาดหวัง",
default: 0,
})
evaluate_expect_level: number;
@Column({
type: "text",
nullable: false,
comment: "คำอธิบายผลผลิตของงานที่เกิดขึ้นจริง",
default: null,
})
output_desc!: string;
@Column({
nullable: false,
comment: "ระดับคะแนนผลผลิตของงานที่เกิดขึ้นจริง",
default: 0,
})
evaluate_output_level!: number;
@ManyToOne(() => Assign, (assign: Assign) => assign.evaluateAchievements)
@JoinColumn({ name: "assign_id" })
assign: Assign;
}
export class CreateEvaluateAchievement {
@Column()
assign_id: string;
@Column()
evaluate_id: string;
@Column()
output_id: string;
@Column()
assessor_id: string;
@Column()
evaluate_expect_level: number;
@Column()
output_desc: string;
@Column()
evaluate_output_level: number;
createdUserId?: string;
createdFullName?: string;
updateUserId?: string;
updateFullName?: string;
}
export type UpdateEvaluateAchievement = Partial<CreateEvaluateAchievement>;

View file

@ -0,0 +1,338 @@
import {
Entity,
Column,
ManyToOne,
JoinColumn,
PrimaryGeneratedColumn,
} from "typeorm";
import { EntityBase } from "./base/Base";
import { Assign } from "./Assign";
@Entity("evaluateAssessor")
export class EvaluateAssessor extends EntityBase {
@PrimaryGeneratedColumn("uuid")
id: string;
@Column({
nullable: false,
type: "uuid",
comment: "Id ของแบบมอบหมายงาน",
})
assign_id: string;
@Column({
nullable: false,
type: "uuid",
comment: "Id ผู้ประเมิน",
})
director_id: string;
@Column({
comment: "ครั้งที่บันทึก",
default: 1,
})
no: string;
@Column({
type: "date",
nullable: false,
comment: "วันที่เริ่มการบันทึก",
})
date_start: Date;
@Column({
type: "date",
nullable: false,
comment: "วันที่สิ้นสุดการบันทึก",
})
date_finish: Date;
@Column({
type: "date",
nullable: true,
comment: "วันที่ลงนาม",
})
assessor_dated: Date | null;
@Column({
nullable: false,
comment: "คะแนนความรู้",
default: 0,
})
knowledge_level: number;
@Column({
nullable: false,
comment: "คะแนนทักษะ",
default: 0,
})
skill_level: number;
@Column({
nullable: false,
comment: "คะแนนสมรรถนะ",
default: 0,
})
competency_level: number;
@Column({
nullable: false,
comment: "คะแนนความสามารถในการเรียนรู้งาน",
default: 0,
})
learn_level: number;
@Column({
nullable: false,
comment: "คะแนนความสามารถในการปรับใช้ความรู้กับงานในหน้าที่",
default: 0,
})
apply_level: number;
@Column({
type: "text",
nullable: true,
comment: "ความสำเร็จของงานที่ได้รับมอบหมายอื่นๆ",
})
achievement_other_desc!: string;
@Column({
nullable: true,
comment: "ระดับความสำเร็จของงานที่ได้รับมอบหมายอื่นๆ",
})
achievement_other_level!: number;
@Column({
type: "text",
nullable: false,
comment: "จุดเด่น",
})
achievement_strength_desc: string;
@Column({
type: "text",
nullable: false,
comment: "สิ่งที่ควรปรับปรุง",
})
achievement_improve_desc: string;
@Column({
nullable: false,
comment: "คะแนนความประพฤติ ให้บริการประชาชนหรือผู้รับบริการด้วยอัธยาศัยดี",
default: 0,
})
conduct1_level: number;
@Column({
nullable: false,
comment: "คะแนนความประพฤติ มีความรับผิดชอบในการปฏิบัติบัติงาน",
default: 0,
})
conduct2_level: number;
@Column({
nullable: false,
comment:
"คะแนนความประพฤติ ให้บริการประชาชนหรือผู้รับบริการด้วยความรวดเร็ว เอาใจใส่เป็นมาตรฐานเดียวกัน",
default: 0,
})
conduct3_level: number;
@Column({
nullable: false,
comment:
"คะแนนความประพฤติ ตั้งใจปฏิบัติบัติหน้าที่ราชการด้วยความอุตสาหะ ขยันหมั่นเพียร",
default: 0,
})
conduct4_level: number;
@Column({
nullable: false,
comment:
"คะแนนความมีจรรยาบรรณ อุทิศตนและเสียสละเวลาในการปฏิบัติบัติงานอย่างเต็มกำลังความสามารถ",
default: 0,
})
moral1_level: number;
@Column({
nullable: false,
comment:
"คะแนนความมีจรรยาบรรณ มีจิตสำนึกที่ดี ปฏิบัติบัติงานด้วยความซื่อสัตย์ สุจริต",
default: 0,
})
moral2_level: number;
@Column({
nullable: false,
comment:
"คะแนนความมีจรรยาบรรณ ยึดมั่นในสถาบันบัพระมหากษัตริย์ และไม่กระทำการใด ๆ อันจะก่อให้เกิดความเสียหายต่อประเทศชาติ",
default: 0,
})
moral3_level: number;
@Column({
nullable: false,
comment: "คะแนนการรักษาวินัย มีความรับรัผิดชอบในการรักษาเวลาทำงาน",
default: 0,
})
discipline1_level: number;
@Column({
nullable: false,
comment:
"คะแนนการรักษาวินัย แต่งกายในการปฏิบัติบัติงานได้อย่างเหมาะสมกับการเป็นข้าราชการ",
default: 0,
})
discipline2_level: number;
@Column({
nullable: false,
comment:
"คะแนนการรักษาวินัย ไม่กระทำการใด ๆ อันอาจก่อให้เกิดความเสียหายแก่ชื่อเสียงของหน่วยงาน",
default: 0,
})
discipline3_level: number;
@Column({
nullable: false,
comment:
"คะแนนการรักษาวินัย ไม่กระทำการใด ๆ อันเป็นการเสื่อมเกียรติและศักดิ์ศรีของความเป็นข้าราชการ",
default: 0,
})
discipline4_level: number;
@Column({
nullable: false,
comment:
"คะแนนการรักษาวินัย ปฏิบัติบัติหน้าที่อย่างตรงไปตรงมาโดยยึดหลักจรรยาบรรณวิชาชีพ",
default: 0,
})
discipline5_level: number;
@Column({
type: "text",
nullable: true,
comment: "ความประพฤติอื่นๆ",
})
behavior_other_desc!: string;
@Column({
nullable: true,
comment: "ระดับความประพฤติอื่นๆ",
})
behavior_other_level!: number;
@Column({
type: "text",
nullable: false,
comment: "จุดเด่นของความประพฤติ",
})
behavior_strength_desc: string;
@Column({
type: "text",
nullable: false,
comment: "สิ่งที่ควรปรับปรุงของความประพฤติ",
})
behavior_improve_desc: string;
@Column({
nullable: false,
comment: "คะแนนการพัฒนาตนเอง การปฐมนิเทศ",
default: 0,
})
orientation: number;
@Column({
nullable: false,
comment: "คะแนนการพัฒนาตนเอง การเรียนรู้ด้วยตนเอง",
default: 0,
})
self_learning: number;
@Column({
nullable: false,
comment: "คะแนนการพัฒนาตนเอง การอบรมสัมนาร่วมกัน",
default: 0,
})
training_seminar: number;
@Column({
nullable: false,
comment: "คะแนนการพัฒนาตนเอง การอบรมอื่น ๆ ตามที่หน่วยงานกำหนด",
default: 0,
})
other_training: number;
@ManyToOne(() => Assign, (assign: Assign) => assign.evaluateAchievements)
@JoinColumn({ name: "assign_id" })
assign: Assign;
}
interface DataLevel {
id: number;
labal: string;
level: number;
}
interface DataOuptut {
id: number;
text: string;
level: number;
}
interface DataOther {
text: string;
level: number | null;
}
export class CreateEvaluateAssessor {
evaluate_no?: number;
@Column()
start_date: Date;
@Column()
date_finish: Date;
evaluate_expenct_level: DataLevel[];
evaluate_ouptut: DataOuptut[];
knowledge_level: number;
skill_level: number;
competency_level: number;
learn_level: number;
apply_level: number;
success_level?: number;
achievement_other: DataOther;
achievement_strength_desc: string;
achievement_improve_desc: string;
conduct1_level: number;
conduct2_level: number;
conduct3_level: number;
conduct4_level: number;
moral1_level: number;
moral2_level: number;
moral3_level: number;
discipline1_level: number;
discipline2_level: number;
discipline3_level: number;
discipline4_level: number;
discipline5_level: number;
behavio_orther: DataOther;
behavior_strength_desc: string;
behavior_improve_desc: string;
orientation: number;
self_learning: number;
training_seminar: number;
other_training: number;
assessor_dated: Date;
createdUserId?: string;
createdFullName?: string;
updateUserId?: string;
updateFullName?: string;
}
export type UpdateEvaluateAssessor = Partial<CreateEvaluateAssessor>;

View file

@ -0,0 +1,415 @@
import {
Entity,
Column,
ManyToOne,
JoinColumn,
PrimaryGeneratedColumn,
} from "typeorm";
import { EntityBase } from "./base/Base";
import { Assign } from "./Assign";
@Entity("evaluateChairman")
export class EvaluateChairman extends EntityBase {
@PrimaryGeneratedColumn("uuid")
id: string;
@Column({
nullable: false,
type: "uuid",
comment: "Id ของแบบมอบหมายงาน",
})
assign_id: string;
@Column({
nullable: false,
type: "uuid",
comment: "Id ผู้ประเมิน",
})
director_id: string;
@Column({
comment: "ครั้งที่ประเมิน",
default: 1,
})
no: string;
@Column({
type: "date",
nullable: false,
comment: "วันที่เริ่มการบันทึก",
})
date_start: Date;
@Column({
type: "date",
nullable: false,
comment: "วันที่สิ้นสุดการบันทึก",
})
date_finish: Date;
@Column({
type: "date",
nullable: true,
comment: "วันที่ลงนาม",
})
chairman_dated: Date | null;
@Column({
nullable: false,
comment: "คะแนนความสามารถในการเรียนรู้งาน",
default: 0,
})
knowledge_level: number;
@Column({
nullable: false,
comment: "คะแนนความสามารถในการปรับใช้ความรู้กับงานในหน้าที่",
default: 0,
})
apply_level: number;
@Column({
nullable: false,
comment: "คะแนนความสำเร็จของงานที่ได้รับมอบหมาย",
default: 0,
})
success_level: number;
@Column({
type: "text",
nullable: true,
comment: "ความสำเร็จของงานที่ได้รับมอบหมายอื่นๆ",
})
achievement_other_desc!: string;
@Column({
nullable: true,
comment: "ระดับความสำเร็จของงานที่ได้รับมอบหมายอื่นๆ",
})
achievement_other_level!: number;
@Column({
nullable: false,
comment: "คะแนนความประพฤติ ให้บริการประชาชนหรือผู้รับบริการด้วยอัธยาศัยดี",
default: 0,
})
conduct1_level: number;
@Column({
nullable: false,
comment: "คะแนนความประพฤติ มีความรับผิดชอบในการปฏิบัติบัติงาน",
default: 0,
})
conduct2_level: number;
@Column({
nullable: false,
comment:
"คะแนนความประพฤติ ให้บริการประชาชนหรือผู้รับบริการด้วยความรวดเร็ว เอาใจใส่เป็นมาตรฐานเดียวกัน",
default: 0,
})
conduct3_level: number;
@Column({
nullable: false,
comment:
"คะแนนความประพฤติ ตั้งใจปฏิบัติบัติหน้าที่ราชการด้วยความอุตสาหะ ขยันหมั่นเพียร",
default: 0,
})
conduct4_level: number;
@Column({
nullable: false,
comment:
"คะแนนความมีจรรยาบรรณ อุทิศตนและเสียสละเวลาในการปฏิบัติบัติงานอย่างเต็มกำลังความสามารถ",
default: 0,
})
moral1_level: number;
@Column({
nullable: false,
comment:
"คะแนนความมีจรรยาบรรณ มีจิตสำนึกที่ดี ปฏิบัติบัติงานด้วยความซื่อสัตย์ สุจริต",
default: 0,
})
moral2_level: number;
@Column({
nullable: false,
comment:
"คะแนนความมีจรรยาบรรณ ยึดมั่นในสถาบันบัพระมหากษัตริย์ และไม่กระทำการใด ๆ อันจะก่อให้เกิดความเสียหายต่อประเทศชาติ",
default: 0,
})
moral3_level: number;
@Column({
nullable: false,
comment: "คะแนนการรักษาวินัย มีความรับรัผิดชอบในการรักษาเวลาทำงาน",
default: 0,
})
discipline1_level: number;
@Column({
nullable: false,
comment:
"คะแนนการรักษาวินัย แต่งกายในการปฏิบัติบัติงานได้อย่างเหมาะสมกับการเป็นข้าราชการ",
default: 0,
})
discipline2_level: number;
@Column({
nullable: false,
comment:
"คะแนนการรักษาวินัย ไม่กระทำการใด ๆ อันอาจก่อให้เกิดความเสียหายแก่ชื่อเสียงของหน่วยงาน",
default: 0,
})
discipline3_level: number;
@Column({
nullable: false,
comment:
"คะแนนการรักษาวินัย ไม่กระทำการใด ๆ อันเป็นการเสื่อมเกียรติและศักดิ์ศรีของความเป็นข้าราชการ",
default: 0,
})
discipline4_level: number;
@Column({
nullable: false,
comment:
"คะแนนการรักษาวินัย ปฏิบัติบัติหน้าที่อย่างตรงไปตรงมาโดยยึดหลักจรรยาบรรณวิชาชีพ",
default: 0,
})
discipline5_level: number;
@Column({
type: "text",
nullable: true,
comment: "ความประพฤติอื่นๆ",
})
behavior_other_desc!: string;
@Column({
nullable: true,
comment: "ระดับความประพฤติอื่นๆ",
})
behavior_other_level!: number;
@Column({
nullable: false,
comment: "คะแนนการพัฒนาตนเอง การปฐมนิเทศ",
default: 0,
})
develop_orientation_score: number;
@Column({
nullable: false,
comment: "คะแนนการพัฒนาตนเอง การเรียนรู้ด้วยตนเอง",
default: 0,
})
develop_self_learning_score: number;
@Column({
nullable: false,
comment: "คะแนนการพัฒนาตนเอง การอบรมสัมนาร่วมกัน",
default: 0,
})
develop_training_seminar_score: number;
@Column({
nullable: false,
comment: "คะแนนการพัฒนาตนเอง การอบรมอื่น ๆ ตามที่หน่วยงานกำหนด",
default: 0,
})
develop_other_training_score: number;
@Column({
type: "float",
comment: "เปอร์เซ็นต์การพัฒนาตนเอง การปฐมนิเทศ",
default: 0,
})
develop_orientation_percent: number;
@Column({
type: "float",
comment: "เปอร์เซ็นต์การพัฒนาตนเอง การเรียนรู้ด้วยตนเอง",
default: 0,
})
develop_self_learning_percent: number;
@Column({
type: "float",
comment: "เปอร์เซ็นต์การพัฒนาตนเอง การอบรมสัมนาร่วมกัน",
default: 0,
})
develop_training_seminar_percent: number;
@Column({
type: "float",
comment: "เปอร์เซ็นต์การพัฒนาตนเอง การอบรมอื่น ๆ ตามที่หน่วยงานกำหนด",
default: 0,
})
develop_other_training_percent: number;
@Column({
nullable: true,
comment: "ผลประเมิน 0 = ไม่ผ่าน, 1 = ผ่าน",
})
develop_result: number;
@Column({
nullable: false,
comment: "คะแนน ผลสัมฤทธิ์ของการทดลองปฏิบัติบัติหน้าที่ราชการ",
default: 0,
})
achievement_score: number;
@Column({
nullable: false,
comment: "คะแนนเต็ม พฤติกรรม",
default: 0,
})
achievement_score_total: number;
@Column({
type: "float",
comment: "เปอร์เซ็นต์ผลสัมฤทธิ์",
default: 0,
})
achievement_percent: number;
@Column({
nullable: true,
comment: "ผลประเมินผลสัมฤทธิ์ 0 = ไม่ผ่าน, 1 = ผ่าน",
})
achievement_result: number;
@Column({
nullable: false,
comment: "คะแนน พฤติกรรม",
default: 0,
})
behavior_score: number;
@Column({
nullable: false,
comment: "ผลคะแนนรวมพฤติกรรม",
default: 0,
})
behavior_score_total: number;
@Column({
type: "float",
comment: "เปอร์เซ็นต์พฤติกรรม",
default: 0,
})
behavior_percent: number;
@Column({
nullable: true,
comment: "ผลประเมินพฤติกรรม 0 = ไม่ผ่าน, 1 = ผ่าน",
})
behavior_result: number;
@Column({
nullable: false,
comment: "ผลคะแนนรวม",
default: 0,
})
sum_score: number;
@Column({
nullable: false,
comment: "เปอร์เซ็นต์ผลรวม",
default: 0,
})
sum_percent: number;
@Column({
nullable: true,
comment: "ผลการประเมิน 0 = ไม่ผ่าน, 1 = ผ่าน",
})
evaluate_result: number;
@Column({
type: "date",
nullable: true,
comment: "วันที่ลงนาม",
})
director1_dated!: Date | null;
@Column({
type: "date",
nullable: true,
comment: "วันที่ลงนาม",
})
director2_dated!: Date | null;
@ManyToOne(() => Assign, (assign: Assign) => assign.evaluateChairmans)
@JoinColumn({ name: "assign_id" })
assign: Assign;
}
interface DataOtherChairman {
text: string;
level: number | null;
}
export class CreateEvaluateChairman {
evaluate_no?: number;
@Column()
start_date: Date;
@Column()
date_finish: Date;
chairman_dated: Date | null;
director1_dated: Date | null;
director2_dated: Date | null;
knowledge_level: number;
apply_level: number;
success_level: number;
achievement_other: DataOtherChairman;
conduct1_level: number;
conduct2_level: number;
conduct3_level: number;
conduct4_level: number;
moral1_level: number;
moral2_level: number;
moral3_level: number;
discipline1_level: number;
discipline2_level: number;
discipline3_level: number;
discipline4_level: number;
discipline5_level: number;
behavior_orther: DataOtherChairman;
develop_orientation_score: number;
develop_self_learning_score: number;
develop_training_seminar_score: number;
develop_other_training_score: number;
develop_orientation_percent: number;
develop_self_learning_percent: number;
develop_training_seminar_percent: number;
develop_other_training_percent: number;
develop_result: number;
achievement_score: number;
achievement_score_total: number;
achievement_percent: number;
achievement_result: number;
behavior_score: number;
behavior_score_total: number;
behavior_percent: number;
behavior_result: number;
sum_score: number;
sum_percent: number;
evaluate_result: number;
createdUserId?: string;
createdFullName?: string;
updateUserId?: string;
updateFullName?: string;
}
export type UpdateEvaluateChairman = Partial<CreateEvaluateChairman>;

View file

@ -0,0 +1,311 @@
import {
Entity,
Column,
ManyToOne,
JoinColumn,
PrimaryGeneratedColumn,
} from "typeorm";
import { EntityBase } from "./base/Base";
import { Assign } from "./Assign";
@Entity("evaluateCommander")
export class EvaluateCommander extends EntityBase {
@PrimaryGeneratedColumn("uuid")
id: string;
@Column({
nullable: false,
type: "uuid",
comment: "Id ของแบบมอบหมายงาน",
})
assign_id: string;
@Column({
nullable: false,
type: "uuid",
comment: "Id ผู้ประเมิน",
})
director_id: string;
@Column({
comment: "ครั้งที่ประเมิน",
default: 1,
})
no: string;
@Column({
type: "date",
nullable: false,
comment: "วันที่เริ่มการบันทึก",
})
date_start: Date;
@Column({
type: "date",
nullable: false,
comment: "วันที่สิ้นสุดการบันทึก",
})
date_finish: Date;
@Column({
type: "date",
nullable: true,
comment: "วันที่ลงนาม",
})
commander_dated: Date | null;
@Column({
nullable: false,
comment: "คะแนนความรู้ความสามารถ",
default: 0,
})
knowledge_level: number;
@Column({
nullable: false,
comment: "คะแนนทักษะ",
default: 0,
})
skill_level: number;
@Column({
nullable: false,
comment: "คะแนนสมรรถนะ",
default: 0,
})
competency_level: number;
@Column({
nullable: false,
comment: "คะแนนความสามารถในการเรียนรู้งาน",
default: 0,
})
learn_level: number;
@Column({
nullable: false,
comment: "คะแนนความสามารถในการปรับใช้ความรู้กับงานในหน้าที่",
default: 0,
})
apply_level: number;
@Column({
nullable: false,
comment: "คะแนนความสำเร็จของงานที่ได้รับมอบหมาย",
default: 0,
})
success_level: number;
@Column({
nullable: true,
comment: "ความสำเร็จของงานที่ได้รับมอบหมายอื่นๆ",
})
achievement_other_desc!: string;
@Column({
nullable: true,
comment: "ระดับความสำเร็จของงานที่ได้รับมอบหมายอื่นๆ",
})
achievement_other_level!: number;
@Column({
nullable: false,
comment: "คะแนนความประพฤติ ให้บริการประชาชนหรือผู้รับบริการด้วยอัธยาศัยดี",
default: 0,
})
conduct1_level: number;
@Column({
nullable: false,
comment: "คะแนนความประพฤติ มีความรับผิดชอบในการปฏิบัติบัติงาน",
default: 0,
})
conduct2_level: number;
@Column({
nullable: false,
comment:
"คะแนนความประพฤติ ให้บริการประชาชนหรือผู้รับบริการด้วยความรวดเร็ว เอาใจใส่เป็นมาตรฐานเดียวกัน",
default: 0,
})
conduct3_level: number;
@Column({
nullable: false,
comment:
"คะแนนความประพฤติ ตั้งใจปฏิบัติบัติหน้าที่ราชการด้วยความอุตสาหะ ขยันหมั่นเพียร",
default: 0,
})
conduct4_level: number;
@Column({
nullable: false,
comment:
"คะแนนความมีจรรยาบรรณ อุทิศตนและเสียสละเวลาในการปฏิบัติบัติงานอย่างเต็มกำลังความสามารถ",
default: 0,
})
moral1_level: number;
@Column({
nullable: false,
comment:
"คะแนนความมีจรรยาบรรณ มีจิตสำนึกที่ดี ปฏิบัติบัติงานด้วยความซื่อสัตย์ สุจริต",
default: 0,
})
moral2_level: number;
@Column({
nullable: false,
comment:
"คะแนนความมีจรรยาบรรณ ยึดมั่นในสถาบันบัพระมหากษัตริย์ และไม่กระทำการใด ๆ อันจะก่อให้เกิดความเสียหายต่อประเทศชาติ",
default: 0,
})
moral3_level: number;
@Column({
nullable: false,
comment: "คะแนนการรักษาวินัย มีความรับรัผิดชอบในการรักษาเวลาทำงาน",
default: 0,
})
discipline1_level: number;
@Column({
nullable: false,
comment:
"คะแนนการรักษาวินัย แต่งกายในการปฏิบัติบัติงานได้อย่างเหมาะสมกับการเป็นข้าราชการ",
default: 0,
})
discipline2_level: number;
@Column({
nullable: false,
comment:
"คะแนนการรักษาวินัย ไม่กระทำการใด ๆ อันอาจก่อให้เกิดความเสียหายแก่ชื่อเสียงของหน่วยงาน",
default: 0,
})
discipline3_level: number;
@Column({
nullable: false,
comment:
"คะแนนการรักษาวินัย ไม่กระทำการใด ๆ อันเป็นการเสื่อมเกียรติและศักดิ์ศรีของความเป็นข้าราชการ",
default: 0,
})
discipline4_level: number;
@Column({
nullable: false,
comment:
"คะแนนการรักษาวินัย ปฏิบัติบัติหน้าที่อย่างตรงไปตรงมาโดยยึดหลักจรรยาบรรณวิชาชีพ",
default: 0,
})
discipline5_level: number;
@Column({
nullable: true,
comment: "ความประพฤติอื่นๆ",
})
behavior_other_desc!: string;
@Column({
nullable: true,
comment: "ระดับความประพฤติอื่นๆ",
})
behavior_other_level!: number;
@Column({
nullable: false,
comment: "จุดเด่นของความประพฤติ",
})
behavior_strength_desc: string;
@Column({
nullable: false,
comment: "สิ่งที่ควรปรับปรุงของความประพฤติ",
})
behavior_improve_desc: string;
@Column({
nullable: false,
comment: "คะแนนการพัฒนาตนเอง การปฐมนิเทศ",
default: 0,
})
orientation: number;
@Column({
nullable: false,
comment: "คะแนนการพัฒนาตนเอง การเรียนรู้ด้วยตนเอง",
default: 0,
})
self_learning: number;
@Column({
nullable: false,
comment: "คะแนนการพัฒนาตนเอง การอบรมสัมนาร่วมกัน",
default: 0,
})
training_seminar: number;
@Column({
nullable: false,
comment: "คะแนนการพัฒนาตนเอง การอบรมอื่น ๆ ตามที่หน่วยงานกำหนด",
default: 0,
})
other_training: number;
@ManyToOne(() => Assign, (assign: Assign) => assign.evaluateCommanders)
@JoinColumn({ name: "assign_id" })
assign: Assign;
}
interface DataOtherCommander {
text: string;
level: number | null;
}
export class CreateEvaluateCommander {
evaluate_no?: number;
@Column()
start_date: Date;
@Column()
date_finish: Date;
commander_dated: Date | null;
knowledge_level: number;
skill_level: number;
competency_level: number;
learn_level: number;
apply_level: number;
success_level: number;
achievement_other: DataOtherCommander;
conduct1_level: number;
conduct2_level: number;
conduct3_level: number;
conduct4_level: number;
moral1_level: number;
moral2_level: number;
moral3_level: number;
discipline1_level: number;
discipline2_level: number;
discipline3_level: number;
discipline4_level: number;
discipline5_level: number;
behavior_orther: DataOtherCommander;
behavior_strength_desc: string;
behavior_improve_desc: string;
orientation: number;
self_learning: number;
training_seminar: number;
other_training: number;
createdUserId?: string;
createdFullName?: string;
updateUserId?: string;
updateFullName?: string;
}
export type UpdateEvaluateCommander = Partial<CreateEvaluateCommander>;

View file

@ -0,0 +1,113 @@
import {
Entity,
Column,
ManyToOne,
JoinColumn,
PrimaryGeneratedColumn,
} from "typeorm";
import { EntityBase } from "./base/Base";
import { Assign } from "./Assign";
@Entity("evaluateResult")
export class EvaluateResult extends EntityBase {
@PrimaryGeneratedColumn("uuid")
id: string;
@Column({
nullable: false,
type: "uuid",
comment: "Id ของแบบมอบหมายงาน",
})
assign_id: string;
@Column({
nullable: false,
type: "uuid",
comment: "Id ผู้ประเมิน",
})
director_id: string;
@Column({
comment: "ครั้งที่ประเมิน",
default: 1,
})
no: string;
@Column({
type: "date",
nullable: false,
comment: "วันที่เริ่มการบันทึก",
})
date_start: Date;
@Column({
type: "date",
nullable: false,
comment: "วันที่สิ้นสุดการบันทึก",
})
date_finish: Date;
@Column({
nullable: true,
comment: "ผลประเมิน 0 = ไม่ผ่าน, 1 = ผ่าน",
})
develop_complete: number;
@Column({
nullable: true,
comment: "ผลการทดลองงาน 0 = ไม่ผ่าน, 1 = ผ่าน",
})
pass_result: number;
@Column({
nullable: true,
comment: "จำนวนเดือนที่ขยาย",
})
expand_month!: number;
@Column({
type: "longtext",
nullable: true,
comment: "เหตุผล",
})
reson!: string;
@Column({
type: "date",
nullable: true,
comment: "วันที่ลงนาม",
})
chairman_dated!: Date | null;
@Column({
type: "date",
nullable: true,
comment: "วันที่ลงนาม",
})
director1_dated!: Date | null;
@Column({
type: "date",
nullable: true,
comment: "วันที่ลงนาม",
})
director2_dated!: Date | null;
@ManyToOne(() => Assign, (assign: Assign) => assign.evaluateResults)
@JoinColumn({ name: "assign_id" })
assign: Assign;
}
export class CreateEvaluateResult {
start_date: Date;
date_finish: Date;
develop_complete: number;
pass_result: number;
expand_month: number;
reson: string;
chairman_dated: Date | null;
director1_dated: Date | null;
director2_dated: Date | null;
}
export type UpdateEvaluateResult = Partial<CreateEvaluateResult>;

85
src/entities/Knowledge.ts Normal file
View file

@ -0,0 +1,85 @@
import { AssignKnowledge } from "./AssignKnowledge";
import {
Entity,
Column,
PrimaryGeneratedColumn,
OneToMany,
JoinColumn,
} from "typeorm";
import { EntityBase } from "./base/Base";
export enum TypeKnowledge {
PERFORMANCE = "performance",
LAW = "law",
}
@Entity("knowledge")
export class Knowledge extends EntityBase {
@PrimaryGeneratedColumn()
id: number;
@Column({
nullable: false,
type: "enum",
enum: TypeKnowledge,
default: TypeKnowledge.PERFORMANCE,
comment:
"หมวดหมู่ความรู้ performance คือความรู้ความสามารถ, law คือความรู้ด้านกฎหมาย",
})
type: TypeKnowledge;
@Column({
nullable: false,
comment: "หัวข้อความรู้",
})
title: string;
@Column({
type: "text",
nullable: true,
comment: "คำอธิบายระดับความรู้ ระดับที่ 1",
})
level1: string;
@Column({
type: "text",
nullable: true,
comment: "คำอธิบายระดับความรู้ ระดับที่ 2",
})
level2: string;
@Column({
type: "text",
nullable: true,
comment: "คำอธิบายระดับความรู้ ระดับที่ 3",
})
level3: string;
@Column({
type: "text",
nullable: true,
comment: "คำอธิบายระดับความรู้ ระดับที่ 4",
})
level4: string;
@Column({
type: "text",
nullable: true,
comment: "คำอธิบายระดับความรู้ ระดับที่ 5",
})
level5: string;
@Column({
nullable: false,
comment: "สถานะการใช้งาน 1 คือใช้งานปกติ, 0 คือไม่ใช้งาน",
default: 1,
})
active: number;
@OneToMany(
() => AssignKnowledge,
(assignKnowledge: AssignKnowledge) => assignKnowledge.knowledge_id
)
@JoinColumn({ name: "id" })
assignKnowledge: AssignKnowledge[];
}

47
src/entities/Law.ts Normal file
View file

@ -0,0 +1,47 @@
import {
Entity,
Column,
PrimaryGeneratedColumn,
OneToMany,
JoinColumn,
} from "typeorm";
import { EntityBase } from "./base/Base";
import { AssignLaw } from "./AssignLaw";
@Entity("laws")
export class Law extends EntityBase {
@PrimaryGeneratedColumn()
id: number;
@Column({
nullable: false,
comment: "เลเวลของ node ข้อมูล",
default: 0,
})
parent_id: number;
@Column({
type: "text",
nullable: true,
comment: "คำอธิบาย",
})
description: string;
@Column({
nullable: false,
comment: "1 คือเลือกได้, 0 คือไม่ให้เลือก",
default: 1,
})
status_select: number;
@Column({
nullable: false,
comment: "คำอธิบายระดับความรู้ ระดับที่ 5",
default: 1,
})
active: number;
@OneToMany(() => AssignLaw, (assignLaw: AssignLaw) => assignLaw.law_id)
@JoinColumn({ name: "id" })
assignLaw: AssignLaw[];
}

View file

@ -0,0 +1,85 @@
import { Entity, Column, PrimaryGeneratedColumn } from "typeorm";
import { EntityBase } from "./base/Base";
export enum TypeKnowledgeSkill {
ACADEMIC = "academic",
GENERAL = "general",
}
@Entity("knowlegeSkillMaps")
export class MapKnowledgeSkill extends EntityBase {
@Column({
primary: true,
comment: "runing number",
})
no: number;
@Column({
primary: true,
comment: "ระดับตำแหน่ง",
})
positionLevelName: string;
@Column({
primary: true,
comment: "ตำแหน่งทางสายงาน",
})
positionName: string;
@Column({
nullable: false,
type: "enum",
enum: TypeKnowledgeSkill,
comment: "academic คือทักษะด้านวิชาการ / general คือทักษะทั่วไป",
})
type: TypeKnowledgeSkill;
@Column({
nullable: false,
comment: "ระดับความสามารถที่คาดหวัง",
default: 1,
})
knowlage_performance_level: number;
@Column({
nullable: false,
comment: "ระดับความรู้ด้านกฎหมายที่คาดหวัง",
default: 1,
})
knowlage_law_level: number;
@Column({
nullable: false,
comment: "ทักษะด้านคอมพิวเตอร์ที่คาดหวัง",
default: 1,
})
skill_computer_level: number;
@Column({
nullable: false,
comment: "ทักษะด้านภาษาอังกฤษที่คาดหวัง",
default: 1,
})
skill_english_level: number;
@Column({
nullable: false,
comment: "ทักษะด้านการจัดการข้อมูลที่คาดหวัง",
default: 1,
})
skill_information_level: number;
@Column({
nullable: false,
comment: "ทักษะด้านการบริหารจัดการทรัพยากรที่คาดหวัง",
default: 1,
})
skill_resourse_level: number;
@Column({
nullable: false,
comment: "สถานะการใช้งาน 1 คือใช้งานปกติ, 0 คือไม่ใช้งาน",
default: 1,
})
active: number;
}

179
src/entities/Personal.ts Normal file
View file

@ -0,0 +1,179 @@
import {
Entity,
Column,
PrimaryGeneratedColumn,
OneToOne,
JoinColumn,
OneToMany,
} from "typeorm";
import { EntityBase } from "./base/Base";
import { Assign } from "./Assign";
@Entity("personal")
export class Personal extends EntityBase {
@PrimaryGeneratedColumn("uuid")
personal_id: string;
@Column({
nullable: true,
comment: "เลขที่คำสั่งบรรจุ",
})
order_number!: string;
@Column({
nullable: false,
comment:
"1 อยู่ระหว่างการทดลองปฏิบัติหน้าที่ราชการ, 2 พ้นการทดลองปฏิบัติหน้าที่ราชการ, 3 ไม่พ้นการทดลองปฏิบัติหน้าที่ราชการ, 4 ยุติการทดลองปฏิบัติหน้าที่ราชการเนื่องจากเปลี่ยนตำแหน่ง, 5 ยุติการทดลองปฏิบัติหน้าที่ราชการเนื่องจากลาออก, 6 ยุติการทดลองปฏิบัติหน้าที่ราชการเนื่องจากถึงแก่กรรม, 7 ขยายระยะเวลาทดลองปฏิบัติหน้าที่ราชการ, 8 ดึงรายชื่อไปออกคำสั่งแล้ว",
default: 1,
})
probation_status: number;
@Column({
nullable: false,
comment: "สถานะการใช้งาน 1 คือใช้งานปกติ, 0 คือไม่ใช้งาน",
default: 1,
})
active: number;
@Column({
nullable: true,
comment: "คำนำหน้าชื่อ",
})
prefixName: string;
@Column({
nullable: false,
comment: "ชื่อ",
})
firstName: string;
@Column({
nullable: false,
comment: "นามสกุล",
})
lastName: string;
@Column({
type: Boolean,
comment:
"สถานะการทดลองงาน 1 คืออยู่ระหว่างการทดลองงาน, 0 คือไม่อยู่ระหว่างการทดลองงาน",
default: 0,
})
isProbation: number;
@Column({
nullable: true,
comment: "ตำแหน่งในสายงาน",
})
positionName: string;
@Column({
nullable: true,
comment: "เลขที่ตำแหน่ง",
})
posNo: string;
@Column({
nullable: true,
comment: "ระดับตำแหน่ง",
})
positionLevelName: string;
@Column({
nullable: true,
comment: "ด้าน/สาขา",
})
positionLineName: string;
@Column({
nullable: true,
comment: "ประเภทตำแหน่ง",
})
positionTypeName: string;
@Column({
nullable: true,
comment: "หน่วยงาน",
})
orgRootName: string;
@Column({
nullable: true,
comment: "สังกัด",
})
organization: string;
@OneToMany(() => Assign, (assign: Assign) => assign.personal_id)
@JoinColumn({ name: "personal_id" })
assign: Assign[];
}
export class CreatePersonal {
@Column()
personal_id: string;
@Column()
order_number: string;
@Column()
probation_status: number;
// @Column()
// profiles: PersonalProfile;
@Column()
createdUserId: string;
@Column()
createdFullName: string;
@Column()
updateUserId: string;
@Column()
updateFullName: string;
@Column()
prefixName: string;
@Column()
firstName: string;
@Column()
lastName: string;
@Column()
isProbation: number;
@Column()
positionName: string;
@Column()
posNo: string;
@Column()
positionLevelName: string;
@Column()
positionTypeName: string;
@Column()
positionLineName: string;
@Column()
orgRootName: string;
@Column()
organization: string;
}
export class PostPersonal {
id: string;
prefix: string;
firstName: string;
lastName: string;
isProbation: boolean;
posTypeName?: string | null;
posLevelName: string | null;
position: string | null;
posLineName?: string | null;
posNo?: string | null;
organization?: string;
orgRootName: string | null;
orgChild1Name?: string | null;
orgChild2Name?: string | null;
orgChild3Name?: string | null;
orgChild4Name?: string | null;
order_number?: string;
rank?: string | null;
idcard?: string;
positionField?: string | null;
positionArea?: string | null;
posExecutiveName?: string | null;
positionExecutiveField?: string | null;
}
export type UpdatePersonal = Partial<CreatePersonal>;

85
src/entities/Skill.ts Normal file
View file

@ -0,0 +1,85 @@
import {
Entity,
Column,
PrimaryGeneratedColumn,
OneToMany,
JoinColumn,
} from "typeorm";
import { EntityBase } from "./base/Base";
import { AssignSkill } from "./AssignSkill";
export enum TypeSkill {
COMPUTER = "computer",
ENG = "english",
INFORMATION = "information",
RESOURSE = "resourse",
}
@Entity("skills")
export class Skill extends EntityBase {
@PrimaryGeneratedColumn()
id: number;
@Column({
nullable: false,
type: "enum",
enum: TypeSkill,
comment: "หมวดหมู่ทักษะ",
})
type: TypeSkill;
@Column({
nullable: false,
comment: "หัวข้อความรู้",
})
title: string;
@Column({
type: "text",
nullable: true,
comment: "คำอธิบายระดับความรู้ ระดับที่ 1",
})
level1: string;
@Column({
type: "text",
nullable: true,
comment: "คำอธิบายระดับความรู้ ระดับที่ 2",
})
level2: string;
@Column({
type: "text",
nullable: true,
comment: "คำอธิบายระดับความรู้ ระดับที่ 3",
})
level3: string;
@Column({
type: "text",
nullable: true,
comment: "คำอธิบายระดับความรู้ ระดับที่ 4",
})
level4: string;
@Column({
type: "text",
nullable: true,
comment: "คำอธิบายระดับความรู้ ระดับที่ 5",
})
level5: string;
@Column({
nullable: false,
comment: "สถานะการใช้งาน 1 คือใช้งานปกติ, 0 คือไม่ใช้งาน",
default: 1,
})
active: number;
@OneToMany(
() => AssignSkill,
(assignSkill: AssignSkill) => assignSkill.skill_id
)
@JoinColumn({ name: "id" })
assignSkill: AssignSkill[];
}

66
src/entities/Survey.ts Normal file
View file

@ -0,0 +1,66 @@
import {
Entity,
Column,
PrimaryGeneratedColumn,
ManyToOne,
JoinColumn,
} from "typeorm";
import { EntityBase } from "./base/Base";
import { Assign } from "./Assign";
@Entity("survey")
export class Survey extends EntityBase {
@Column({
primary: true,
nullable: false,
comment: "Id ของบุคคล",
})
personal_id: string;
@Column({
primary: true,
nullable: false,
comment: "Id ของแบบมอบหมายงาน",
})
assign_id: string;
@Column({
type: "longtext",
nullable: true,
comment: "ความคิดเห็นข้อที่ 1",
})
answer1: string;
@Column({
type: "longtext",
nullable: true,
comment: "ความคิดเห็นข้อที่ 2",
})
answer2: string;
@Column({
type: "longtext",
nullable: true,
comment: "ความคิดเห็นข้อที่ 3",
})
answer3: string;
@ManyToOne(() => Assign, (assign: Assign) => assign.surveys)
@JoinColumn({ name: "assign_id" })
assign: Assign;
}
export class CreateSurvel {
@Column()
personal_id: string;
@Column()
assign_id: string;
@Column()
answer1: string;
@Column()
answer2: string;
@Column()
answer3: string;
}
export type UpdateSurvel = Partial<CreateSurvel>;

47
src/entities/base/Base.ts Normal file
View file

@ -0,0 +1,47 @@
import {
Entity,
Column,
CreateDateColumn,
UpdateDateColumn,
// PrimaryGeneratedColumn,
} from "typeorm";
@Entity()
export class EntityBase {
// @PrimaryGeneratedColumn("uuid")
// id: string;
@CreateDateColumn({ comment: "สร้างข้อมูลเมื่อ" })
createdAt!: Date;
@Column({
comment: "User Id ที่สร้างข้อมูล",
length: 40,
default: "00000000-0000-0000-0000-000000000000",
})
createdUserId!: String;
@UpdateDateColumn({ comment: "แก้ไขข้อมูลล่าสุดเมื่อ" })
updatedAt!: Date;
@Column({
comment: "User Id ที่แก้ไขข้อมูล",
length: 40,
default: "00000000-0000-0000-0000-000000000000",
})
updateUserId!: String;
@Column({
comment: "ชื่อ User ที่สร้างข้อมูล",
length: 200,
default: "System Administrator",
})
createdFullName!: String;
@Column({
comment: "ชื่อ User ที่แก้ไขข้อมูลล่าสุด",
length: 200,
default: "System Administrator",
})
updateFullName!: String;
}

View file

@ -0,0 +1,96 @@
import {
Controller,
Request,
Get,
Post,
Put,
Delete,
Patch,
Route,
Security,
Tags,
Path,
} from "tsoa";
import axios from "axios";
import { addLogSequence } from "./utils";
class CallAPI {
//Get
public async GetData(request: any, @Path() path: any) {
const token = request.headers.authorization;
const url = process.env.API_URL + path;
try {
const response = await axios.get(url, {
headers: {
Authorization: `${token}`,
"Content-Type": "application/json",
api_key: process.env.API_KEY,
},
});
addLogSequence(request, {
action: "request",
status: "success",
description: "connected",
request: {
method: "GET",
url: url,
response: JSON.stringify(response.data.result),
},
});
return response.data.result;
} catch (error) {
addLogSequence(request, {
action: "request",
status: "error",
description: "unconnected",
request: {
method: "GET",
url: url,
response: JSON.stringify(error),
},
});
throw error;
}
}
//Post
public async PostData(request: any, @Path() path: any, sendData: any) {
const token = request.headers.authorization;
const url = process.env.API_URL + path;
try {
const response = await axios.post(url, sendData, {
headers: {
Authorization: `${token}`,
"Content-Type": "application/json",
api_key: process.env.API_KEY,
},
});
addLogSequence(request, {
action: "request",
status: "success",
description: "connected",
request: {
method: "POST",
url: url,
payload: JSON.stringify(sendData),
response: JSON.stringify(response.data.result),
},
});
return response.data.result;
} catch (error) {
addLogSequence(request, {
action: "request",
status: "error",
description: "unconnected",
request: {
method: "POST",
url: url,
payload: JSON.stringify(sendData),
response: JSON.stringify(error),
},
});
throw error;
}
}
}
export default CallAPI;

View file

@ -0,0 +1,91 @@
class Extension {
public static ToThaiMonth(value: number) {
switch (value) {
case 1:
return "มกราคม";
case 2:
return "กุมภาพันธ์";
case 3:
return "มีนาคม";
case 4:
return "เมษายน";
case 5:
return "พฤษภาคม";
case 6:
return "มิถุนายน";
case 7:
return "กรกฎาคม";
case 8:
return "สิงหาคม";
case 9:
return "กันยายน";
case 10:
return "ตุลาคม";
case 11:
return "พฤศจิกายน";
case 12:
return "ธันวาคม";
default:
return "";
}
}
public static ToThaiYear(value: number) {
if (value < 2400) return value + 543;
else return value;
}
public static ToCeYear(value: number) {
if (value >= 2400) return value - 543;
else return value;
}
public static ToThaiNumber(value: string) {
let arabicNumbers = "0123456789";
let thaiNumbers = "๐๑๒๓๔๕๖๗๘๙";
let result = "";
for (let digit of value) {
let index = arabicNumbers.indexOf(digit);
if (index >= 0) {
result += thaiNumbers[index];
} else {
result += digit;
}
}
return result;
}
public static ToThaiFullDate(value: Date) {
let yy = value.getFullYear() < 2400 ? value.getFullYear() + 543 : value.getFullYear();
return (
"วันที่ " +
value.getDate() +
" เดือน " +
Extension.ToThaiMonth(value.getMonth() + 1) +
" พ.ศ. " +
yy
);
}
public static ToThaiFullDate2(value: Date) {
let yy = value.getFullYear() < 2400 ? value.getFullYear() + 543 : value.getFullYear();
return value.getDate() + " " + Extension.ToThaiMonth(value.getMonth() + 1) + " " + yy;
}
public static ToThaiFullDate3(value: Date) {
let yy = value.getFullYear() < 2400 ? value.getFullYear() + 543 : value.getFullYear();
return value.getDate() + " เดือน " + Extension.ToThaiMonth(value.getMonth() + 1) + " พ.ศ. " + yy;
}
public static sumObjectValues(array: any, propertyName: any) {
let sum = 0;
for (let i = 0; i < array.length; i++) {
if (array[i][propertyName] !== undefined) {
sum += array[i][propertyName];
}
}
return sum;
}
}
export default Extension;

View file

@ -0,0 +1,19 @@
import HttpStatus from "./http-status";
class HttpError extends Error {
/**
* HTTP Status Code
*/
status: HttpStatus;
message: string;
constructor(status: HttpStatus, message: string) {
super(message);
this.name = "HttpError";
this.status = status;
this.message = message;
}
}
export default HttpError;

View file

@ -0,0 +1,380 @@
/**
* Hypertext Transfer Protocol (HTTP) response status codes.
* @see {@link https://en.wikipedia.org/wiki/List_of_HTTP_status_codes}
*/
enum HttpStatus {
/**
* The server has received the request headers and the client should proceed to send the request body
* (in the case of a request for which a body needs to be sent; for example, a POST request).
* Sending a large request body to a server after a request has been rejected for inappropriate headers would be inefficient.
* To have a server check the request's headers, a client must send Expect: 100-continue as a header in its initial request
* and receive a 100 Continue status code in response before sending the body. The response 417 Expectation Failed indicates the request should not be continued.
*/
CONTINUE = 100,
/**
* The requester has asked the server to switch protocols and the server has agreed to do so.
*/
SWITCHING_PROTOCOLS = 101,
/**
* A WebDAV request may contain many sub-requests involving file operations, requiring a long time to complete the request.
* This code indicates that the server has received and is processing the request, but no response is available yet.
* This prevents the client from timing out and assuming the request was lost.
*/
PROCESSING = 102,
/**
* Standard response for successful HTTP requests.
* The actual response will depend on the request method used.
* In a GET request, the response will contain an entity corresponding to the requested resource.
* In a POST request, the response will contain an entity describing or containing the result of the action.
*/
OK = 200,
/**
* The request has been fulfilled, resulting in the creation of a new resource.
*/
CREATED = 201,
/**
* The request has been accepted for processing, but the processing has not been completed.
* The request might or might not be eventually acted upon, and may be disallowed when processing occurs.
*/
ACCEPTED = 202,
/**
* SINCE HTTP/1.1
* The server is a transforming proxy that received a 200 OK from its origin,
* but is returning a modified version of the origin's response.
*/
NON_AUTHORITATIVE_INFORMATION = 203,
/**
* The server successfully processed the request and is not returning any content.
*/
NO_CONTENT = 204,
/**
* The server successfully processed the request, but is not returning any content.
* Unlike a 204 response, this response requires that the requester reset the document view.
*/
RESET_CONTENT = 205,
/**
* The server is delivering only part of the resource (byte serving) due to a range header sent by the client.
* The range header is used by HTTP clients to enable resuming of interrupted downloads,
* or split a download into multiple simultaneous streams.
*/
PARTIAL_CONTENT = 206,
/**
* The message body that follows is an XML message and can contain a number of separate response codes,
* depending on how many sub-requests were made.
*/
MULTI_STATUS = 207,
/**
* The members of a DAV binding have already been enumerated in a preceding part of the (multistatus) response,
* and are not being included again.
*/
ALREADY_REPORTED = 208,
/**
* The server has fulfilled a request for the resource,
* and the response is a representation of the result of one or more instance-manipulations applied to the current instance.
*/
IM_USED = 226,
/**
* Indicates multiple options for the resource from which the client may choose (via agent-driven content negotiation).
* For example, this code could be used to present multiple video format options,
* to list files with different filename extensions, or to suggest word-sense disambiguation.
*/
MULTIPLE_CHOICES = 300,
/**
* This and all future requests should be directed to the given URI.
*/
MOVED_PERMANENTLY = 301,
/**
* This is an example of industry practice contradicting the standard.
* The HTTP/1.0 specification (RFC 1945) required the client to perform a temporary redirect
* (the original describing phrase was "Moved Temporarily"), but popular browsers implemented 302
* with the functionality of a 303 See Other. Therefore, HTTP/1.1 added status codes 303 and 307
* to distinguish between the two behaviours. However, some Web applications and frameworks
* use the 302 status code as if it were the 303.
*/
FOUND = 302,
/**
* SINCE HTTP/1.1
* The response to the request can be found under another URI using a GET method.
* When received in response to a POST (or PUT/DELETE), the client should presume that
* the server has received the data and should issue a redirect with a separate GET message.
*/
SEE_OTHER = 303,
/**
* Indicates that the resource has not been modified since the version specified by the request headers If-Modified-Since or If-None-Match.
* In such case, there is no need to retransmit the resource since the client still has a previously-downloaded copy.
*/
NOT_MODIFIED = 304,
/**
* SINCE HTTP/1.1
* The requested resource is available only through a proxy, the address for which is provided in the response.
* Many HTTP clients (such as Mozilla and Internet Explorer) do not correctly handle responses with this status code, primarily for security reasons.
*/
USE_PROXY = 305,
/**
* No longer used. Originally meant "Subsequent requests should use the specified proxy."
*/
SWITCH_PROXY = 306,
/**
* SINCE HTTP/1.1
* In this case, the request should be repeated with another URI; however, future requests should still use the original URI.
* In contrast to how 302 was historically implemented, the request method is not allowed to be changed when reissuing the original request.
* For example, a POST request should be repeated using another POST request.
*/
TEMPORARY_REDIRECT = 307,
/**
* The request and all future requests should be repeated using another URI.
* 307 and 308 parallel the behaviors of 302 and 301, but do not allow the HTTP method to change.
* So, for example, submitting a form to a permanently redirected resource may continue smoothly.
*/
PERMANENT_REDIRECT = 308,
/**
* The server cannot or will not process the request due to an apparent client error
* (e.g., malformed request syntax, too large size, invalid request message framing, or deceptive request routing).
*/
BAD_REQUEST = 400,
/**
* Similar to 403 Forbidden, but specifically for use when authentication is required and has failed or has not yet
* been provided. The response must include a WWW-Authenticate header field containing a challenge applicable to the
* requested resource. See Basic access authentication and Digest access authentication. 401 semantically means
* "unauthenticated",i.e. the user does not have the necessary credentials.
*/
UNAUTHORIZED = 401,
/**
* Reserved for future use. The original intention was that this code might be used as part of some form of digital
* cash or micro payment scheme, but that has not happened, and this code is not usually used.
* Google Developers API uses this status if a particular developer has exceeded the daily limit on requests.
*/
PAYMENT_REQUIRED = 402,
/**
* The request was valid, but the server is refusing action.
* The user might not have the necessary permissions for a resource.
*/
FORBIDDEN = 403,
/**
* The requested resource could not be found but may be available in the future.
* Subsequent requests by the client are permissible.
*/
NOT_FOUND = 404,
/**
* A request method is not supported for the requested resource;
* for example, a GET request on a form that requires data to be presented via POST, or a PUT request on a read-only resource.
*/
METHOD_NOT_ALLOWED = 405,
/**
* The requested resource is capable of generating only content not acceptable according to the Accept headers sent in the request.
*/
NOT_ACCEPTABLE = 406,
/**
* The client must first authenticate itself with the proxy.
*/
PROXY_AUTHENTICATION_REQUIRED = 407,
/**
* The server timed out waiting for the request.
* According to HTTP specifications:
* "The client did not produce a request within the time that the server was prepared to wait. The client MAY repeat the request without modifications at any later time."
*/
REQUEST_TIMEOUT = 408,
/**
* Indicates that the request could not be processed because of conflict in the request,
* such as an edit conflict between multiple simultaneous updates.
*/
CONFLICT = 409,
/**
* Indicates that the resource requested is no longer available and will not be available again.
* This should be used when a resource has been intentionally removed and the resource should be purged.
* Upon receiving a 410 status code, the client should not request the resource in the future.
* Clients such as search engines should remove the resource from their indices.
* Most use cases do not require clients and search engines to purge the resource, and a "404 Not Found" may be used instead.
*/
GONE = 410,
/**
* The request did not specify the length of its content, which is required by the requested resource.
*/
LENGTH_REQUIRED = 411,
/**
* The server does not meet one of the preconditions that the requester put on the request.
*/
PRECONDITION_FAILED = 412,
/**
* The request is larger than the server is willing or able to process. Previously called "Request Entity Too Large".
*/
PAYLOAD_TOO_LARGE = 413,
/**
* The URI provided was too long for the server to process. Often the result of too much data being encoded as a query-string of a GET request,
* in which case it should be converted to a POST request.
* Called "Request-URI Too Long" previously.
*/
URI_TOO_LONG = 414,
/**
* The request entity has a media type which the server or resource does not support.
* For example, the client uploads an image as image/svg+xml, but the server requires that images use a different format.
*/
UNSUPPORTED_MEDIA_TYPE = 415,
/**
* The client has asked for a portion of the file (byte serving), but the server cannot supply that portion.
* For example, if the client asked for a part of the file that lies beyond the end of the file.
* Called "Requested Range Not Satisfiable" previously.
*/
RANGE_NOT_SATISFIABLE = 416,
/**
* The server cannot meet the requirements of the Expect request-header field.
*/
EXPECTATION_FAILED = 417,
/**
* This code was defined in 1998 as one of the traditional IETF April Fools' jokes, in RFC 2324, Hyper Text Coffee Pot Control Protocol,
* and is not expected to be implemented by actual HTTP servers. The RFC specifies this code should be returned by
* teapots requested to brew coffee. This HTTP status is used as an Easter egg in some websites, including Google.com.
*/
I_AM_A_TEAPOT = 418,
/**
* The request was directed at a server that is not able to produce a response (for example because a connection reuse).
*/
MISDIRECTED_REQUEST = 421,
/**
* The request was well-formed but was unable to be followed due to semantic errors.
*/
UNPROCESSABLE_ENTITY = 422,
/**
* The resource that is being accessed is locked.
*/
LOCKED = 423,
/**
* The request failed due to failure of a previous request (e.g., a PROPPATCH).
*/
FAILED_DEPENDENCY = 424,
/**
* The client should switch to a different protocol such as TLS/1.0, given in the Upgrade header field.
*/
UPGRADE_REQUIRED = 426,
/**
* The origin server requires the request to be conditional.
* Intended to prevent "the 'lost update' problem, where a client
* GETs a resource's state, modifies it, and PUTs it back to the server,
* when meanwhile a third party has modified the state on the server, leading to a conflict."
*/
PRECONDITION_REQUIRED = 428,
/**
* The user has sent too many requests in a given amount of time. Intended for use with rate-limiting schemes.
*/
TOO_MANY_REQUESTS = 429,
/**
* The server is unwilling to process the request because either an individual header field,
* or all the header fields collectively, are too large.
*/
REQUEST_HEADER_FIELDS_TOO_LARGE = 431,
/**
* A server operator has received a legal demand to deny access to a resource or to a set of resources
* that includes the requested resource. The code 451 was chosen as a reference to the novel Fahrenheit 451.
*/
UNAVAILABLE_FOR_LEGAL_REASONS = 451,
/**
* A generic error message, given when an unexpected condition was encountered and no more specific message is suitable.
*/
INTERNAL_SERVER_ERROR = 500,
/**
* The server either does not recognize the request method, or it lacks the ability to fulfill the request.
* Usually this implies future availability (e.g., a new feature of a web-service API).
*/
NOT_IMPLEMENTED = 501,
/**
* The server was acting as a gateway or proxy and received an invalid response from the upstream server.
*/
BAD_GATEWAY = 502,
/**
* The server is currently unavailable (because it is overloaded or down for maintenance).
* Generally, this is a temporary state.
*/
SERVICE_UNAVAILABLE = 503,
/**
* The server was acting as a gateway or proxy and did not receive a timely response from the upstream server.
*/
GATEWAY_TIMEOUT = 504,
/**
* The server does not support the HTTP protocol version used in the request
*/
HTTP_VERSION_NOT_SUPPORTED = 505,
/**
* Transparent content negotiation for the request results in a circular reference.
*/
VARIANT_ALSO_NEGOTIATES = 506,
/**
* The server is unable to store the representation needed to complete the request.
*/
INSUFFICIENT_STORAGE = 507,
/**
* The server detected an infinite loop while processing the request.
*/
LOOP_DETECTED = 508,
/**
* Further extensions to the request are required for the server to fulfill it.
*/
NOT_EXTENDED = 510,
/**
* The client needs to authenticate to gain network access.
* Intended for use by intercepting proxies used to control access to the network (e.g., "captive portals" used
* to require agreement to Terms of Service before granting full Internet access via a Wi-Fi hotspot).
*/
NETWORK_AUTHENTICATION_REQUIRED = 511,
}
export default HttpStatus;

View file

@ -0,0 +1,19 @@
import { RequestWithUser } from "../middlewares/user";
import HttpStatus from "./http-status";
import { addLogSequence } from "./utils";
class HttpSuccess {
/**
* HTTP Status Code
*/
status: HttpStatus;
message: string;
data?: any;
constructor(data?: any) {
this.status = HttpStatus.OK;
this.message = "สำเร็จ";
this.data = data;
}
}
export default HttpSuccess;

View file

@ -0,0 +1,227 @@
import axios from "axios";
import { RequestWithUser } from "../middlewares/user";
import CallAPI from "./call-api";
import HttpError from "./http-error";
import HttpStatus from "./http-status";
class CheckAuth {
public async Permission(req: RequestWithUser, system: string, action: string) {
if (
req.headers.hasOwnProperty("api_key") &&
req.headers["api_key"] &&
req.headers["api_key"] == process.env.API_KEY
) {
return null;
}
return await new CallAPI()
.GetData(req, "/org/permission")
.then((x) => {
let permission = false;
let role = x.roles.find((x: any) => x.authSysId == system);
if (!role) throw "ไม่มีสิทธิ์เข้าระบบ";
if (role.attrOwnership == "OWNER") return "OWNER";
if (action.trim().toLocaleUpperCase() == "CREATE") permission = role.attrIsCreate;
if (action.trim().toLocaleUpperCase() == "DELETE") permission = role.attrIsDelete;
if (action.trim().toLocaleUpperCase() == "GET") permission = role.attrIsGet;
if (action.trim().toLocaleUpperCase() == "LIST") permission = role.attrIsList;
if (action.trim().toLocaleUpperCase() == "UPDATE") permission = role.attrIsUpdate;
if (permission == false) throw "ไม่มีสิทธิ์ใช้งานระบบนี้";
return role.attrPrivilege;
})
.catch((x) => {
if (x.status != undefined) {
throw new HttpError(x.status, x.message);
} else {
throw new HttpError(HttpStatus.FORBIDDEN, x);
}
});
}
public async PermissionOrg(req: RequestWithUser, system: string, action: string) {
if (
req.headers.hasOwnProperty("api_key") &&
req.headers["api_key"] &&
req.headers["api_key"] == process.env.API_KEY
) {
return null;
}
return await new CallAPI()
.GetData(req, "/org/permission/org")
.then(async (x) => {
let privilege = null;
if (action.trim().toLocaleUpperCase() == "CREATE")
privilege = await this.PermissionCreate(req, system);
if (action.trim().toLocaleUpperCase() == "DELETE")
privilege = await this.PermissionDelete(req, system);
if (action.trim().toLocaleUpperCase() == "GET")
privilege = await this.PermissionGet(req, system);
if (action.trim().toLocaleUpperCase() == "LIST")
privilege = await this.PermissionList(req, system);
if (action.trim().toLocaleUpperCase() == "UPDATE")
privilege = await this.PermissionUpdate(req, system);
let data: any = {
root: [null],
child1: [null],
child2: [null],
child3: [null],
child4: [null],
};
let node = 4;
if (x.orgChild1Id == null) {
node = 0;
} else if (x.orgChild2Id == null) {
node = 1;
} else if (x.orgChild3Id == null) {
node = 2;
} else if (x.orgChild4Id == null) {
node = 3;
}
if (privilege == "ROOT") {
data = {
root: [x.orgRootId],
child1: null,
child2: null,
child3: null,
child4: null,
};
} else if (privilege == "CHILD") {
data = {
root: node >= 0 ? [x.orgRootId] : null,
child1: node >= 1 ? [x.orgChild1Id] : null,
child2: node >= 2 ? [x.orgChild2Id] : null,
child3: node >= 3 ? [x.orgChild3Id] : null,
child4: node >= 4 ? [x.orgChild4Id] : null,
};
} else if (privilege == "NORMAL") {
data = {
root: [x.orgRootId],
child1: [x.orgChild1Id],
child2: [x.orgChild2Id],
child3: [x.orgChild3Id],
child4: [x.orgChild4Id],
};
} else if (privilege == "SPECIFIC") {
} else if (privilege == "OWNER") {
data = {
root: null,
child1: null,
child2: null,
child3: null,
child4: null,
};
}
return data;
})
.catch((x) => {
if (x.status != undefined) {
throw new HttpError(x.status, x.message);
} else {
throw new HttpError(HttpStatus.FORBIDDEN, x);
}
});
}
public async PermissionOrgByUser(
req: RequestWithUser,
system: string,
action: string,
profileId: string,
) {
if (
req.headers.hasOwnProperty("api_key") &&
req.headers["api_key"] &&
req.headers["api_key"] == process.env.API_KEY
) {
return true;
}
return await new CallAPI()
.GetData(req, `/org/permission/user/${profileId}`)
.then(async (x) => {
let org = {
root: [null],
child1: [null],
child2: [null],
child3: [null],
child4: [null],
};
if (action.trim().toLocaleUpperCase() == "CREATE")
org = await this.PermissionOrgCreate(req, system);
if (action.trim().toLocaleUpperCase() == "DELETE")
org = await this.PermissionOrgDelete(req, system);
if (action.trim().toLocaleUpperCase() == "GET")
org = await this.PermissionOrgGet(req, system);
if (action.trim().toLocaleUpperCase() == "LIST")
org = await this.PermissionOrgList(req, system);
if (action.trim().toLocaleUpperCase() == "UPDATE")
org = await this.PermissionOrgUpdate(req, system);
if (org.root != null) if (x.orgRootId != org.root[0]) throw "ไม่มีสิทธิ์เข้าถึงข้อมูล";
if (org.child1 != null)
if (x.orgChild1Id != org.child1[0]) throw "ไม่มีสิทธิ์เข้าถึงข้อมูล";
if (org.child2 != null)
if (x.orgChild2Id != org.child2[0]) throw "ไม่มีสิทธิ์เข้าถึงข้อมูล";
if (org.child3 != null)
if (x.orgChild3Id != org.child3[0]) throw "ไม่มีสิทธิ์เข้าถึงข้อมูล";
if (org.child4 != null)
if (x.orgChild4Id != org.child4[0]) throw "ไม่มีสิทธิ์เข้าถึงข้อมูล";
return true;
})
.catch((x) => {
if (x.status != undefined) {
throw new HttpError(x.status, x.message);
} else {
throw new HttpError(HttpStatus.FORBIDDEN, x);
}
});
}
public async PermissionCreate(req: RequestWithUser, system: string) {
return await this.Permission(req, system, "CREATE");
}
public async PermissionDelete(req: RequestWithUser, system: string) {
return await this.Permission(req, system, "DELETE");
}
public async PermissionGet(req: RequestWithUser, system: string) {
return await this.Permission(req, system, "GET");
}
public async PermissionList(req: RequestWithUser, system: string) {
return await this.Permission(req, system, "LIST");
}
public async PermissionUpdate(req: RequestWithUser, system: string) {
return await this.Permission(req, system, "UPDATE");
}
public async PermissionOrgCreate(req: RequestWithUser, system: string) {
return await this.PermissionOrg(req, system, "CREATE");
}
public async PermissionOrgDelete(req: RequestWithUser, system: string) {
return await this.PermissionOrg(req, system, "DELETE");
}
public async PermissionOrgGet(req: RequestWithUser, system: string) {
return await this.PermissionOrg(req, system, "GET");
}
public async PermissionOrgList(req: RequestWithUser, system: string) {
return await this.PermissionOrg(req, system, "LIST");
}
public async PermissionOrgUpdate(req: RequestWithUser, system: string) {
return await this.PermissionOrg(req, system, "UPDATE");
}
public async PermissionOrgUserCreate(req: RequestWithUser, system: string, profileId: string) {
return await this.PermissionOrgByUser(req, system, "CREATE", profileId);
}
public async PermissionOrgUserDelete(req: RequestWithUser, system: string, profileId: string) {
return await this.PermissionOrgByUser(req, system, "DELETE", profileId);
}
public async PermissionOrgUserGet(req: RequestWithUser, system: string, profileId: string) {
return await this.PermissionOrgByUser(req, system, "GET", profileId);
}
public async PermissionOrgUserList(req: RequestWithUser, system: string, profileId: string) {
return await this.PermissionOrgByUser(req, system, "LIST", profileId);
}
public async PermissionOrgUserUpdate(req: RequestWithUser, system: string, profileId: string) {
return await this.PermissionOrgByUser(req, system, "UPDATE", profileId);
}
}
export default CheckAuth;

View file

@ -0,0 +1,39 @@
export interface StorageFolder {
/**
* @prop Full path to this folder. It is used as key as there are no files or directories at the same location.
*/
pathname: string;
/**
* @prop Directory / Folder name.
*/
name: string;
createdAt: string | Date;
createdBy: string | Date;
}
export interface StorageFile {
/**
* @prop Full path to this folder. It is used as key as there are no files or directories at the same location.
*/
pathname: string;
fileName: string;
fileSize: number;
fileType: string;
title: string;
description: string;
author: string;
category: string[];
keyword: string[];
metadata: Record<string, unknown>;
path: string;
upload: boolean;
updatedAt: string | Date;
updatedBy: string;
createdAt: string | Date;
createdBy: string;
}

65
src/interfaces/utils.ts Normal file
View file

@ -0,0 +1,65 @@
import { RequestWithUser } from "../middlewares/user";
export type DataDiff = {
before: any;
after: any;
};
export type LogSequence = {
action: string;
status: "success" | "error";
description: string;
query?: any;
request?: {
method?: "GET" | "POST" | "PUT" | "DELETE" | "PATCH";
url?: string;
payload?: string;
response?: string;
};
};
export function setLogDataDiff(req: RequestWithUser, data: DataDiff) {
req.app.locals.logData.dataDiff = {
before: JSON.stringify(data.before),
after: JSON.stringify(data.after),
};
}
export function addLogSequence(req: RequestWithUser, data: LogSequence) {
if (!req?.app?.locals?.logData?.sequence) {
req.app.locals.logData.sequence = [];
}
req.app.locals.logData.sequence =
req.app.locals.logData.sequence.concat(data);
}
export function editLogSequence(
req: RequestWithUser,
index: number,
data: LogSequence
) {
req.app.locals.logData.sequence[index] = data;
}
export function findEndDate(month: number, start_date: Date | string) {
const startDate = new Date(start_date);
startDate.setMonth(startDate.getMonth() + month);
startDate.setDate(startDate.getDate() - 1);
return startDate.toISOString().slice(0, 10);
}
interface PassPerson {
id: string;
name: string;
}
interface PassAssign {
date_start: Date;
date_finish: Date;
}
export type DataPass = {
person: PassPerson;
assign: PassAssign;
};

88
src/middlewares/auth.ts Normal file
View file

@ -0,0 +1,88 @@
import * as express from "express";
import { createDecoder, createVerifier } from "fast-jwt";
import HttpError from "../interfaces/http-error";
import HttpStatus from "../interfaces/http-status";
import { addLogSequence } from "../interfaces/utils";
import { RequestWithUser } from "./user";
if (!process.env.AUTH_PUBLIC_KEY && !process.env.AUTH_REALM_URL) {
throw new Error("Require keycloak AUTH_PUBLIC_KEY or AUTH_REALM_URL.");
}
if (process.env.AUTH_PUBLIC_KEY && process.env.AUTH_REALM_URL && !process.env.AUTH_PREFERRED_MODE) {
throw new Error(
"AUTH_PREFFERRED must be specified if AUTH_PUBLIC_KEY and AUTH_REALM_URL is provided.",
);
}
const jwtVerify = createVerifier({
key: async () => {
return `-----BEGIN PUBLIC KEY-----\n${process.env.AUTH_PUBLIC_KEY}\n-----END PUBLIC KEY-----`;
},
});
const jwtDecode = createDecoder();
export async function expressAuthentication(
request: RequestWithUser,
securityName: string,
_scopes?: string[],
) {
if (process.env.NODE_ENV !== "production" && process.env.AUTH_BYPASS) {
return { preferred_username: "bypassed" };
}
if (securityName !== "bearerAuth") throw new Error("ไม่ทราบวิธีการยืนยันตัวตน");
const token = request.headers["authorization"]?.includes("Bearer ")
? request.headers["authorization"].split(" ")[1]
: request.headers["authorization"];
if (!token) throw new HttpError(HttpStatus.UNAUTHORIZED, "ไม่พบข้อมูลสำหรับยืนยันตัวตน");
let payload: Record<string, any> = {};
switch (process.env.AUTH_PREFERRED_MODE) {
case "online":
payload = await verifyOnline(token);
break;
case "offline":
payload = await verifyOffline(token);
break;
default:
if (process.env.AUTH_REALM_URL) payload = await verifyOnline(token);
if (process.env.AUTH_PUBLIC_KEY) payload = await verifyOffline(token);
break;
}
if (!request.app.locals.logData) {
request.app.locals.logData = {};
}
// addLogSequence(request, {
// action: "database",
// status: "success",
// description: "Query Data.",
// });
request.app.locals.logData.userId = payload.sub;
request.app.locals.logData.userName = payload.name;
request.app.locals.logData.user = payload.preferred_username;
return payload;
}
async function verifyOffline(token: string) {
const payload = await jwtVerify(token).catch((_) => null);
if (!payload) throw new HttpError(HttpStatus.UNAUTHORIZED, "ไม่สามารถยืนยันตัวตนได้");
return payload;
}
async function verifyOnline(token: string) {
const res = await fetch(`${process.env.AUTH_REALM_URL}/protocol/openid-connect/userinfo`, {
headers: { authorization: `Bearer ${token}` },
}).catch((e) => console.error(e));
if (!res) throw new Error("ไม่สามารถเข้าถึงระบบยืนยันตัวตน");
if (!res.ok) throw new HttpError(HttpStatus.UNAUTHORIZED, "ไม่สามารถยืนยันตัวตนได้");
return await jwtDecode(token);
}

36
src/middlewares/error.ts Normal file
View file

@ -0,0 +1,36 @@
import { NextFunction, Request, Response } from "express";
import HttpError from "../interfaces/http-error";
import HttpStatus from "../interfaces/http-status";
import { ValidateError } from "tsoa";
function error(error: Error, _req: Request, res: Response, _next: NextFunction) {
const logData = _req.app.locals.logData.sequence?.at(-1);
if (logData) {
logData.status = "error";
logData.description = error.message;
}
if (error instanceof HttpError) {
return res.status(error.status).json({
status: error.status,
message: error.message,
});
}
if (error instanceof ValidateError) {
return res.status(error.status).json({
status: HttpStatus.UNPROCESSABLE_ENTITY,
message: "Validation error(s).",
detail: error.fields,
});
}
console.error("Exception Caught:" + error);
return res.status(HttpStatus.INTERNAL_SERVER_ERROR).json({
status: HttpStatus.INTERNAL_SERVER_ERROR,
message: error.message,
});
}
export default error;

79
src/middlewares/logs.ts Normal file
View file

@ -0,0 +1,79 @@
import { NextFunction, Request, Response } from "express";
import { Client } from "@elastic/elasticsearch";
if (!process.env.ELASTICSEARCH_INDEX) {
throw new Error("Require ELASTICSEARCH_INDEX to store log.");
}
const ELASTICSEARCH_INDEX = process.env.ELASTICSEARCH_INDEX;
const LOG_LEVEL_MAP: Record<string, number> = {
debug: 4,
info: 3,
warning: 2,
error: 1,
none: 0,
};
const elasticsearch = new Client({
node: `${process.env.ELASTICSEARCH_PROTOCOL}://${process.env.ELASTICSEARCH_HOST}:${process.env.ELASTICSEARCH_PORT}`,
});
async function logMiddleware(req: Request, res: Response, next: NextFunction) {
if (!req.url.startsWith("/api/")) return next();
let data: any;
const originalJson = res.json;
res.json = function (v: any) {
data = v;
return originalJson.call(this, v);
};
const timestamp = new Date().toISOString();
const start = performance.now();
req.app.locals.logData = {};
res.on("finish", () => {
if (!req.url.startsWith("/api/")) return;
const level = LOG_LEVEL_MAP[process.env.LOG_LEVEL ?? "debug"] || 4;
if (level === 1 && res.statusCode < 500) return;
if (level === 2 && res.statusCode < 400) return;
if (level === 3 && res.statusCode < 200) return;
const obj = {
logType:
res.statusCode >= 500
? "error"
: res.statusCode >= 400
? "warning"
: "info",
ip: req.ip,
systemName: "probation",
startTimeStamp: timestamp,
endTimeStamp: new Date().toISOString(),
processTime: performance.now() - start,
host: req.hostname,
method: req.method,
endpoint: req.url,
responseCode: String(res.statusCode === 304 ? 200 : res.statusCode),
responseDescription: data?.message,
input: (level === 4 && JSON.stringify(req.body, null, 2)) || undefined,
output: (level === 4 && JSON.stringify(data, null, 2)) || undefined,
...req.app.locals.logData,
};
elasticsearch.index({
index: ELASTICSEARCH_INDEX,
document: obj,
});
});
return next();
}
export default logMiddleware;

13
src/middlewares/user.ts Normal file
View file

@ -0,0 +1,13 @@
import type { Request } from "express";
export type RequestWithUser = Request & {
user: {
sub: string;
name: string;
given_name: string;
familiy_name: string;
preferred_username: string;
email: string;
role: string[];
};
};

File diff suppressed because one or more lines are too long

266
static/index.html Normal file
View file

@ -0,0 +1,266 @@
<!--
~ Copyright 2016 Red Hat, Inc. and/or its affiliates
~ and other contributors as indicated by the @author tags.
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<html>
<head>
<script src="./keycloak.js"></script>
</head>
<body>
<div>
<button onclick="keycloak.login()">Login</button>
<button onclick="keycloak.login({ action: 'UPDATE_PASSWORD' })">
Update Password
</button>
<button onclick="keycloak.logout()">Logout</button>
<button onclick="keycloak.register()">Register</button>
<button onclick="keycloak.accountManagement()">Account</button>
<button onclick="refreshToken(9999)">Refresh Token</button>
<button onclick="refreshToken(30)">
Refresh Token (if <30s validity)
</button>
<button onclick="loadProfile()">Get Profile</button>
<button onclick="updateProfile()">Update profile</button>
<button onclick="loadUserInfo()">Get User Info</button>
<button onclick="output(keycloak.tokenParsed)">Show Token</button>
<button onclick="output(keycloak.refreshTokenParsed)">
Show Refresh Token
</button>
<button onclick="output(keycloak.idTokenParsed)">Show ID Token</button>
<button onclick="showExpires()">Show Expires</button>
<button onclick="output(keycloak)">Show Details</button>
<button onclick="output(keycloak.createLoginUrl())">
Show Login URL
</button>
<button onclick="output(keycloak.createLogoutUrl())">
Show Logout URL
</button>
<button onclick="output(keycloak.createRegisterUrl())">
Show Register URL
</button>
<button onclick="output(keycloak.createAccountUrl())">
Show Account URL
</button>
</div>
<h2>Result</h2>
<pre
style="
background-color: #ddd;
border: 1px solid #ccc;
padding: 10px;
word-wrap: break-word;
white-space: pre-wrap;
"
id="output"
></pre>
<h2>Events</h2>
<pre
style="
background-color: #ddd;
border: 1px solid #ccc;
padding: 10px;
word-wrap: break-word;
white-space: pre-wrap;
"
id="events"
></pre>
<script>
function loadProfile() {
keycloak
.loadUserProfile()
.success(function (profile) {
output(profile);
})
.error(function () {
output("Failed to load profile");
});
}
function updateProfile() {
var url = keycloak.createAccountUrl().split("?")[0];
var req = new XMLHttpRequest();
req.open("POST", url, true);
req.setRequestHeader("Accept", "application/json");
req.setRequestHeader("Content-Type", "application/json");
req.setRequestHeader("Authorization", "bearer " + keycloak.token);
req.onreadystatechange = function () {
if (req.readyState == 4) {
if (req.status == 200) {
output("Success");
} else {
output("Failed");
}
}
};
req.send(
'{"email":"myemail@foo.bar","firstName":"test","lastName":"bar"}'
);
}
function loadUserInfo() {
keycloak
.loadUserInfo()
.success(function (userInfo) {
output(userInfo);
})
.error(function () {
output("Failed to load user info");
});
}
function refreshToken(minValidity) {
keycloak
.updateToken(minValidity)
.then(function (refreshed) {
if (refreshed) {
output(keycloak.tokenParsed);
} else {
output(
"Token not refreshed, valid for " +
Math.round(
keycloak.tokenParsed.exp +
keycloak.timeSkew -
new Date().getTime() / 1000
) +
" seconds"
);
}
})
.catch(function () {
output("Failed to refresh token");
});
}
function showExpires() {
if (!keycloak.tokenParsed) {
output("Not authenticated");
return;
}
var o =
"Token Expires:\t\t" +
new Date(
(keycloak.tokenParsed.exp + keycloak.timeSkew) * 1000
).toLocaleString() +
"\n";
o +=
"Token Expires in:\t" +
Math.round(
keycloak.tokenParsed.exp +
keycloak.timeSkew -
new Date().getTime() / 1000
) +
" seconds\n";
if (keycloak.refreshTokenParsed) {
o +=
"Refresh Token Expires:\t" +
new Date(
(keycloak.refreshTokenParsed.exp + keycloak.timeSkew) * 1000
).toLocaleString() +
"\n";
o +=
"Refresh Expires in:\t" +
Math.round(
keycloak.refreshTokenParsed.exp +
keycloak.timeSkew -
new Date().getTime() / 1000
) +
" seconds";
}
output(o);
}
function output(data) {
if (typeof data === "object") {
data = JSON.stringify(data, null, " ");
}
document.getElementById("output").innerHTML = data;
}
function event(event) {
var e = document.getElementById("events").innerHTML;
document.getElementById("events").innerHTML =
new Date().toLocaleString() + "\t" + event + "\n" + e;
}
var keycloak = Keycloak();
keycloak.onAuthSuccess = function () {
event("Auth Success");
};
keycloak.onAuthError = function (errorData) {
event("Auth Error: " + JSON.stringify(errorData));
};
keycloak.onAuthRefreshSuccess = function () {
event("Auth Refresh Success");
};
keycloak.onAuthRefreshError = function () {
event("Auth Refresh Error");
};
keycloak.onAuthLogout = function () {
event("Auth Logout");
};
keycloak.onTokenExpired = function () {
event("Access token expired.");
};
keycloak.onActionUpdate = function (status) {
switch (status) {
case "success":
event("Action completed successfully");
break;
case "cancelled":
event("Action cancelled by user");
break;
case "error":
event("Action failed");
break;
}
};
// Flow can be changed to 'implicit' or 'hybrid', but then client must enable implicit flow in admin console too
var initOptions = {
responseMode: "fragment",
flow: "standard",
};
keycloak
.init(initOptions)
.then(function (authenticated) {
output(
"Init Success (" +
(authenticated ? "Authenticated" : "Not Authenticated") +
")"
);
})
.catch(function () {
output("Init Error");
});
</script>
</body>
</html>

2952
static/keycloak.js Normal file

File diff suppressed because it is too large Load diff

9
static/keycloak.json Normal file
View file

@ -0,0 +1,9 @@
{
"realm": "bma-ehr",
"auth-server-url": "https://id.frappet.synology.me",
"ssl-required": "external",
"resource": "bma-ehr",
"public-client": true,
"client_secret": "tF0huP4MylOSMVZEohwPMI8DDttW66A7",
"realm-public-key":"MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvYg0ZJvH6HgNOzyPp7PCvY3bJwD9WdsNn6gZbuvIfqJQZ8iSH1t0p3fgODO/fqwcj9UFeh1bVFOSjuW+JpnPehROqzt81KNl9zLLNXoN4LimReQHaMM3dU7DCbRylgVCouIDvObyjg8G+Cy5lZvFKWym/DPwGVpSdbvDZJ83qxq2dp7GJXS8PhOvA+MB1K009/jW5pBTUwNArLjoFccr+gIYIiOJDg2rYyIF3fDkwyWkuxr6xRt10+BRJytselwy/18kbDuJxVPaapdgTXI6wLzx7HWcDk30n5EvhJEumnIPpRst8gucqNYmB4MH+vsyoxV5WLuO3qmVRzFbtAppRQIDAQAB"
}

18
tsconfig.json Normal file
View file

@ -0,0 +1,18 @@
{
"compilerOptions": {
"incremental": true,
"outDir": "dist",
"lib": ["ESNext", "DOM", "DOM.Iterable"],
"target": "ESNext",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"strictPropertyInitialization": false,
"strict": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"resolveJsonModule": true,
"skipLibCheck": true
}
}

38
tsoa.json Normal file
View file

@ -0,0 +1,38 @@
{
"entryFile": "src/app.ts",
"noImplicitAdditionalProperties": "throw-on-extras",
"controllerPathGlobs": ["src/controllers/*Controller.ts"],
"spec": {
"outputDirectory": "src",
"specVersion": 3,
"spec": {
"info": {
"title": "bma-ehr-probation - API",
"version": "0.0.1",
"description": "ระบบการพัฒนาบุคลากร/การศึกษาต่อ (Development: DV)",
"license": {
"name": "by Frappet",
"url": "https://frappet.com"
}
},
"basePath": "/"
},
"securityDefinitions": {
"bearerAuth": {
"type": "apiKey",
"name": "Authorization",
"description": "Keycloak Bearer Token",
"in": "header"
}
},
"tags": [
{
"name": "Test", "description": "สำหรับทดสอบ"
}
]
},
"routes": {
"routesDir": "src/",
"authenticationModule": "src/middlewares/auth.ts"
}
}

87
workflows/release.yaml Normal file
View file

@ -0,0 +1,87 @@
name: release-test
run-name: release-test ${{ github.actor }}
on:
push:
tags:
- "version-[0-9]+.[0-9]+.[0-9]+"
workflow_dispatch:
env:
REGISTRY: docker.frappet.com
IMAGE_NAME: ehr/bma-ehr-development-service
DEPLOY_HOST: frappet.com
# COMPOSE_PATH: /home/frappet/docker/bma-ehr
COMPOSE_PATH: /home/frappet/docker/bma/bma-ehr-development
jobs:
# act workflow_dispatch -W .github/workflows/release.yaml --input IMAGE_VER=test-v1 -s DOCKER_USER=sorawit -s DOCKER_PASS=P@ssword -s SSH_PASSWORD=P@ssw0rd
release-test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
# skip Set up QEMU because it fail on act and container
# Gen Version try to get version from tag or inut
- name: Set output tags
id: vars
run: echo "tag=${GITHUB_REF#refs/*/}" >> $GITHUB_OUTPUT
- name: Gen Version
id: gen_ver
run: |
if [[ $GITHUB_REF == 'refs/tags/'* ]]; then
IMAGE_VER=${{ steps.vars.outputs.tag }}
else
IMAGE_VER=${{ github.event.inputs.IMAGE_VER }}
fi
if [[ $IMAGE_VER == '' ]]; then
IMAGE_VER='test-vBeta'
fi
echo '::set-output name=image_ver::'$IMAGE_VER
- name: Check Version
run: |
echo $GITHUB_REF
echo ${{ steps.gen_ver.outputs.image_ver }}
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Login in to registry
uses: docker/login-action@v2
with:
registry: ${{env.REGISTRY}}
username: ${{secrets.DOCKER_USER}}
password: ${{secrets.DOCKER_PASS}}
- name: Build and push docker image
uses: docker/build-push-action@v3
with:
context: .
platforms: linux/amd64
push: true
tags: ${{env.REGISTRY}}/${{env.IMAGE_NAME}}:${{ steps.gen_ver.outputs.image_ver }},${{env.REGISTRY}}/${{env.IMAGE_NAME}}:latest
- name: Remote Deployment
uses: appleboy/ssh-action@v0.1.8
with:
host: ${{env.DEPLOY_HOST}}
username: frappet
password: ${{ secrets.SSH_PASSWORD }}
port: 10102
script: |
cd "${{env.COMPOSE_PATH}}"
docker compose pull
docker compose up -d
echo "${{ steps.gen_ver.outputs.image_ver }}"> success
- uses: snow-actions/line-notify@v1.1.0
if: success()
with:
access_token: ${{ secrets.TOKEN_LINE }}
message: |
-Success✅✅✅
Image: ${{env.IMAGE_NAME}}
Version: ${{ steps.gen_ver.outputs.IMAGE_VER }}
By: ${{github.actor}}
- uses: snow-actions/line-notify@v1.1.0
if: failure()
with:
access_token: ${{ secrets.TOKEN_LINE }}
message: |
-Failure❌❌❌
Image: ${{env.IMAGE_NAME}}
Version: ${{ steps.gen_ver.outputs.IMAGE_VER }}
By: ${{github.actor}}