From 0703810fa32872579f14b9f3290a13f041cc0be7 Mon Sep 17 00:00:00 2001 From: waruneeauy Date: Thu, 5 Sep 2024 13:59:43 +0700 Subject: [PATCH] start project --- .dockerignore | 7 + .env.example | 12 + .github/workflows/release.yaml | 86 + .gitignore | 133 + .prettierignore | 18 + Dockerfile | 35 + README.md | 21 + nodemon.json | 6 + package.json | 49 + src/app.ts | 47 + src/controllers/AssignController.ts | 761 +++++ src/controllers/CalculateController.ts | 47 + src/controllers/DataOptionsController.ts | 302 ++ src/controllers/EvaluateChairmanController.ts | 444 +++ src/controllers/EvaluateController.ts | 414 +++ src/controllers/EvaluateRecordController.ts | 1263 +++++++ src/controllers/EvaluateResultController.ts | 444 +++ src/controllers/MyController.ts | 11 + src/controllers/PersonalController.ts | 195 ++ src/controllers/ReportController.ts | 1119 +++++++ src/controllers/SurveyController.ts | 70 + src/database/data-source.ts | 76 + src/entities/Assign.ts | 212 ++ src/entities/AssignCompetency.ts | 55 + src/entities/AssignCompetencyGroup.ts | 55 + src/entities/AssignDirector.ts | 91 + src/entities/AssignJob.ts | 44 + src/entities/AssignKnowledge.ts | 46 + src/entities/AssignLaw.ts | 45 + src/entities/AssignOutput.ts | 46 + src/entities/AssignSkill.ts | 42 + src/entities/EvaluateAchievement.ts | 90 + src/entities/EvaluateAssessor.ts | 338 ++ src/entities/EvaluateChairman.ts | 415 +++ src/entities/EvaluateCommander.ts | 311 ++ src/entities/EvaluateResult.ts | 113 + src/entities/Knowledge.ts | 85 + src/entities/Law.ts | 47 + src/entities/MapKnowledgeSkill.ts | 85 + src/entities/Personal.ts | 179 + src/entities/Skill.ts | 85 + src/entities/Survey.ts | 66 + src/entities/base/Base.ts | 47 + src/interfaces/call-api.ts | 96 + src/interfaces/extension.ts | 91 + src/interfaces/http-error.ts | 19 + src/interfaces/http-status.ts | 380 +++ src/interfaces/http-success.ts | 19 + src/interfaces/permission.ts | 227 ++ src/interfaces/storage-fs.ts | 39 + src/interfaces/utils.ts | 65 + src/middlewares/auth.ts | 88 + src/middlewares/error.ts | 36 + src/middlewares/logs.ts | 79 + src/middlewares/user.ts | 13 + src/migration/1725518260097-start.ts | 86 + static/index.html | 266 ++ static/keycloak.js | 2952 +++++++++++++++++ static/keycloak.json | 9 + tsconfig.json | 18 + tsoa.json | 38 + workflows/release.yaml | 87 + 62 files changed, 12665 insertions(+) create mode 100644 .dockerignore create mode 100644 .env.example create mode 100644 .github/workflows/release.yaml create mode 100644 .gitignore create mode 100644 .prettierignore create mode 100644 Dockerfile create mode 100644 README.md create mode 100644 nodemon.json create mode 100644 package.json create mode 100644 src/app.ts create mode 100644 src/controllers/AssignController.ts create mode 100644 src/controllers/CalculateController.ts create mode 100644 src/controllers/DataOptionsController.ts create mode 100644 src/controllers/EvaluateChairmanController.ts create mode 100644 src/controllers/EvaluateController.ts create mode 100644 src/controllers/EvaluateRecordController.ts create mode 100644 src/controllers/EvaluateResultController.ts create mode 100644 src/controllers/MyController.ts create mode 100644 src/controllers/PersonalController.ts create mode 100644 src/controllers/ReportController.ts create mode 100644 src/controllers/SurveyController.ts create mode 100644 src/database/data-source.ts create mode 100644 src/entities/Assign.ts create mode 100644 src/entities/AssignCompetency.ts create mode 100644 src/entities/AssignCompetencyGroup.ts create mode 100644 src/entities/AssignDirector.ts create mode 100644 src/entities/AssignJob.ts create mode 100644 src/entities/AssignKnowledge.ts create mode 100644 src/entities/AssignLaw.ts create mode 100644 src/entities/AssignOutput.ts create mode 100644 src/entities/AssignSkill.ts create mode 100644 src/entities/EvaluateAchievement.ts create mode 100644 src/entities/EvaluateAssessor.ts create mode 100644 src/entities/EvaluateChairman.ts create mode 100644 src/entities/EvaluateCommander.ts create mode 100644 src/entities/EvaluateResult.ts create mode 100644 src/entities/Knowledge.ts create mode 100644 src/entities/Law.ts create mode 100644 src/entities/MapKnowledgeSkill.ts create mode 100644 src/entities/Personal.ts create mode 100644 src/entities/Skill.ts create mode 100644 src/entities/Survey.ts create mode 100644 src/entities/base/Base.ts create mode 100644 src/interfaces/call-api.ts create mode 100644 src/interfaces/extension.ts create mode 100644 src/interfaces/http-error.ts create mode 100644 src/interfaces/http-status.ts create mode 100644 src/interfaces/http-success.ts create mode 100644 src/interfaces/permission.ts create mode 100644 src/interfaces/storage-fs.ts create mode 100644 src/interfaces/utils.ts create mode 100644 src/middlewares/auth.ts create mode 100644 src/middlewares/error.ts create mode 100644 src/middlewares/logs.ts create mode 100644 src/middlewares/user.ts create mode 100644 src/migration/1725518260097-start.ts create mode 100644 static/index.html create mode 100644 static/keycloak.js create mode 100644 static/keycloak.json create mode 100644 tsconfig.json create mode 100644 tsoa.json create mode 100644 workflows/release.yaml diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..421d949 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,7 @@ +.DS_Store +node_modules +/dist + +.env +.env.* +!.env.example diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..daeaf3a --- /dev/null +++ b/.env.example @@ -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 diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml new file mode 100644 index 0000000..5116050 --- /dev/null +++ b/.github/workflows/release.yaml @@ -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}} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e8ab805 --- /dev/null +++ b/.gitignore @@ -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.* diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..db4f49d --- /dev/null +++ b/.prettierignore @@ -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 diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..4ca0f48 --- /dev/null +++ b/Dockerfile @@ -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" ] \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..2d78d17 --- /dev/null +++ b/README.md @@ -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 diff --git a/nodemon.json b/nodemon.json new file mode 100644 index 0000000..0d5adab --- /dev/null +++ b/nodemon.json @@ -0,0 +1,6 @@ +{ + "exec": "tsoa spec-and-routes && ts-node src/app.ts", + "ext": "ts", + "watch": ["src"], + "ignore": ["src/routes.ts"] +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..67445e7 --- /dev/null +++ b/package.json @@ -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" + } +} diff --git a/src/app.ts b/src/app.ts new file mode 100644 index 0000000..326a5a2 --- /dev/null +++ b/src/app.ts @@ -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(); diff --git a/src/controllers/AssignController.ts b/src/controllers/AssignController.ts new file mode 100644 index 0000000..ed6d539 --- /dev/null +++ b/src/controllers/AssignController.ts @@ -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, + }); + } +} diff --git a/src/controllers/CalculateController.ts b/src/controllers/CalculateController.ts new file mode 100644 index 0000000..281ed85 --- /dev/null +++ b/src/controllers/CalculateController.ts @@ -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 }); + } +} diff --git a/src/controllers/DataOptionsController.ts b/src/controllers/DataOptionsController.ts new file mode 100644 index 0000000..ca801c9 --- /dev/null +++ b/src/controllers/DataOptionsController.ts @@ -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 }); + } +} diff --git a/src/controllers/EvaluateChairmanController.ts b/src/controllers/EvaluateChairmanController.ts new file mode 100644 index 0000000..3ed3108 --- /dev/null +++ b/src/controllers/EvaluateChairmanController.ts @@ -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(); + } +} diff --git a/src/controllers/EvaluateController.ts b/src/controllers/EvaluateController.ts new file mode 100644 index 0000000..9dcf649 --- /dev/null +++ b/src/controllers/EvaluateController.ts @@ -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(); + } +} diff --git a/src/controllers/EvaluateRecordController.ts b/src/controllers/EvaluateRecordController.ts new file mode 100644 index 0000000..1b74269 --- /dev/null +++ b/src/controllers/EvaluateRecordController.ts @@ -0,0 +1,1263 @@ +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 { AssignOutput } from "../entities/AssignOutput"; +import { + CreateEvaluateAssessor, + EvaluateAssessor, +} from "../entities/EvaluateAssessor"; +import { + CreateEvaluateAchievement, + EvaluateAchievement, +} from "../entities/EvaluateAchievement"; +import CallAPI from "../interfaces/call-api"; + +@Route("api/v1/evaluate-record") +@Tags("แบบบันทึกผล") +@Security("bearerAuth") +@Response( + HttpStatusCode.INTERNAL_SERVER_ERROR, + "เกิดข้อผิดพลาด ไม่สามารถแสดงรายการได้ กรุณาลองใหม่ในภายหลัง" +) +@SuccessResponse(HttpStatusCode.OK, "สำเร็จ") +export class EvaluateRecordController extends Controller { + private assignDirectorRepository = + AppDataSource.getRepository(AssignDirector); + private assignRepository = AppDataSource.getRepository(Assign); + private personalRepository = AppDataSource.getRepository(Personal); + private assignOutputRepository = AppDataSource.getRepository(AssignOutput); + private evaluateAssessorRepository = + AppDataSource.getRepository(EvaluateAssessor); + private evaluateAchievementRepository = + AppDataSource.getRepository(EvaluateAchievement); + + /** + * API ดึงข้อมูลตอนกดสร้างแบบบันทึกผล + * + * @summary get ข้อมูลตอนกดสร้างแบบบันทึกผล + * + */ + @Get("create") + async GetCreate( + @Query() assign_id: string, + @Request() request: RequestWithUser + ) { + const directorData = await this.assignDirectorRepository.findOne({ + select: [ + "personal_id", + "dated", + "fullname", + "position", + "posType", + "posLevel", + ], + where: { + assign_id, + role: "mentor", + }, + }); + + if (!directorData) { + return new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบข้อมูล"); + } + + const director = await { + ...directorData, + name: directorData.fullname, + label: `${directorData.fullname} (${directorData.position}, ${directorData.posType}: ${directorData.posLevel})`, + }; + + const director_id = director.personal_id; + + const assign = await this.assignRepository.findOne({ + 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", + "organization", + ], + where: { + personal_id: assign.personal_id, + }, + }); + if (!profileData) { + return new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบข้อมูลบุคคล"); + } + + const profile = await { + id: profileData.personal_id, + name: + profileData.prefixName + + profileData.firstName + + " " + + profileData.lastName, + positionName: profileData.positionName, + positionLevelName: profileData.positionLevelName, + Oc: profileData.organization, + }; + + const output = await this.assignOutputRepository.find({ + select: ["id", "output_desc"], + where: { + assign_id, + }, + order: { id: "ASC" }, + }); + + const evaluate_amount = await this.evaluateAssessorRepository.count({ + where: { + assign_id, + director_id, + }, + }); + + const evaluate_no = await (evaluate_amount + 1); + const start_date = await (evaluate_amount == 0 + ? assign.date_start + : findEndDate(evaluate_amount * 2, assign.date_start)); + + var commanderData = await this.assignDirectorRepository.findOne({ + select: [ + "personal_id", + "dated", + "fullname", + "position", + "posType", + "posLevel", + ], + where: { personal_id: director_id }, + }); + + if (!commanderData) { + return new HttpError( + HttpStatusCode.NOT_FOUND, + "ไม่พบข้อมูลผู้บังคับบัญชา" + ); + } + + const commander = await { + ...commanderData, + name: commanderData.fullname, + label: `${commanderData.fullname} (${commanderData.position}, ${commanderData.posType}: ${commanderData.posLevel})`, + }; + + return new HttpSuccess({ + person: profile, + assign, + assign_output: output, + evaluate_no, + start_date, + end_date: findEndDate(2, start_date), + director: commander, + }); + } + + /** + * API ข้อมูลแบบบันทึกผล(ผู้ดูแล) + * + * @summary ข้อมูลแบบบันทึกผล(ผู้ดูแล) + * + */ + @Get("") + async GetData(@Query() assign_id: string, @Query() evaluate_no?: string) { + const director = await this.assignDirectorRepository.findOne({ + select: ["personal_id"], + where: { + assign_id, + role: "mentor", + }, + }); + if (!director) { + return new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบข้อมูลผู้ดูแล"); + } + + const director_id = director.personal_id; + + let evaluate: any = null; + let evaluateData: any = []; + if (evaluate_no) { + evaluate = await this.evaluateAssessorRepository.findOne({ + where: { + director_id, + assign_id, + no: evaluate_no, + }, + }); + + if (!evaluate) { + return new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบข้อมูลแบบประเมิน"); + } + + const achievementData = await this.evaluateAchievementRepository.find({ + select: [ + "output_desc", + "evaluate_expect_level", + "output_desc", + "evaluate_output_level", + ], + where: { + evaluate_id: evaluate.id, + }, + order: { output_id: "ASC" }, + }); + if (!achievementData) { + return new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบข้อมูล"); + } + + const achievements = await achievementData.map((achievement) => { + return { + ...achievement, + evaluate_expect_desc: achievement.output_desc, + evaluate_output_desc: achievement.output_desc, + }; + }); + + evaluateData = await { + id: evaluate.id, + director_id: evaluate.director_id, + assign_id, + no: evaluate.no, + date_start: evaluate.date_start, + date_finish: evaluate.date_finish, + assessor_dated: evaluate.assessor_dated, + knowledge_level: evaluate.knowledge_level, + skill_level: evaluate.skill_level, + competency_level: evaluate.competency_level, + learn_level: evaluate.learn_level, + apply_level: evaluate.apply_level, + achievement_other_desc: evaluate.achievement_other_desc, + achievement_other_level: evaluate.achievement_other_level, + achievement_strength_desc: evaluate.achievement_strength_desc, + achievement_improve_desc: evaluate.achievement_improve_desc, + conduct1_level: evaluate.conduct1_level, + conduct2_level: evaluate.conduct2_level, + conduct3_level: evaluate.conduct3_level, + conduct4_level: evaluate.conduct4_level, + moral1_level: evaluate.moral1_level, + moral2_level: evaluate.moral2_level, + moral3_level: evaluate.moral3_level, + discipline1_level: evaluate.discipline1_level, + discipline2_level: evaluate.discipline2_level, + discipline3_level: evaluate.discipline3_level, + discipline4_level: evaluate.discipline4_level, + discipline5_level: evaluate.discipline5_level, + behavior_other_desc: evaluate.behavior_other_desc, + behavior_other_level: evaluate.behavior_other_level, + behavior_strength_desc: evaluate.behavior_strength_desc, + behavior_improve_desc: evaluate.behavior_improve_desc, + orientation: evaluate.orientation, + self_learning: evaluate.self_learning, + training_seminar: evaluate.training_seminar, + other_training: evaluate.other_training, + createdAt: evaluate.createdAt, + updatedAt: evaluate.updatedAt, + achievements: achievements, + }; + } else { + evaluate = await this.evaluateAssessorRepository.find({ + where: { + director_id: director_id, + assign_id, + }, + order: { no: "ASC" }, + }); + + if (!evaluate) { + return new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบข้อมูล"); + } + + await Promise.all( + evaluate.map(async (element: any, index: number) => { + const achievementData = await this.evaluateAchievementRepository.find( + { + select: [ + "output_desc", + "evaluate_expect_level", + "output_desc", + "evaluate_output_level", + ], + where: { + evaluate_id: element.id, + }, + order: { output_id: "ASC" }, + } + ); + + const achievements = await achievementData.map((achievement) => { + return { + ...achievement, + evaluate_expect_desc: achievement.output_desc, + evaluate_output_desc: achievement.output_desc, + }; + }); + + evaluateData[index] = await { + id: element.id, + director_id: element.director_id, + assign_id, + no: element.no, + date_start: element.date_start, + date_finish: element.date_finish, + assessor_dated: element.assessor_dated, + knowledge_level: element.knowledge_level, + skill_level: element.skill_level, + competency_level: element.competency_level, + learn_level: element.learn_level, + apply_level: element.apply_level, + achievement_other_desc: element.achievement_other_desc, + achievement_other_level: element.achievement_other_level, + achievement_strength_desc: element.achievement_strength_desc, + achievement_improve_desc: element.achievement_improve_desc, + conduct1_level: element.conduct1_level, + conduct2_level: element.conduct2_level, + conduct3_level: element.conduct3_level, + conduct4_level: element.conduct4_level, + moral1_level: element.moral1_level, + moral2_level: element.moral2_level, + moral3_level: element.moral3_level, + discipline1_level: element.discipline1_level, + discipline2_level: element.discipline2_level, + discipline3_level: element.discipline3_level, + discipline4_level: element.discipline4_level, + discipline5_level: element.discipline5_level, + behavior_other_desc: element.behavior_other_desc, + behavior_other_level: element.behavior_other_level, + behavior_strength_desc: element.behavior_strength_desc, + behavior_improve_desc: element.behavior_improve_desc, + orientation: element.orientation, + self_learning: element.self_learning, + training_seminar: element.training_seminar, + other_training: element.other_training, + createdAt: element.createdAt, + updatedAt: element.updatedAt, + achievements: achievements, + }; + }) + ); + } + + 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({ + 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})`, + })); + + return new HttpSuccess({ + experimentee: experimentee ? experimentee : null, + director: directors ? directors : null, + assign, + evaluate: evaluateData, + }); + } + + /** + * API เพิ่มแบบบันทึกผล(ผู้ดูแล) + * + * @summary เพิ่มแบบบันทึกผล(ผู้ดูแล) + * + */ + @Post("") + async PostData( + @Query() assign_id: string, + @Body() requestBody: CreateEvaluateAssessor, + @Request() request: RequestWithUser + ) { + const director = await this.assignDirectorRepository.findOne({ + select: ["personal_id"], + where: { + assign_id, + role: "mentor", + }, + }); + 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.behavio_orther.text, + behavior_other_level: + requestBody.behavio_orther.text != "" + ? Number(requestBody.behavio_orther.level) + : 0, + behavior_strength_desc: requestBody.behavior_strength_desc, + behavior_improve_desc: requestBody.behavior_improve_desc, + orientation: requestBody.orientation, + self_learning: requestBody.self_learning, + training_seminar: requestBody.training_seminar, + other_training: requestBody.other_training, + createdUserId: request.user.sub, + createdFullName: request.user.name, + updateUserId: request.user.sub, + updateFullName: request.user.name, + }; + + const evaluateAssessor = await this.evaluateAssessorRepository.save( + postData, + { data: request } + ); + + if (evaluateAssessor) { + await Promise.all( + requestBody.evaluate_expenct_level.map(async (expenct, index) => { + const evaluateAchievement: CreateEvaluateAchievement = await { + evaluate_id: evaluateAssessor.id, + assign_id, + output_id: expenct.id.toString(), + + assessor_id: director_id, + evaluate_expect_level: Number(expenct.level), + output_desc: requestBody.evaluate_ouptut[index].text, + evaluate_output_level: Number( + requestBody.evaluate_ouptut[index].level + ), + createdUserId: request.user.sub, + createdFullName: request.user.name, + updateUserId: request.user.sub, + updateFullName: request.user.name, + }; + await this.evaluateAchievementRepository.save(evaluateAchievement, { + data: request, + }); + }) + ); + } + + setLogDataDiff(request, { before: null, after: postData }); + + if (Number(requestBody.evaluate_no) < 3) { + // #noted cronjob + // แจ้งผู้ดูแลและผู้บังคับบัญชาเข้ามาบันทึกผลทุก 2 เดือน 3 ครั้ง + const dateSaveForm = await findEndDate(2, 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: dateSaveForm, + }) + .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: CreateEvaluateAssessor, + @Request() request: RequestWithUser + ) { + const director = await this.assignDirectorRepository.findOne({ + select: ["personal_id"], + where: { + assign_id, + role: "mentor", + }, + }); + if (!director) { + return new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบข้อมูลผู้ดูแล"); + } + + const director_id = director.personal_id; + + let evaluate = await this.evaluateAssessorRepository.findOne({ + where: { id: evaluate_id }, + }); + + const before = evaluate; + + if (!evaluate) { + return new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบข้อมูลการประเมิน"); + } + + evaluate.assessor_dated = requestBody.assessor_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.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.achievement_strength_desc = requestBody.achievement_strength_desc; + evaluate.achievement_improve_desc = requestBody.achievement_improve_desc; + 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.behavio_orther.text; + evaluate.behavior_other_level = + requestBody.behavio_orther.text != "" + ? Number(requestBody.behavio_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; + + const evaluateAssessor = await this.evaluateAssessorRepository.save( + evaluate, + { data: request } + ); + + if (evaluateAssessor) { + await this.evaluateAchievementRepository.delete({ + evaluate_id, + }); + await Promise.all( + requestBody.evaluate_expenct_level.map(async (expenct, index) => { + const evaluateAchievement: CreateEvaluateAchievement = await { + evaluate_id: evaluateAssessor.id, + assign_id, + output_id: expenct.id.toString(), + assessor_id: director_id, + evaluate_expect_level: Number(expenct.level), + output_desc: requestBody.evaluate_ouptut[index].text, + evaluate_output_level: Number( + requestBody.evaluate_ouptut[index].level + ), + createdUserId: request.user.sub, + createdFullName: request.user.name, + updateUserId: request.user.sub, + updateFullName: request.user.name, + }; + await this.evaluateAchievementRepository.save(evaluateAchievement, { + data: request, + }); + }) + ); + } + + setLogDataDiff(request, { before, after: evaluate }); + + return new HttpSuccess(); + } + + /** + * API ดึงข้อมูลตอนกดสร้างแบบบันทึกผล (ผู้บังคัญบัญชา) + * + * @summary get ข้อมูลตอนกดสร้างแบบบันทึกผล (ผู้บังคัญบัญชา) + * + */ + @Get("create/commander") + async GetCreateCommander( + @Query() assign_id: string, + @Request() request: RequestWithUser + ) { + const directorData = await this.assignDirectorRepository.findOne({ + select: [ + "personal_id", + "dated", + "fullname", + "position", + "posType", + "posLevel", + ], + where: { + assign_id, + role: "commander", + }, + }); + + if (!directorData) { + return new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบข้อมูล"); + } + + const director = await { + ...directorData, + name: directorData.fullname, + label: `${directorData.fullname} (${directorData.position}, ${directorData.posType}: ${directorData.posLevel})`, + }; + + const director_id = director.personal_id; + + const assign = await this.assignRepository.findOne({ + 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", + "organization", + ], + where: { + personal_id: assign.personal_id, + }, + }); + if (!profileData) { + return new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบข้อมูลบุคคล"); + } + + const profile = await { + id: profileData.personal_id, + name: + profileData.prefixName + + profileData.firstName + + " " + + profileData.lastName, + positionName: profileData.positionName, + positionLevelName: profileData.positionLevelName, + Oc: profileData.organization, + }; + + const output = await this.assignOutputRepository.find({ + select: ["id", "output_desc"], + where: { + assign_id, + }, + order: { id: "ASC" }, + }); + + const evaluate_amount = await this.evaluateAssessorRepository.count({ + where: { + assign_id, + director_id, + }, + }); + + const evaluate_no = await (evaluate_amount + 1); + const start_date = await (evaluate_amount == 0 + ? assign.date_start + : findEndDate(evaluate_amount * 2, assign.date_start)); + + return new HttpSuccess({ + person: profile, + assign, + assign_output: output, + evaluate_no, + start_date, + end_date: findEndDate(2, start_date), + director, + }); + } + + /** + * API ข้อมูลแบบบันทึกผล (ผู้บังคัญบัญชา) + * + * @summary ข้อมูลแบบบันทึกผล (ผู้บังคัญบัญชา) + * + */ + @Get("commander") + async GetDataCommander( + @Query() assign_id: string, + @Query() evaluate_no?: string + ) { + 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; + let evaluateData: any = []; + if (evaluate_no) { + evaluate = await this.evaluateAssessorRepository.findOne({ + where: { + director_id, + assign_id, + no: evaluate_no, + }, + }); + + if (!evaluate) { + return new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบข้อมูลแบบประเมิน"); + } + + const achievementData = await this.evaluateAchievementRepository.find({ + select: [ + "output_desc", + "evaluate_expect_level", + "output_desc", + "evaluate_output_level", + ], + where: { + evaluate_id: evaluate.id, + }, + order: { output_id: "ASC" }, + }); + if (!achievementData) { + return new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบข้อมูล"); + } + + const achievements = await achievementData.map((achievement) => { + return { + ...achievement, + evaluate_expect_desc: achievement.output_desc, + evaluate_output_desc: achievement.output_desc, + }; + }); + + evaluateData = await { + id: evaluate.id, + director_id: evaluate.director_id, + assign_id, + no: evaluate.no, + date_start: evaluate.date_start, + date_finish: evaluate.date_finish, + assessor_dated: evaluate.assessor_dated, + knowledge_level: evaluate.knowledge_level, + skill_level: evaluate.skill_level, + competency_level: evaluate.competency_level, + learn_level: evaluate.learn_level, + apply_level: evaluate.apply_level, + achievement_other_desc: evaluate.achievement_other_desc, + achievement_other_level: evaluate.achievement_other_level, + achievement_strength_desc: evaluate.achievement_strength_desc, + achievement_improve_desc: evaluate.achievement_improve_desc, + conduct1_level: evaluate.conduct1_level, + conduct2_level: evaluate.conduct2_level, + conduct3_level: evaluate.conduct3_level, + conduct4_level: evaluate.conduct4_level, + moral1_level: evaluate.moral1_level, + moral2_level: evaluate.moral2_level, + moral3_level: evaluate.moral3_level, + discipline1_level: evaluate.discipline1_level, + discipline2_level: evaluate.discipline2_level, + discipline3_level: evaluate.discipline3_level, + discipline4_level: evaluate.discipline4_level, + discipline5_level: evaluate.discipline5_level, + behavior_other_desc: evaluate.behavior_other_desc, + behavior_other_level: evaluate.behavior_other_level, + behavior_strength_desc: evaluate.behavior_strength_desc, + behavior_improve_desc: evaluate.behavior_improve_desc, + orientation: evaluate.orientation, + self_learning: evaluate.self_learning, + training_seminar: evaluate.training_seminar, + other_training: evaluate.other_training, + createdAt: evaluate.createdAt, + updatedAt: evaluate.updatedAt, + achievements: achievements, + }; + } else { + evaluate = await this.evaluateAssessorRepository.find({ + where: { + director_id: director_id, + assign_id, + }, + order: { no: "ASC" }, + }); + + if (!evaluate) { + return new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบข้อมูล"); + } + + await Promise.all( + evaluate.map(async (element: any, index: number) => { + const achievementData = await this.evaluateAchievementRepository.find( + { + select: [ + "output_desc", + "evaluate_expect_level", + "output_desc", + "evaluate_output_level", + ], + where: { + evaluate_id: element.id, + }, + order: { output_id: "ASC" }, + } + ); + + const achievements = await achievementData.map((achievement) => { + return { + ...achievement, + evaluate_expect_desc: achievement.output_desc, + evaluate_output_desc: achievement.output_desc, + }; + }); + + evaluateData[index] = await { + id: element.id, + director_id: element.director_id, + assign_id, + no: element.no, + date_start: element.date_start, + date_finish: element.date_finish, + assessor_dated: element.assessor_dated, + knowledge_level: element.knowledge_level, + skill_level: element.skill_level, + competency_level: element.competency_level, + learn_level: element.learn_level, + apply_level: element.apply_level, + achievement_other_desc: element.achievement_other_desc, + achievement_other_level: element.achievement_other_level, + achievement_strength_desc: element.achievement_strength_desc, + achievement_improve_desc: element.achievement_improve_desc, + conduct1_level: element.conduct1_level, + conduct2_level: element.conduct2_level, + conduct3_level: element.conduct3_level, + conduct4_level: element.conduct4_level, + moral1_level: element.moral1_level, + moral2_level: element.moral2_level, + moral3_level: element.moral3_level, + discipline1_level: element.discipline1_level, + discipline2_level: element.discipline2_level, + discipline3_level: element.discipline3_level, + discipline4_level: element.discipline4_level, + discipline5_level: element.discipline5_level, + behavior_other_desc: element.behavior_other_desc, + behavior_other_level: element.behavior_other_level, + behavior_strength_desc: element.behavior_strength_desc, + behavior_improve_desc: element.behavior_improve_desc, + orientation: element.orientation, + self_learning: element.self_learning, + training_seminar: element.training_seminar, + other_training: element.other_training, + createdAt: element.createdAt, + updatedAt: element.updatedAt, + achievements: achievements, + }; + }) + ); + } + + 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({ + 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})`, + })); + + return new HttpSuccess({ + experimentee: experimentee ? experimentee : null, + person: directors ? directors : null, + assign, + evaluate: evaluateData, + }); + } + + /** + * API บันทึกข้อมูลแบบบันทึกผล (ผู้บังคัญบัญชา) + * + * @summary บันทึกข้อมูลแบบบันทึกผล (ผู้บังคัญบัญชา) + * + */ + @Post("commander") + async PostDataCommander( + @Query() assign_id: string, + @Body() requestBody: CreateEvaluateAssessor, + @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.behavio_orther.text, + behavior_other_level: + requestBody.behavio_orther.text != "" + ? Number(requestBody.behavio_orther.level) + : 0, + behavior_strength_desc: requestBody.behavior_strength_desc, + behavior_improve_desc: requestBody.behavior_improve_desc, + orientation: requestBody.orientation, + self_learning: requestBody.self_learning, + training_seminar: requestBody.training_seminar, + other_training: requestBody.other_training, + createdUserId: request.user.sub, + createdFullName: request.user.name, + updateUserId: request.user.sub, + updateFullName: request.user.name, + }; + + const evaluateAssessor = await this.evaluateAssessorRepository.save( + postData, + { data: request } + ); + if (evaluateAssessor) { + await Promise.all( + requestBody.evaluate_expenct_level.map(async (expenct, index) => { + const evaluateAchievement: CreateEvaluateAchievement = await { + evaluate_id: evaluateAssessor.id, + assign_id, + output_id: expenct.id.toString(), + assessor_id: director_id, + evaluate_expect_level: Number(expenct.level), + output_desc: requestBody.evaluate_ouptut[index].text, + evaluate_output_level: Number( + requestBody.evaluate_ouptut[index].level + ), + createdUserId: request.user.sub, + createdFullName: request.user.name, + updateUserId: request.user.sub, + updateFullName: request.user.name, + }; + await this.evaluateAchievementRepository.save(evaluateAchievement, { + data: request, + }); + }) + ); + } + + setLogDataDiff(request, { before: null, after: postData }); + + if (Number(requestBody.evaluate_no) < 3) { + // #noted cronjob + // แจ้งผู้ดูแลและผู้บังคับบัญชาเข้ามาบันทึกผลทุก 2 เดือน 3 ครั้ง + const dateSaveForm = await findEndDate(2, 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: dateSaveForm, + }) + .catch((error) => { + console.error("Error calling API:", error); + }); + } + + return new HttpSuccess(); + } + + /** + * API แก้ไขข้อมูลแบบบันทึกผล (ผู้บังคัญบัญชา) + * @summary แก้ไขข้อมูลแบบบันทึกผล (ผู้บังคัญบัญชา) + * + */ + @Put("commander") + async UpdateDataCommander( + @Query() assign_id: string, + @Query() evaluate_id: string, + @Body() requestBody: CreateEvaluateAssessor, + @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; + + let evaluate = await this.evaluateAssessorRepository.findOne({ + where: { id: evaluate_id }, + }); + + if (!evaluate) { + return new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบข้อมูลการประเมิน"); + } + const before = evaluate; + + evaluate.assessor_dated = requestBody.assessor_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.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.achievement_strength_desc = requestBody.achievement_strength_desc; + evaluate.achievement_improve_desc = requestBody.achievement_improve_desc; + 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.behavio_orther.text; + evaluate.behavior_other_level = + requestBody.behavio_orther.text != "" + ? Number(requestBody.behavio_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; + + const evaluateAssessor = await this.evaluateAssessorRepository.save( + evaluate, + { data: request } + ); + + if (evaluateAssessor) { + await this.evaluateAchievementRepository.delete({ + evaluate_id, + }); + await Promise.all( + requestBody.evaluate_expenct_level.map(async (expenct, index) => { + const evaluateAchievement: CreateEvaluateAchievement = await { + evaluate_id: evaluateAssessor.id, + assign_id, + output_id: expenct.id.toString(), + assessor_id: director_id, + evaluate_expect_level: Number(expenct.level), + output_desc: requestBody.evaluate_ouptut[index].text, + evaluate_output_level: Number( + requestBody.evaluate_ouptut[index].level + ), + createdUserId: request.user.sub, + createdFullName: request.user.name, + updateUserId: request.user.sub, + updateFullName: request.user.name, + }; + await this.evaluateAchievementRepository.save(evaluateAchievement, { + data: request, + }); + }) + ); + } + + setLogDataDiff(request, { before, after: evaluate }); + + return new HttpSuccess(); + } +} diff --git a/src/controllers/EvaluateResultController.ts b/src/controllers/EvaluateResultController.ts new file mode 100644 index 0000000..d705581 --- /dev/null +++ b/src/controllers/EvaluateResultController.ts @@ -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(); + } +} diff --git a/src/controllers/MyController.ts b/src/controllers/MyController.ts new file mode 100644 index 0000000..7f14906 --- /dev/null +++ b/src/controllers/MyController.ts @@ -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" }; + } +} diff --git a/src/controllers/PersonalController.ts b/src/controllers/PersonalController.ts new file mode 100644 index 0000000..a47e561 --- /dev/null +++ b/src/controllers/PersonalController.ts @@ -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); + } +} diff --git a/src/controllers/ReportController.ts b/src/controllers/ReportController.ts new file mode 100644 index 0000000..c81ee29 --- /dev/null +++ b/src/controllers/ReportController.ts @@ -0,0 +1,1119 @@ +import { + Controller, + Route, + Security, + Tags, + Body, + Request, + SuccessResponse, + Response, + Get, + Query, + Put, +} 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 { DataPass, setLogDataDiff } from "../interfaces/utils"; +import { Personal } from "../entities/Personal"; +import { Assign } from "../entities/Assign"; +import { EvaluateChairman } from "../entities/EvaluateChairman"; +import { EvaluateResult } from "../entities/EvaluateResult"; +import { EvaluateAssessor } from "../entities/EvaluateAssessor"; +import { AssignDirector } from "../entities/AssignDirector"; +import { EvaluateAchievement } from "../entities/EvaluateAchievement"; +import { EvaluateCommander } from "../entities/EvaluateCommander"; +import CallAPI from "../interfaces/call-api"; + +@Route("api/v1/report") +@Tags("Report") +@Security("bearerAuth") +@Response( + HttpStatusCode.INTERNAL_SERVER_ERROR, + "เกิดข้อผิดพลาด ไม่สามารถแสดงรายการได้ กรุณาลองใหม่ในภายหลัง" +) +@SuccessResponse(HttpStatusCode.OK, "สำเร็จ") +export class ReportController extends Controller { + private evaluateChairmanRepository = + AppDataSource.getRepository(EvaluateChairman); + private evaluateResultRepository = + AppDataSource.getRepository(EvaluateResult); + private personalRepository = AppDataSource.getRepository(Personal); + private evaluateAssessorRepository = + AppDataSource.getRepository(EvaluateAssessor); + private assignDirectorRepository = + AppDataSource.getRepository(AssignDirector); + private evaluateAchievementRepository = + AppDataSource.getRepository(EvaluateAchievement); + private evaluateCommanderRepository = + AppDataSource.getRepository(EvaluateCommander); + + /** + * API สำหรับออกรายงาน + * + * @summary ผลการทดลองปฏิบัติราชการ + * + */ + @Get("") + async GetReport( + @Query() assign_id: string, + @Request() request: RequestWithUser + ) { + const evaluate = await this.evaluateChairmanRepository.findOne({ + where: { assign_id }, + }); + + const result = await this.evaluateResultRepository.findOne({ + where: { assign_id }, + }); + + if (!evaluate || !result) { + return new HttpError( + HttpStatusCode.NOT_FOUND, + "ไม่พบข้อมูลผลการทดลองปฏิบัติราชการ" + ); + } + + const develop_total_score = await (Number( + evaluate.develop_orientation_score + ) + + Number(evaluate.develop_self_learning_score) + + Number(evaluate.develop_training_seminar_score) + + Number(evaluate.develop_other_training_score)); + const develop_total_percent = await (Number( + evaluate.develop_orientation_percent + ) + + Number(evaluate.develop_orientation_percent) + + Number(evaluate.develop_self_learning_percent) + + Number(evaluate.develop_training_seminar_percent)); + + const data = await { + develop_orientation_score: evaluate.develop_orientation_score, + develop_self_learning_score: evaluate.develop_self_learning_score, + develop_training_seminar_score: evaluate.develop_training_seminar_score, + develop_other_training_score: evaluate.develop_other_training_score, + develop_total_score, + develop_orientation_percent: evaluate.develop_orientation_percent, + develop_self_learning_percent: evaluate.develop_self_learning_percent, + develop_training_seminar_percent: + evaluate.develop_training_seminar_percent, + develop_other_training_percent: evaluate.develop_other_training_percent, + develop_total_percent, + develop_result: evaluate.develop_result, + achievement_score: evaluate.achievement_score, + achievement_score_total: evaluate.achievement_score_total, + achievement_percent: evaluate.achievement_percent, + achievement_result: evaluate.achievement_result, + behavior_score: evaluate.behavior_score, + behavior_score_total: evaluate.behavior_score_total, + behavior_percent: evaluate.behavior_percent, + behavior_result: evaluate.behavior_result, + sum_score: evaluate.sum_score, + sum_percent: evaluate.sum_percent, + reason: result.reson, + pass_result: result.pass_result, + evaluate_date: result.chairman_dated, + }; + + return new HttpSuccess(data); + } + + /** + * API แสดงรายการผู้ผ่านทดลองงาน + * + * @summary รายการผู้ผ่านทดลองงาน + * + */ + @Get("pass") + async GetPass(@Request() request: RequestWithUser) { + const lists = await this.personalRepository.find({ + where: { probation_status: 2 }, + }); + + let data: DataPass[] = []; + await Promise.all( + lists.map(async (list) => { + const assign = await AppDataSource.getRepository(Assign).findOne({ + select: ["date_start", "date_finish"], + where: { personal_id: list.personal_id }, + }); + + if (assign) + data.push({ + person: { + id: list.personal_id, + name: `${list.prefixName}${list.firstName} ${list.lastName}`, + }, + assign, + }); + }) + ); + + return new HttpSuccess(data); + } + + /** + * API แสดงรายการผู้ไม่ผ่านทดลองงาน + * + * @summary รายการคนไม่ผ่านการทดลองปฏิบัติราชการและรอไปออกคำสั่ง + * + */ + @Get("not-pass") + async GetDataNotPass(@Request() request: RequestWithUser) { + const lists = await this.personalRepository.find({ + where: { probation_status: 3 }, + }); + + let data: DataPass[] = []; + await Promise.all( + lists.map(async (list) => { + const assign = await AppDataSource.getRepository(Assign).findOne({ + select: ["date_start", "date_finish"], + where: { personal_id: list.personal_id }, + }); + + if (assign) + data.push({ + person: { + id: list.personal_id, + name: `${list.prefixName}${list.firstName} ${list.lastName}`, + }, + assign, + }); + }) + ); + + return new HttpSuccess(data); + } + + /** + * API แสดงรายการคนที่ถูกขยายระยะเวลาทดลองงาน + * + * @summary รายการคนที่ถูกขยายระยะเวลาทดลองปฏิบัติหน้าที่ราชการและรอไปออกคำสั่ง + * + */ + @Get("expand") + async GetDataExpand(@Request() request: RequestWithUser) { + const lists = await this.personalRepository.find({ + select: ["personal_id"], + where: { probation_status: 7 }, + }); + + return new HttpSuccess(lists); + } + + /** + * API สำหรับปรับสถานะการดึงไปออกคำสั่ง + * + * @summary ปรับสถานะการดึงไปออกคำสั่ง + * + */ + @Put("status") + async UpdateStatus( + @Query() personal_id: string, + @Body() requestBody: { command_no: string }, + @Request() request: RequestWithUser + ) { + const personal = await this.personalRepository.findOne({ + where: { personal_id }, + }); + const before = personal; + + if (!personal) { + return new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบข้อมูลบุคคล"); + } + + personal.order_number = requestBody.command_no; + personal.probation_status = 8; + + this.personalRepository.save(personal, { data: request }); + setLogDataDiff(request, { before, after: personal }); + + const resultText = + (await personal.probation_status) === 2 + ? "ไม่ต่ำกว่ามาตรฐานที่กำหนด เห็นควรให้รับราชการต่อ" + : "ต่ำกว่ามาตรฐานที่กำหนด เห็นควรให้ออกจากราชการ"; + + await new CallAPI() + .PostData(request, "/placement/noti", { + subject: "ผลการประเมินการทดลองปฏิบัติหน้าที่ราชการ", + body: `ผลการประเมินการทดลองปฏิบัติหน้าที่ราชการ ${resultText}`, + receiverUserId: personal_id, + payload: "", + isSendMail: false, + isSendInbox: true, + isSendNotification: true, + }) + .catch((error) => { + console.error("Error calling API:", error); + }); + + return new HttpSuccess(); + } + + /** + * API สำหรับปรับสถานะการดึงไปออกคำสั่ง + * + * @summary ปรับสถานะการดึงไปออกคำสั่ง + * + */ + @Put("change-status") + async ChangeStatus( + @Query() personal_id: string, + @Body() reqBody: { status: number }, + @Request() request: RequestWithUser + ) { + const person = await this.personalRepository.findOne({ + where: { personal_id }, + }); + const before = person; + + if (!person) { + return new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบข้อมูลบุคคล"); + } + + person.probation_status = await reqBody.status; + await this.personalRepository.save(person, { data: request }); + setLogDataDiff(request, { before, after: person }); + + return new HttpSuccess(); + } + + /** + * API แสดงข้อมูลแบบบันทึกผลตาม id + * + * @summary ข้อมูลแบบบันทึกผลตาม id + * + */ + @Get("form-record") + async GetDataFormRecord( + @Query() id: string, + @Request() request: RequestWithUser + ) { + const evaluate = await this.evaluateAssessorRepository.findOne({ + where: { id }, + }); + + if (!evaluate) { + return new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบข้อมูล"); + } + + const directorData = await this.assignDirectorRepository.findOne({ + select: [ + "personal_id", + "fullname", + "position", + "posType", + "posLevel", + "role", + "dated", + ], + where: { personal_id: evaluate.director_id }, + }); + + if (!directorData) { + return new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบข้อมูล"); + } + + const director = await { + ...directorData, + name: directorData.fullname, + position: directorData.position, + }; + + const achievements = await this.evaluateAchievementRepository.find({ + select: ["evaluate_expect_level", "evaluate_output_level"], + where: { evaluate_id: evaluate.id }, + }); + + if (!achievements) { + return new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบข้อมูล"); + } + + let achievement: any = []; + await Promise.all( + achievements.map(async (element) => { + const evaluate_expect_level = await { + col1: element.evaluate_expect_level == 1 ? "/" : "", + col2: element.evaluate_expect_level == 2 ? "/" : "", + col3: element.evaluate_expect_level == 3 ? "/" : "", + col4: element.evaluate_expect_level == 4 ? "/" : "", + col5: element.evaluate_expect_level == 5 ? "/" : "", + }; + + const evaluate_output_level = await { + col1: element.evaluate_output_level == 1 ? "/" : "", + col2: element.evaluate_output_level == 2 ? "/" : "", + col3: element.evaluate_output_level == 3 ? "/" : "", + col4: element.evaluate_output_level == 4 ? "/" : "", + col5: element.evaluate_output_level == 5 ? "/" : "", + }; + + achievement.push({ + ...element, + evaluate_expect_level, + evaluate_output_level, + }); + }) + ); + + const evaluateData = await { + id: evaluate.id, + no: evaluate.no, + date_start: evaluate.date_start, + date_finish: evaluate.date_finish, + sign_dated: evaluate.assessor_dated, + knowledge_level: { + col1: evaluate.knowledge_level == 1 ? "/" : "", + col2: evaluate.knowledge_level == 2 ? "/" : "", + col3: evaluate.knowledge_level == 3 ? "/" : "", + col4: evaluate.knowledge_level == 4 ? "/" : "", + col5: evaluate.knowledge_level == 5 ? "/" : "", + }, + skill_level: { + col1: evaluate.skill_level == 1 ? "/" : "", + col2: evaluate.skill_level == 2 ? "/" : "", + col3: evaluate.skill_level == 3 ? "/" : "", + col4: evaluate.skill_level == 4 ? "/" : "", + col5: evaluate.skill_level == 5 ? "/" : "", + }, + competency_level: { + col1: evaluate.competency_level == 1 ? "/" : "", + col2: evaluate.competency_level == 2 ? "/" : "", + col3: evaluate.competency_level == 3 ? "/" : "", + col4: evaluate.competency_level == 4 ? "/" : "", + col5: evaluate.competency_level == 5 ? "/" : "", + }, + learn_level: { + col1: evaluate.learn_level == 1 ? "/" : "", + col2: evaluate.learn_level == 2 ? "/" : "", + col3: evaluate.learn_level == 3 ? "/" : "", + col4: evaluate.learn_level == 4 ? "/" : "", + col5: evaluate.learn_level == 5 ? "/" : "", + }, + apply_level: { + col1: evaluate.apply_level == 1 ? "/" : "", + col2: evaluate.apply_level == 2 ? "/" : "", + col3: evaluate.apply_level == 3 ? "/" : "", + col4: evaluate.apply_level == 4 ? "/" : "", + col5: evaluate.apply_level == 5 ? "/" : "", + }, + achievement_other_desc: evaluate.achievement_other_desc, + achievement_other_level: + evaluate.achievement_other_level != null + ? { + col1: evaluate.achievement_other_level == 1 ? "/" : "", + col2: evaluate.achievement_other_level == 2 ? "/" : "", + col3: evaluate.achievement_other_level == 3 ? "/" : "", + col4: evaluate.achievement_other_level == 4 ? "/" : "", + col5: evaluate.achievement_other_level == 5 ? "/" : "", + } + : { + col1: "", + col2: "", + col3: "", + col4: "", + col5: "", + }, + achievement_strength_desc: evaluate.achievement_strength_desc, + achievement_improve_desc: evaluate.achievement_improve_desc, + conduct1_level: { + col1: evaluate.conduct1_level == 1 ? "/" : "", + col2: evaluate.conduct1_level == 2 ? "/" : "", + col3: evaluate.conduct1_level == 3 ? "/" : "", + col4: evaluate.conduct1_level == 4 ? "/" : "", + col5: evaluate.conduct1_level == 5 ? "/" : "", + }, + conduct2_level: { + col1: evaluate.conduct2_level == 1 ? "/" : "", + col2: evaluate.conduct2_level == 2 ? "/" : "", + col3: evaluate.conduct2_level == 3 ? "/" : "", + col4: evaluate.conduct2_level == 4 ? "/" : "", + col5: evaluate.conduct2_level == 5 ? "/" : "", + }, + conduct3_level: { + col1: evaluate.conduct3_level == 1 ? "/" : "", + col2: evaluate.conduct3_level == 2 ? "/" : "", + col3: evaluate.conduct3_level == 3 ? "/" : "", + col4: evaluate.conduct3_level == 4 ? "/" : "", + col5: evaluate.conduct3_level == 5 ? "/" : "", + }, + conduct4_level: { + col1: evaluate.conduct4_level == 1 ? "/" : "", + col2: evaluate.conduct4_level == 2 ? "/" : "", + col3: evaluate.conduct4_level == 3 ? "/" : "", + col4: evaluate.conduct4_level == 4 ? "/" : "", + col5: evaluate.conduct4_level == 5 ? "/" : "", + }, + moral1_level: { + col1: evaluate.moral1_level == 1 ? "/" : "", + col2: evaluate.moral1_level == 2 ? "/" : "", + col3: evaluate.moral1_level == 3 ? "/" : "", + col4: evaluate.moral1_level == 4 ? "/" : "", + col5: evaluate.moral1_level == 5 ? "/" : "", + }, + moral2_level: { + col1: evaluate.moral2_level == 1 ? "/" : "", + col2: evaluate.moral2_level == 2 ? "/" : "", + col3: evaluate.moral2_level == 3 ? "/" : "", + col4: evaluate.moral2_level == 4 ? "/" : "", + col5: evaluate.moral2_level == 5 ? "/" : "", + }, + moral3_level: { + col1: evaluate.moral3_level == 1 ? "/" : "", + col2: evaluate.moral3_level == 2 ? "/" : "", + col3: evaluate.moral3_level == 3 ? "/" : "", + col4: evaluate.moral3_level == 4 ? "/" : "", + col5: evaluate.moral3_level == 5 ? "/" : "", + }, + discipline1_level: { + col1: evaluate.discipline1_level == 1 ? "/" : "", + col2: evaluate.discipline1_level == 2 ? "/" : "", + col3: evaluate.discipline1_level == 3 ? "/" : "", + col4: evaluate.discipline1_level == 4 ? "/" : "", + col5: evaluate.discipline1_level == 5 ? "/" : "", + }, + discipline2_level: { + col1: evaluate.discipline2_level == 1 ? "/" : "", + col2: evaluate.discipline2_level == 2 ? "/" : "", + col3: evaluate.discipline2_level == 3 ? "/" : "", + col4: evaluate.discipline2_level == 4 ? "/" : "", + col5: evaluate.discipline2_level == 5 ? "/" : "", + }, + discipline3_level: { + col1: evaluate.discipline3_level == 1 ? "/" : "", + col2: evaluate.discipline3_level == 2 ? "/" : "", + col3: evaluate.discipline3_level == 3 ? "/" : "", + col4: evaluate.discipline3_level == 4 ? "/" : "", + col5: evaluate.discipline3_level == 5 ? "/" : "", + }, + discipline4_level: { + col1: evaluate.discipline4_level == 1 ? "/" : "", + col2: evaluate.discipline4_level == 2 ? "/" : "", + col3: evaluate.discipline4_level == 3 ? "/" : "", + col4: evaluate.discipline4_level == 4 ? "/" : "", + col5: evaluate.discipline4_level == 5 ? "/" : "", + }, + discipline5_level: { + col1: evaluate.discipline5_level == 1 ? "/" : "", + col2: evaluate.discipline5_level == 2 ? "/" : "", + col3: evaluate.discipline5_level == 3 ? "/" : "", + col4: evaluate.discipline5_level == 4 ? "/" : "", + col5: evaluate.discipline5_level == 5 ? "/" : "", + }, + behavior_other_desc: evaluate.behavior_other_desc, + behavior_other_level: + evaluate.behavior_other_level != null + ? { + col1: evaluate.behavior_other_level == 1 ? "/" : "", + col2: evaluate.behavior_other_level == 2 ? "/" : "", + col3: evaluate.behavior_other_level == 3 ? "/" : "", + col4: evaluate.behavior_other_level == 4 ? "/" : "", + col5: evaluate.behavior_other_level == 5 ? "/" : "", + } + : { + col1: "", + col2: "", + col3: "", + col4: "", + col5: "", + }, + + behavior_strength_desc: evaluate.behavior_strength_desc, + behavior_improve_desc: evaluate.behavior_improve_desc, + orientation: evaluate.orientation, + self_learning: evaluate.self_learning, + training_seminar: evaluate.training_seminar, + other_training: evaluate.other_training, + createdAt: evaluate.createdAt, + updatedAt: evaluate.updatedAt, + achievements: achievement, + role: director.role, + }; + + const assign = await AppDataSource.getRepository(Assign).findOne({ + select: ["id", "personal_id", "round_no", "date_start", "date_finish"], + where: { id: evaluate.assign_id }, + }); + + if (!assign) { + return new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบข้อมูล"); + } + + const experimenteeData = await this.personalRepository.findOne({ + where: { + personal_id: assign.personal_id, + }, + }); + + if (!experimenteeData) { + return new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบข้อมูล"); + } + + const experimentee = await { + ...experimenteeData, + name: `${experimenteeData.prefixName}${experimenteeData.firstName} ${experimenteeData.lastName}`, + PositionLevelName: experimenteeData.positionLevelName, + PositionLineName: experimenteeData.positionLineName, + Position: experimenteeData.positionName, + Department: "-", + OrganizationOrganization: experimenteeData.orgRootName, + Oc: experimenteeData.organization, + }; + + const data = await { + experimentee: experimentee ? experimentee : null, + director: director ? director : null, + assign, + evaluate: evaluateData, + }; + return new HttpSuccess(data); + } + + /** + * API แสดงข้อมูลแบบประเมินผล (ผู้บังคับบัญชา) ตาม id + * + * @summary ข้อมูลแบบประเมินผล (ผู้บังคับบัญชา) ตาม id + * + */ + @Get("evaluate-commander") + async GetDataEvaluateCommander( + @Query() id: string, + @Request() request: RequestWithUser + ) { + const evaluate = await this.evaluateCommanderRepository.findOne({ + where: { id }, + }); + + if (!evaluate) { + return new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบข้อมูล"); + } + + const evaluateData = await { + id: evaluate.id, + no: evaluate.no, + date_start: evaluate.date_start, + date_finish: evaluate.date_finish, + sign_dated: evaluate.commander_dated, + knowledge_level: { + col1: evaluate.knowledge_level == 1 ? "/" : "", + col2: evaluate.knowledge_level == 2 ? "/" : "", + col3: evaluate.knowledge_level == 3 ? "/" : "", + col4: evaluate.knowledge_level == 4 ? "/" : "", + col5: evaluate.knowledge_level == 5 ? "/" : "", + }, + skill_level: { + col1: evaluate.skill_level == 1 ? "/" : "", + col2: evaluate.skill_level == 2 ? "/" : "", + col3: evaluate.skill_level == 3 ? "/" : "", + col4: evaluate.skill_level == 4 ? "/" : "", + col5: evaluate.skill_level == 5 ? "/" : "", + }, + competency_level: { + col1: evaluate.competency_level == 1 ? "/" : "", + col2: evaluate.competency_level == 2 ? "/" : "", + col3: evaluate.competency_level == 3 ? "/" : "", + col4: evaluate.competency_level == 4 ? "/" : "", + col5: evaluate.competency_level == 5 ? "/" : "", + }, + learn_level: { + col1: evaluate.learn_level == 1 ? "/" : "", + col2: evaluate.learn_level == 2 ? "/" : "", + col3: evaluate.learn_level == 3 ? "/" : "", + col4: evaluate.learn_level == 4 ? "/" : "", + col5: evaluate.learn_level == 5 ? "/" : "", + }, + apply_level: { + col1: evaluate.apply_level == 1 ? "/" : "", + col2: evaluate.apply_level == 2 ? "/" : "", + col3: evaluate.apply_level == 3 ? "/" : "", + col4: evaluate.apply_level == 4 ? "/" : "", + col5: evaluate.apply_level == 5 ? "/" : "", + }, + success_level: { + col1: evaluate.success_level == 1 ? "/" : "", + col2: evaluate.success_level == 2 ? "/" : "", + col3: evaluate.success_level == 3 ? "/" : "", + col4: evaluate.success_level == 4 ? "/" : "", + col5: evaluate.success_level == 5 ? "/" : "", + }, + achievement_other_desc: evaluate.achievement_other_desc, + achievement_other_level: + evaluate.achievement_other_level != null + ? { + col1: evaluate.achievement_other_level == 1 ? "/" : "", + col2: evaluate.achievement_other_level == 2 ? "/" : "", + col3: evaluate.achievement_other_level == 3 ? "/" : "", + col4: evaluate.achievement_other_level == 4 ? "/" : "", + col5: evaluate.achievement_other_level == 5 ? "/" : "", + } + : { + col1: "", + col2: "", + col3: "", + col4: "", + col5: "", + }, + conduct1_level: { + col1: evaluate.conduct1_level == 1 ? "/" : "", + col2: evaluate.conduct1_level == 2 ? "/" : "", + col3: evaluate.conduct1_level == 3 ? "/" : "", + col4: evaluate.conduct1_level == 4 ? "/" : "", + col5: evaluate.conduct1_level == 5 ? "/" : "", + }, + conduct2_level: { + col1: evaluate.conduct2_level == 1 ? "/" : "", + col2: evaluate.conduct2_level == 2 ? "/" : "", + col3: evaluate.conduct2_level == 3 ? "/" : "", + col4: evaluate.conduct2_level == 4 ? "/" : "", + col5: evaluate.conduct2_level == 5 ? "/" : "", + }, + conduct3_level: { + col1: evaluate.conduct3_level == 1 ? "/" : "", + col2: evaluate.conduct3_level == 2 ? "/" : "", + col3: evaluate.conduct3_level == 3 ? "/" : "", + col4: evaluate.conduct3_level == 4 ? "/" : "", + col5: evaluate.conduct3_level == 5 ? "/" : "", + }, + conduct4_level: { + col1: evaluate.conduct4_level == 1 ? "/" : "", + col2: evaluate.conduct4_level == 2 ? "/" : "", + col3: evaluate.conduct4_level == 3 ? "/" : "", + col4: evaluate.conduct4_level == 4 ? "/" : "", + col5: evaluate.conduct4_level == 5 ? "/" : "", + }, + moral1_level: { + col1: evaluate.moral1_level == 1 ? "/" : "", + col2: evaluate.moral1_level == 2 ? "/" : "", + col3: evaluate.moral1_level == 3 ? "/" : "", + col4: evaluate.moral1_level == 4 ? "/" : "", + col5: evaluate.moral1_level == 5 ? "/" : "", + }, + moral2_level: { + col1: evaluate.moral2_level == 1 ? "/" : "", + col2: evaluate.moral2_level == 2 ? "/" : "", + col3: evaluate.moral2_level == 3 ? "/" : "", + col4: evaluate.moral2_level == 4 ? "/" : "", + col5: evaluate.moral2_level == 5 ? "/" : "", + }, + moral3_level: { + col1: evaluate.moral3_level == 1 ? "/" : "", + col2: evaluate.moral3_level == 2 ? "/" : "", + col3: evaluate.moral3_level == 3 ? "/" : "", + col4: evaluate.moral3_level == 4 ? "/" : "", + col5: evaluate.moral3_level == 5 ? "/" : "", + }, + discipline1_level: { + col1: evaluate.discipline1_level == 1 ? "/" : "", + col2: evaluate.discipline1_level == 2 ? "/" : "", + col3: evaluate.discipline1_level == 3 ? "/" : "", + col4: evaluate.discipline1_level == 4 ? "/" : "", + col5: evaluate.discipline1_level == 5 ? "/" : "", + }, + discipline2_level: { + col1: evaluate.discipline2_level == 1 ? "/" : "", + col2: evaluate.discipline2_level == 2 ? "/" : "", + col3: evaluate.discipline2_level == 3 ? "/" : "", + col4: evaluate.discipline2_level == 4 ? "/" : "", + col5: evaluate.discipline2_level == 5 ? "/" : "", + }, + discipline3_level: { + col1: evaluate.discipline3_level == 1 ? "/" : "", + col2: evaluate.discipline3_level == 2 ? "/" : "", + col3: evaluate.discipline3_level == 3 ? "/" : "", + col4: evaluate.discipline3_level == 4 ? "/" : "", + col5: evaluate.discipline3_level == 5 ? "/" : "", + }, + discipline4_level: { + col1: evaluate.discipline4_level == 1 ? "/" : "", + col2: evaluate.discipline4_level == 2 ? "/" : "", + col3: evaluate.discipline4_level == 3 ? "/" : "", + col4: evaluate.discipline4_level == 4 ? "/" : "", + col5: evaluate.discipline4_level == 5 ? "/" : "", + }, + discipline5_level: { + col1: evaluate.discipline5_level == 1 ? "/" : "", + col2: evaluate.discipline5_level == 2 ? "/" : "", + col3: evaluate.discipline5_level == 3 ? "/" : "", + col4: evaluate.discipline5_level == 4 ? "/" : "", + col5: evaluate.discipline5_level == 5 ? "/" : "", + }, + behavior_other_desc: evaluate.behavior_other_desc, + behavior_other_level: + evaluate.behavior_other_level != null + ? { + col1: evaluate.behavior_other_level == 1 ? "/" : "", + col2: evaluate.behavior_other_level == 2 ? "/" : "", + col3: evaluate.behavior_other_level == 3 ? "/" : "", + col4: evaluate.behavior_other_level == 4 ? "/" : "", + col5: evaluate.behavior_other_level == 5 ? "/" : "", + } + : { + col1: "", + col2: "", + col3: "", + col4: "", + col5: "", + }, + behavior_strength_desc: evaluate.behavior_strength_desc, + behavior_improve_desc: evaluate.behavior_improve_desc, + orientation: evaluate.orientation, + self_learning: evaluate.self_learning, + training_seminar: evaluate.training_seminar, + other_training: evaluate.other_training, + createdAt: evaluate.createdAt, + updatedAt: evaluate.updatedAt, + }; + + const assign = await AppDataSource.getRepository(Assign).findOne({ + select: ["id", "personal_id", "round_no", "date_start", "date_finish"], + where: { id: evaluate.assign_id }, + }); + + if (!assign) { + return new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบข้อมูล"); + } + + const experimenteeData = await this.personalRepository.findOne({ + where: { + personal_id: assign.personal_id, + }, + }); + + if (!experimenteeData) { + return new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบข้อมูล"); + } + + const experimentee = await { + ...experimenteeData, + name: `${experimenteeData.prefixName}${experimenteeData.firstName} ${experimenteeData.lastName}`, + PositionLevelName: experimenteeData.positionLevelName, + PositionLineName: experimenteeData.positionLineName, + Position: experimenteeData.positionName, + Department: "-", + OrganizationOrganization: experimenteeData.orgRootName, + Oc: experimenteeData.organization, + }; + + const commanderData = await this.assignDirectorRepository.findOne({ + select: [ + "personal_id", + "fullname", + "position", + "position", + "posType", + "posLevel", + "role", + "dated", + ], + where: { personal_id: evaluate.director_id }, + }); + + if (!commanderData) { + return new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบข้อมูล"); + } + + const commander = await { + ...commanderData, + name: commanderData.fullname, + Position: commanderData.position, + }; + + return new HttpSuccess({ + experimentee: experimentee ? experimentee : null, + commander: commander ? commander : null, + assign, + evaluate: evaluateData, + }); + } + + /** + * API แสดงข้อมูลแบบประเมินผล (คณะกรรมการ) ตาม id + * + * @summary ข้อมูลแบบประเมินผล (คณะกรรมการ) ตาม id + * + */ + @Get("evaluate-chairman") + async GetDataEvaluateChairman( + @Query() id: string, + @Request() request: RequestWithUser + ) { + const evaluate = await this.evaluateChairmanRepository.findOne({ + where: { id }, + }); + + if (!evaluate) { + return new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบข้อมูล"); + } + + const develop_total_score = await (Number( + evaluate.develop_orientation_score + ) + + Number(evaluate.develop_self_learning_score) + + Number(evaluate.develop_training_seminar_score) + + Number(evaluate.develop_other_training_score)); + const develop_total_percent = await (Number( + evaluate.develop_orientation_percent + ) + + Number(evaluate.develop_orientation_percent) + + Number(evaluate.develop_self_learning_percent) + + Number(evaluate.develop_training_seminar_percent)); + + const evaluateData = await { + id: evaluate.id, + // director_id: evaluate.director_id, + // assign_id: evaluate.assign_id, + no: evaluate.no, + date_start: evaluate.date_start, + date_finish: evaluate.date_finish, + chairman_dated: evaluate.chairman_dated, + director1_dated: evaluate.director1_dated, + director2_dated: evaluate.director2_dated, + knowledge_level: { + col1: evaluate.knowledge_level == 1 ? "/" : "", + col2: evaluate.knowledge_level == 2 ? "/" : "", + col3: evaluate.knowledge_level == 3 ? "/" : "", + col4: evaluate.knowledge_level == 4 ? "/" : "", + col5: evaluate.knowledge_level == 5 ? "/" : "", + }, + apply_level: { + col1: evaluate.apply_level == 1 ? "/" : "", + col2: evaluate.apply_level == 2 ? "/" : "", + col3: evaluate.apply_level == 3 ? "/" : "", + col4: evaluate.apply_level == 4 ? "/" : "", + col5: evaluate.apply_level == 5 ? "/" : "", + }, + success_level: { + col1: evaluate.success_level == 1 ? "/" : "", + col2: evaluate.success_level == 2 ? "/" : "", + col3: evaluate.success_level == 3 ? "/" : "", + col4: evaluate.success_level == 4 ? "/" : "", + col5: evaluate.success_level == 5 ? "/" : "", + }, + achievement_other_desc: evaluate.achievement_other_desc, + achievement_other_level: + evaluate.achievement_other_level != null + ? { + col1: evaluate.achievement_other_level == 1 ? "/" : "", + col2: evaluate.achievement_other_level == 2 ? "/" : "", + col3: evaluate.achievement_other_level == 3 ? "/" : "", + col4: evaluate.achievement_other_level == 4 ? "/" : "", + col5: evaluate.achievement_other_level == 5 ? "/" : "", + } + : { + col1: "", + col2: "", + col3: "", + col4: "", + col5: "", + }, + conduct1_level: { + col1: evaluate.conduct1_level == 1 ? "/" : "", + col2: evaluate.conduct1_level == 2 ? "/" : "", + col3: evaluate.conduct1_level == 3 ? "/" : "", + col4: evaluate.conduct1_level == 4 ? "/" : "", + col5: evaluate.conduct1_level == 5 ? "/" : "", + }, + conduct2_level: { + col1: evaluate.conduct2_level == 1 ? "/" : "", + col2: evaluate.conduct2_level == 2 ? "/" : "", + col3: evaluate.conduct2_level == 3 ? "/" : "", + col4: evaluate.conduct2_level == 4 ? "/" : "", + col5: evaluate.conduct2_level == 5 ? "/" : "", + }, + conduct3_level: { + col1: evaluate.conduct3_level == 1 ? "/" : "", + col2: evaluate.conduct3_level == 2 ? "/" : "", + col3: evaluate.conduct3_level == 3 ? "/" : "", + col4: evaluate.conduct3_level == 4 ? "/" : "", + col5: evaluate.conduct3_level == 5 ? "/" : "", + }, + conduct4_level: { + col1: evaluate.conduct4_level == 1 ? "/" : "", + col2: evaluate.conduct4_level == 2 ? "/" : "", + col3: evaluate.conduct4_level == 3 ? "/" : "", + col4: evaluate.conduct4_level == 4 ? "/" : "", + col5: evaluate.conduct4_level == 5 ? "/" : "", + }, + moral1_level: { + col1: evaluate.moral1_level == 1 ? "/" : "", + col2: evaluate.moral1_level == 2 ? "/" : "", + col3: evaluate.moral1_level == 3 ? "/" : "", + col4: evaluate.moral1_level == 4 ? "/" : "", + col5: evaluate.moral1_level == 5 ? "/" : "", + }, + moral2_level: { + col1: evaluate.moral2_level == 1 ? "/" : "", + col2: evaluate.moral2_level == 2 ? "/" : "", + col3: evaluate.moral2_level == 3 ? "/" : "", + col4: evaluate.moral2_level == 4 ? "/" : "", + col5: evaluate.moral2_level == 5 ? "/" : "", + }, + moral3_level: { + col1: evaluate.moral3_level == 1 ? "/" : "", + col2: evaluate.moral3_level == 2 ? "/" : "", + col3: evaluate.moral3_level == 3 ? "/" : "", + col4: evaluate.moral3_level == 4 ? "/" : "", + col5: evaluate.moral3_level == 5 ? "/" : "", + }, + discipline1_level: { + col1: evaluate.discipline1_level == 1 ? "/" : "", + col2: evaluate.discipline1_level == 2 ? "/" : "", + col3: evaluate.discipline1_level == 3 ? "/" : "", + col4: evaluate.discipline1_level == 4 ? "/" : "", + col5: evaluate.discipline1_level == 5 ? "/" : "", + }, + discipline2_level: { + col1: evaluate.discipline2_level == 1 ? "/" : "", + col2: evaluate.discipline2_level == 2 ? "/" : "", + col3: evaluate.discipline2_level == 3 ? "/" : "", + col4: evaluate.discipline2_level == 4 ? "/" : "", + col5: evaluate.discipline2_level == 5 ? "/" : "", + }, + discipline3_level: { + col1: evaluate.discipline3_level == 1 ? "/" : "", + col2: evaluate.discipline3_level == 2 ? "/" : "", + col3: evaluate.discipline3_level == 3 ? "/" : "", + col4: evaluate.discipline3_level == 4 ? "/" : "", + col5: evaluate.discipline3_level == 5 ? "/" : "", + }, + discipline4_level: { + col1: evaluate.discipline4_level == 1 ? "/" : "", + col2: evaluate.discipline4_level == 2 ? "/" : "", + col3: evaluate.discipline4_level == 3 ? "/" : "", + col4: evaluate.discipline4_level == 4 ? "/" : "", + col5: evaluate.discipline4_level == 5 ? "/" : "", + }, + discipline5_level: { + col1: evaluate.discipline5_level == 1 ? "/" : "", + col2: evaluate.discipline5_level == 2 ? "/" : "", + col3: evaluate.discipline5_level == 3 ? "/" : "", + col4: evaluate.discipline5_level == 4 ? "/" : "", + col5: evaluate.discipline5_level == 5 ? "/" : "", + }, + behavior_other_desc: evaluate.behavior_other_desc, + behavior_other_level: + evaluate.behavior_other_level != null + ? { + col1: evaluate.behavior_other_level == 1 ? "/" : "", + col2: evaluate.behavior_other_level == 2 ? "/" : "", + col3: evaluate.behavior_other_level == 3 ? "/" : "", + col4: evaluate.behavior_other_level == 4 ? "/" : "", + col5: evaluate.behavior_other_level == 5 ? "/" : "", + } + : { + col1: "", + col2: "", + col3: "", + col4: "", + col5: "", + }, + + achievement_score: evaluate.achievement_score, + achievement_score_total: evaluate.achievement_score_total, + achievement_percent: evaluate.achievement_percent, + achievement_result: evaluate.achievement_result, + behavior_score: evaluate.behavior_score, + behavior_score_total: evaluate.behavior_score_total, + behavior_percent: evaluate.behavior_percent, + behavior_result: evaluate.behavior_result, + sum_score: evaluate.sum_score, + sum_percent: evaluate.sum_percent, + + develop_orientation_score: evaluate.develop_orientation_score, + develop_self_learning_score: evaluate.develop_self_learning_score, + develop_training_seminar_score: evaluate.develop_training_seminar_score, + develop_other_training_score: evaluate.develop_other_training_score, + develop_total_score: develop_total_score, + develop_orientation_percent: evaluate.develop_orientation_percent, + develop_self_learning_percent: evaluate.develop_self_learning_percent, + develop_training_seminar_percent: + evaluate.develop_training_seminar_percent, + develop_other_training_percent: evaluate.develop_other_training_percent, + develop_total_percent: develop_total_percent, + develop_result: evaluate.develop_result, + evaluate_result: evaluate.evaluate_result, + createdAt: evaluate.createdAt, + updatedAt: evaluate.updatedAt, + }; + + const assign = await AppDataSource.getRepository(Assign).findOne({ + select: ["id", "personal_id", "round_no", "date_start", "date_finish"], + where: { id: evaluate.assign_id }, + }); + + if (!assign) { + return new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบข้อมูล"); + } + + const experimenteeData = await this.personalRepository.findOne({ + where: { + personal_id: assign.personal_id, + }, + }); + + if (!experimenteeData) { + return new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบข้อมูล"); + } + + const experimentee = await { + ...experimenteeData, + name: `${experimenteeData.prefixName}${experimenteeData.firstName} ${experimenteeData.lastName}`, + PositionLevelName: experimenteeData.positionLevelName, + PositionLineName: experimenteeData.positionLineName, + Position: experimenteeData.positionName, + Department: "-", + OrganizationOrganization: experimenteeData.orgRootName, + Oc: experimenteeData.organization, + }; + + const directorData = await this.assignDirectorRepository.find({ + select: [ + "personal_id", + "fullname", + "position", + "posType", + "posLevel", + "role", + "dated", + ], + where: { assign_id: assign.id }, + }); + + if (!directorData) { + return new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบข้อมูล"); + } + + const director = await Promise.all( + directorData.map(async (element) => { + return { + ...element, + name: element.fullname, + Position: element.position, + }; + }) + ); + + const mentorData = await (director.find((x) => x.role == "mentor") ?? null); + const mentor = mentorData != null ? mentorData : null; + + const commanderData = await (director.find((x) => x.role == "commander") ?? + null); + const commander = commanderData != null ? commanderData : null; + + const chairmanData = await (director.find((x) => x.role == "chairman") ?? + null); + const chairman = chairmanData != null ? chairmanData : null; + + return new HttpSuccess({ + experimentee: experimentee ? experimentee : null, + chairman: chairman ? chairman : null, + director1: commander ? commander : null, + director2: mentor ? mentor : null, + assign, + evaluate: evaluateData, + }); + } +} diff --git a/src/controllers/SurveyController.ts b/src/controllers/SurveyController.ts new file mode 100644 index 0000000..38c9632 --- /dev/null +++ b/src/controllers/SurveyController.ts @@ -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); + } +} diff --git a/src/database/data-source.ts b/src/database/data-source.ts new file mode 100644 index 0000000..3061dfe --- /dev/null +++ b/src/database/data-source.ts @@ -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(), +}); diff --git a/src/entities/Assign.ts b/src/entities/Assign.ts new file mode 100644 index 0000000..e7015df --- /dev/null +++ b/src/entities/Assign.ts @@ -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; diff --git a/src/entities/AssignCompetency.ts b/src/entities/AssignCompetency.ts new file mode 100644 index 0000000..cde7c9a --- /dev/null +++ b/src/entities/AssignCompetency.ts @@ -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; diff --git a/src/entities/AssignCompetencyGroup.ts b/src/entities/AssignCompetencyGroup.ts new file mode 100644 index 0000000..efc9d45 --- /dev/null +++ b/src/entities/AssignCompetencyGroup.ts @@ -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; diff --git a/src/entities/AssignDirector.ts b/src/entities/AssignDirector.ts new file mode 100644 index 0000000..6971cb1 --- /dev/null +++ b/src/entities/AssignDirector.ts @@ -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; diff --git a/src/entities/AssignJob.ts b/src/entities/AssignJob.ts new file mode 100644 index 0000000..3bc1430 --- /dev/null +++ b/src/entities/AssignJob.ts @@ -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; diff --git a/src/entities/AssignKnowledge.ts b/src/entities/AssignKnowledge.ts new file mode 100644 index 0000000..13ffa09 --- /dev/null +++ b/src/entities/AssignKnowledge.ts @@ -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; diff --git a/src/entities/AssignLaw.ts b/src/entities/AssignLaw.ts new file mode 100644 index 0000000..e999995 --- /dev/null +++ b/src/entities/AssignLaw.ts @@ -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; diff --git a/src/entities/AssignOutput.ts b/src/entities/AssignOutput.ts new file mode 100644 index 0000000..ef71de5 --- /dev/null +++ b/src/entities/AssignOutput.ts @@ -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; diff --git a/src/entities/AssignSkill.ts b/src/entities/AssignSkill.ts new file mode 100644 index 0000000..38a42e9 --- /dev/null +++ b/src/entities/AssignSkill.ts @@ -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; diff --git a/src/entities/EvaluateAchievement.ts b/src/entities/EvaluateAchievement.ts new file mode 100644 index 0000000..fe48198 --- /dev/null +++ b/src/entities/EvaluateAchievement.ts @@ -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; diff --git a/src/entities/EvaluateAssessor.ts b/src/entities/EvaluateAssessor.ts new file mode 100644 index 0000000..8a3cd8a --- /dev/null +++ b/src/entities/EvaluateAssessor.ts @@ -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; diff --git a/src/entities/EvaluateChairman.ts b/src/entities/EvaluateChairman.ts new file mode 100644 index 0000000..00eb78c --- /dev/null +++ b/src/entities/EvaluateChairman.ts @@ -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; diff --git a/src/entities/EvaluateCommander.ts b/src/entities/EvaluateCommander.ts new file mode 100644 index 0000000..86818ce --- /dev/null +++ b/src/entities/EvaluateCommander.ts @@ -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; diff --git a/src/entities/EvaluateResult.ts b/src/entities/EvaluateResult.ts new file mode 100644 index 0000000..2e972b0 --- /dev/null +++ b/src/entities/EvaluateResult.ts @@ -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; diff --git a/src/entities/Knowledge.ts b/src/entities/Knowledge.ts new file mode 100644 index 0000000..57e038c --- /dev/null +++ b/src/entities/Knowledge.ts @@ -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[]; +} diff --git a/src/entities/Law.ts b/src/entities/Law.ts new file mode 100644 index 0000000..6462aef --- /dev/null +++ b/src/entities/Law.ts @@ -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[]; +} diff --git a/src/entities/MapKnowledgeSkill.ts b/src/entities/MapKnowledgeSkill.ts new file mode 100644 index 0000000..16863ea --- /dev/null +++ b/src/entities/MapKnowledgeSkill.ts @@ -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; +} diff --git a/src/entities/Personal.ts b/src/entities/Personal.ts new file mode 100644 index 0000000..8a8b317 --- /dev/null +++ b/src/entities/Personal.ts @@ -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; diff --git a/src/entities/Skill.ts b/src/entities/Skill.ts new file mode 100644 index 0000000..ce01c91 --- /dev/null +++ b/src/entities/Skill.ts @@ -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[]; +} diff --git a/src/entities/Survey.ts b/src/entities/Survey.ts new file mode 100644 index 0000000..a6ca2c2 --- /dev/null +++ b/src/entities/Survey.ts @@ -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; diff --git a/src/entities/base/Base.ts b/src/entities/base/Base.ts new file mode 100644 index 0000000..7ba6ee7 --- /dev/null +++ b/src/entities/base/Base.ts @@ -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; +} diff --git a/src/interfaces/call-api.ts b/src/interfaces/call-api.ts new file mode 100644 index 0000000..78f0f14 --- /dev/null +++ b/src/interfaces/call-api.ts @@ -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; diff --git a/src/interfaces/extension.ts b/src/interfaces/extension.ts new file mode 100644 index 0000000..5de8bd4 --- /dev/null +++ b/src/interfaces/extension.ts @@ -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; diff --git a/src/interfaces/http-error.ts b/src/interfaces/http-error.ts new file mode 100644 index 0000000..5d396e2 --- /dev/null +++ b/src/interfaces/http-error.ts @@ -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; diff --git a/src/interfaces/http-status.ts b/src/interfaces/http-status.ts new file mode 100644 index 0000000..291446f --- /dev/null +++ b/src/interfaces/http-status.ts @@ -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; diff --git a/src/interfaces/http-success.ts b/src/interfaces/http-success.ts new file mode 100644 index 0000000..d2f282b --- /dev/null +++ b/src/interfaces/http-success.ts @@ -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; diff --git a/src/interfaces/permission.ts b/src/interfaces/permission.ts new file mode 100644 index 0000000..1669018 --- /dev/null +++ b/src/interfaces/permission.ts @@ -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; diff --git a/src/interfaces/storage-fs.ts b/src/interfaces/storage-fs.ts new file mode 100644 index 0000000..63d0769 --- /dev/null +++ b/src/interfaces/storage-fs.ts @@ -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; + + path: string; + upload: boolean; + + updatedAt: string | Date; + updatedBy: string; + createdAt: string | Date; + createdBy: string; +} diff --git a/src/interfaces/utils.ts b/src/interfaces/utils.ts new file mode 100644 index 0000000..d5fde6c --- /dev/null +++ b/src/interfaces/utils.ts @@ -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; +}; diff --git a/src/middlewares/auth.ts b/src/middlewares/auth.ts new file mode 100644 index 0000000..de43a0c --- /dev/null +++ b/src/middlewares/auth.ts @@ -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 = {}; + + 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); +} diff --git a/src/middlewares/error.ts b/src/middlewares/error.ts new file mode 100644 index 0000000..f8d0b56 --- /dev/null +++ b/src/middlewares/error.ts @@ -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; diff --git a/src/middlewares/logs.ts b/src/middlewares/logs.ts new file mode 100644 index 0000000..e31fd34 --- /dev/null +++ b/src/middlewares/logs.ts @@ -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 = { + 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; diff --git a/src/middlewares/user.ts b/src/middlewares/user.ts new file mode 100644 index 0000000..a35cdc4 --- /dev/null +++ b/src/middlewares/user.ts @@ -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[]; + }; +}; diff --git a/src/migration/1725518260097-start.ts b/src/migration/1725518260097-start.ts new file mode 100644 index 0000000..7be33d0 --- /dev/null +++ b/src/migration/1725518260097-start.ts @@ -0,0 +1,86 @@ +import { MigrationInterface, QueryRunner } from "typeorm"; + +export class Start1725518260097 implements MigrationInterface { + name = 'Start1725518260097' + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`CREATE TABLE \`evaluateCommander\` (\`createdAt\` datetime(6) NOT NULL COMMENT 'สร้างข้อมูลเมื่อ' DEFAULT CURRENT_TIMESTAMP(6), \`createdUserId\` varchar(40) NOT NULL COMMENT 'User Id ที่สร้างข้อมูล' DEFAULT '00000000-0000-0000-0000-000000000000', \`updatedAt\` datetime(6) NOT NULL COMMENT 'แก้ไขข้อมูลล่าสุดเมื่อ' DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6), \`updateUserId\` varchar(40) NOT NULL COMMENT 'User Id ที่แก้ไขข้อมูล' DEFAULT '00000000-0000-0000-0000-000000000000', \`createdFullName\` varchar(200) NOT NULL COMMENT 'ชื่อ User ที่สร้างข้อมูล' DEFAULT 'System Administrator', \`updateFullName\` varchar(200) NOT NULL COMMENT 'ชื่อ User ที่แก้ไขข้อมูลล่าสุด' DEFAULT 'System Administrator', \`id\` varchar(36) NOT NULL, \`assign_id\` varchar(255) NOT NULL COMMENT 'Id ของแบบมอบหมายงาน', \`director_id\` varchar(255) NOT NULL COMMENT 'Id ผู้ประเมิน', \`no\` varchar(255) NOT NULL COMMENT 'ครั้งที่ประเมิน' DEFAULT '1', \`date_start\` date NOT NULL COMMENT 'วันที่เริ่มการบันทึก', \`date_finish\` date NOT NULL COMMENT 'วันที่สิ้นสุดการบันทึก', \`commander_dated\` date NULL COMMENT 'วันที่ลงนาม', \`knowledge_level\` int NOT NULL COMMENT 'คะแนนความรู้ความสามารถ' DEFAULT '0', \`skill_level\` int NOT NULL COMMENT 'คะแนนทักษะ' DEFAULT '0', \`competency_level\` int NOT NULL COMMENT 'คะแนนสมรรถนะ' DEFAULT '0', \`learn_level\` int NOT NULL COMMENT 'คะแนนความสามารถในการเรียนรู้งาน' DEFAULT '0', \`apply_level\` int NOT NULL COMMENT 'คะแนนความสามารถในการปรับใช้ความรู้กับงานในหน้าที่' DEFAULT '0', \`success_level\` int NOT NULL COMMENT 'คะแนนความสำเร็จของงานที่ได้รับมอบหมาย' DEFAULT '0', \`achievement_other_desc\` varchar(255) NULL COMMENT 'ความสำเร็จของงานที่ได้รับมอบหมายอื่นๆ', \`achievement_other_level\` int NULL COMMENT 'ระดับความสำเร็จของงานที่ได้รับมอบหมายอื่นๆ', \`conduct1_level\` int NOT NULL COMMENT 'คะแนนความประพฤติ ให้บริการประชาชนหรือผู้รับบริการด้วยอัธยาศัยดี' DEFAULT '0', \`conduct2_level\` int NOT NULL COMMENT 'คะแนนความประพฤติ มีความรับผิดชอบในการปฏิบัติบัติงาน' DEFAULT '0', \`conduct3_level\` int NOT NULL COMMENT 'คะแนนความประพฤติ ให้บริการประชาชนหรือผู้รับบริการด้วยความรวดเร็ว เอาใจใส่เป็นมาตรฐานเดียวกัน' DEFAULT '0', \`conduct4_level\` int NOT NULL COMMENT 'คะแนนความประพฤติ ตั้งใจปฏิบัติบัติหน้าที่ราชการด้วยความอุตสาหะ ขยันหมั่นเพียร' DEFAULT '0', \`moral1_level\` int NOT NULL COMMENT 'คะแนนความมีจรรยาบรรณ อุทิศตนและเสียสละเวลาในการปฏิบัติบัติงานอย่างเต็มกำลังความสามารถ' DEFAULT '0', \`moral2_level\` int NOT NULL COMMENT 'คะแนนความมีจรรยาบรรณ มีจิตสำนึกที่ดี ปฏิบัติบัติงานด้วยความซื่อสัตย์ สุจริต' DEFAULT '0', \`moral3_level\` int NOT NULL COMMENT 'คะแนนความมีจรรยาบรรณ ยึดมั่นในสถาบันบัพระมหากษัตริย์ และไม่กระทำการใด ๆ อันจะก่อให้เกิดความเสียหายต่อประเทศชาติ' DEFAULT '0', \`discipline1_level\` int NOT NULL COMMENT 'คะแนนการรักษาวินัย มีความรับรัผิดชอบในการรักษาเวลาทำงาน' DEFAULT '0', \`discipline2_level\` int NOT NULL COMMENT 'คะแนนการรักษาวินัย แต่งกายในการปฏิบัติบัติงานได้อย่างเหมาะสมกับการเป็นข้าราชการ' DEFAULT '0', \`discipline3_level\` int NOT NULL COMMENT 'คะแนนการรักษาวินัย ไม่กระทำการใด ๆ อันอาจก่อให้เกิดความเสียหายแก่ชื่อเสียงของหน่วยงาน' DEFAULT '0', \`discipline4_level\` int NOT NULL COMMENT 'คะแนนการรักษาวินัย ไม่กระทำการใด ๆ อันเป็นการเสื่อมเกียรติและศักดิ์ศรีของความเป็นข้าราชการ' DEFAULT '0', \`discipline5_level\` int NOT NULL COMMENT 'คะแนนการรักษาวินัย ปฏิบัติบัติหน้าที่อย่างตรงไปตรงมาโดยยึดหลักจรรยาบรรณวิชาชีพ' DEFAULT '0', \`behavior_other_desc\` varchar(255) NULL COMMENT 'ความประพฤติอื่นๆ', \`behavior_other_level\` int NULL COMMENT 'ระดับความประพฤติอื่นๆ', \`behavior_strength_desc\` varchar(255) NOT NULL COMMENT 'จุดเด่นของความประพฤติ', \`behavior_improve_desc\` varchar(255) NOT NULL COMMENT 'สิ่งที่ควรปรับปรุงของความประพฤติ', \`orientation\` int NOT NULL COMMENT 'คะแนนการพัฒนาตนเอง การปฐมนิเทศ' DEFAULT '0', \`self_learning\` int NOT NULL COMMENT 'คะแนนการพัฒนาตนเอง การเรียนรู้ด้วยตนเอง' DEFAULT '0', \`training_seminar\` int NOT NULL COMMENT 'คะแนนการพัฒนาตนเอง การอบรมสัมนาร่วมกัน' DEFAULT '0', \`other_training\` int NOT NULL COMMENT 'คะแนนการพัฒนาตนเอง การอบรมอื่น ๆ ตามที่หน่วยงานกำหนด' DEFAULT '0', PRIMARY KEY (\`id\`)) ENGINE=InnoDB`); + await queryRunner.query(`CREATE TABLE \`assignCompetencyGroup\` (\`createdAt\` datetime(6) NOT NULL COMMENT 'สร้างข้อมูลเมื่อ' DEFAULT CURRENT_TIMESTAMP(6), \`createdUserId\` varchar(40) NOT NULL COMMENT 'User Id ที่สร้างข้อมูล' DEFAULT '00000000-0000-0000-0000-000000000000', \`updatedAt\` datetime(6) NOT NULL COMMENT 'แก้ไขข้อมูลล่าสุดเมื่อ' DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6), \`updateUserId\` varchar(40) NOT NULL COMMENT 'User Id ที่แก้ไขข้อมูล' DEFAULT '00000000-0000-0000-0000-000000000000', \`createdFullName\` varchar(200) NOT NULL COMMENT 'ชื่อ User ที่สร้างข้อมูล' DEFAULT 'System Administrator', \`updateFullName\` varchar(200) NOT NULL COMMENT 'ชื่อ User ที่แก้ไขข้อมูลล่าสุด' DEFAULT 'System Administrator', \`assign_id\` varchar(255) NOT NULL COMMENT 'Id ของแบบมอบหมายงาน', \`competency_group_id\` varchar(255) NOT NULL COMMENT 'Id สมรรถนะประจำกลุ่มงาน', \`competency_group_level\` int NOT NULL COMMENT 'ระดับพฤติกรรม' DEFAULT '0', \`competency_group_name\` varchar(255) NULL COMMENT 'ชื่อสมรรถนะประจำกลุ่มงาน', \`competency_group_description\` text NULL COMMENT 'รายละเอียดสมรรถนะประจำกลุ่มงาน', PRIMARY KEY (\`assign_id\`, \`competency_group_id\`)) ENGINE=InnoDB`); + await queryRunner.query(`CREATE TABLE \`assignCompetency\` (\`createdAt\` datetime(6) NOT NULL COMMENT 'สร้างข้อมูลเมื่อ' DEFAULT CURRENT_TIMESTAMP(6), \`createdUserId\` varchar(40) NOT NULL COMMENT 'User Id ที่สร้างข้อมูล' DEFAULT '00000000-0000-0000-0000-000000000000', \`updatedAt\` datetime(6) NOT NULL COMMENT 'แก้ไขข้อมูลล่าสุดเมื่อ' DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6), \`updateUserId\` varchar(40) NOT NULL COMMENT 'User Id ที่แก้ไขข้อมูล' DEFAULT '00000000-0000-0000-0000-000000000000', \`createdFullName\` varchar(200) NOT NULL COMMENT 'ชื่อ User ที่สร้างข้อมูล' DEFAULT 'System Administrator', \`updateFullName\` varchar(200) NOT NULL COMMENT 'ชื่อ User ที่แก้ไขข้อมูลล่าสุด' DEFAULT 'System Administrator', \`assign_id\` varchar(255) NOT NULL COMMENT 'Id ของแบบมอบหมายงาน', \`competency_id\` varchar(255) NOT NULL COMMENT 'Id สมรรถนะหลัก', \`competency_level\` int NOT NULL COMMENT 'ระดับพฤติกรรม' DEFAULT '0', \`competency_name\` varchar(255) NULL COMMENT 'ชื่อสมรรถนะหลัก', \`competency_description\` text NULL COMMENT 'รายละเอียดสมรรถนะหลัก', PRIMARY KEY (\`assign_id\`, \`competency_id\`)) ENGINE=InnoDB`); + await queryRunner.query(`CREATE TABLE \`assignDirector\` (\`createdAt\` datetime(6) NOT NULL COMMENT 'สร้างข้อมูลเมื่อ' DEFAULT CURRENT_TIMESTAMP(6), \`createdUserId\` varchar(40) NOT NULL COMMENT 'User Id ที่สร้างข้อมูล' DEFAULT '00000000-0000-0000-0000-000000000000', \`updatedAt\` datetime(6) NOT NULL COMMENT 'แก้ไขข้อมูลล่าสุดเมื่อ' DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6), \`updateUserId\` varchar(40) NOT NULL COMMENT 'User Id ที่แก้ไขข้อมูล' DEFAULT '00000000-0000-0000-0000-000000000000', \`createdFullName\` varchar(200) NOT NULL COMMENT 'ชื่อ User ที่สร้างข้อมูล' DEFAULT 'System Administrator', \`updateFullName\` varchar(200) NOT NULL COMMENT 'ชื่อ User ที่แก้ไขข้อมูลล่าสุด' DEFAULT 'System Administrator', \`assign_id\` varchar(255) NOT NULL COMMENT 'Id ของแบบมอบหมายงาน', \`personal_id\` varchar(255) NOT NULL COMMENT 'Id ผู้ดูแล/ผู้บังคับบัญชา/ประธาน', \`role\` enum ('mentor', 'commander', 'chairman') NOT NULL COMMENT 'หน้าที่ mentor = ผู้ดูแล, commander = ผู้บังคับบัญชา, chairman = ประธานกรรมการ', \`fullname\` varchar(255) NOT NULL COMMENT 'ชื่อ-นามสกุล', \`position\` varchar(255) NULL COMMENT 'ตำแหน่งในสายงาน', \`posType\` varchar(255) NULL COMMENT 'ประเภทตำแหน่ง', \`posLevel\` varchar(255) NULL COMMENT 'ระดับตำแหน่ง', \`dated\` date NULL COMMENT 'วันที่ลงนาม', \`ordering\` int NULL COMMENT 'ลำดับการแสดงผล' DEFAULT '0', PRIMARY KEY (\`assign_id\`, \`personal_id\`, \`role\`)) ENGINE=InnoDB`); + await queryRunner.query(`CREATE TABLE \`assignJob\` (\`createdAt\` datetime(6) NOT NULL COMMENT 'สร้างข้อมูลเมื่อ' DEFAULT CURRENT_TIMESTAMP(6), \`createdUserId\` varchar(40) NOT NULL COMMENT 'User Id ที่สร้างข้อมูล' DEFAULT '00000000-0000-0000-0000-000000000000', \`updatedAt\` datetime(6) NOT NULL COMMENT 'แก้ไขข้อมูลล่าสุดเมื่อ' DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6), \`updateUserId\` varchar(40) NOT NULL COMMENT 'User Id ที่แก้ไขข้อมูล' DEFAULT '00000000-0000-0000-0000-000000000000', \`createdFullName\` varchar(200) NOT NULL COMMENT 'ชื่อ User ที่สร้างข้อมูล' DEFAULT 'System Administrator', \`updateFullName\` varchar(200) NOT NULL COMMENT 'ชื่อ User ที่แก้ไขข้อมูลล่าสุด' DEFAULT 'System Administrator', \`assign_id\` varchar(255) NOT NULL COMMENT 'Id ของแบบมอบหมายงาน', \`id\` int NOT NULL COMMENT 'Running number', \`activity_desc\` text NOT NULL COMMENT 'ผลการปฏิบัติงานกิจกรรม/ขั้นตอนการปฏิบัติ', \`goal_desc\` text NOT NULL COMMENT 'ผลการปฏิบัติงานเป้าหมาย', PRIMARY KEY (\`assign_id\`, \`id\`)) ENGINE=InnoDB`); + await queryRunner.query(`CREATE TABLE \`knowledge\` (\`createdAt\` datetime(6) NOT NULL COMMENT 'สร้างข้อมูลเมื่อ' DEFAULT CURRENT_TIMESTAMP(6), \`createdUserId\` varchar(40) NOT NULL COMMENT 'User Id ที่สร้างข้อมูล' DEFAULT '00000000-0000-0000-0000-000000000000', \`updatedAt\` datetime(6) NOT NULL COMMENT 'แก้ไขข้อมูลล่าสุดเมื่อ' DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6), \`updateUserId\` varchar(40) NOT NULL COMMENT 'User Id ที่แก้ไขข้อมูล' DEFAULT '00000000-0000-0000-0000-000000000000', \`createdFullName\` varchar(200) NOT NULL COMMENT 'ชื่อ User ที่สร้างข้อมูล' DEFAULT 'System Administrator', \`updateFullName\` varchar(200) NOT NULL COMMENT 'ชื่อ User ที่แก้ไขข้อมูลล่าสุด' DEFAULT 'System Administrator', \`id\` int NOT NULL AUTO_INCREMENT, \`type\` enum ('performance', 'law') NOT NULL COMMENT 'หมวดหมู่ความรู้ performance คือความรู้ความสามารถ, law คือความรู้ด้านกฎหมาย' DEFAULT 'performance', \`title\` varchar(255) NOT NULL COMMENT 'หัวข้อความรู้', \`level1\` text NULL COMMENT 'คำอธิบายระดับความรู้ ระดับที่ 1', \`level2\` text NULL COMMENT 'คำอธิบายระดับความรู้ ระดับที่ 2', \`level3\` text NULL COMMENT 'คำอธิบายระดับความรู้ ระดับที่ 3', \`level4\` text NULL COMMENT 'คำอธิบายระดับความรู้ ระดับที่ 4', \`level5\` text NULL COMMENT 'คำอธิบายระดับความรู้ ระดับที่ 5', \`active\` int NOT NULL COMMENT 'สถานะการใช้งาน 1 คือใช้งานปกติ, 0 คือไม่ใช้งาน' DEFAULT '1', PRIMARY KEY (\`id\`)) ENGINE=InnoDB`); + await queryRunner.query(`CREATE TABLE \`assignKnowledge\` (\`createdAt\` datetime(6) NOT NULL COMMENT 'สร้างข้อมูลเมื่อ' DEFAULT CURRENT_TIMESTAMP(6), \`createdUserId\` varchar(40) NOT NULL COMMENT 'User Id ที่สร้างข้อมูล' DEFAULT '00000000-0000-0000-0000-000000000000', \`updatedAt\` datetime(6) NOT NULL COMMENT 'แก้ไขข้อมูลล่าสุดเมื่อ' DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6), \`updateUserId\` varchar(40) NOT NULL COMMENT 'User Id ที่แก้ไขข้อมูล' DEFAULT '00000000-0000-0000-0000-000000000000', \`createdFullName\` varchar(200) NOT NULL COMMENT 'ชื่อ User ที่สร้างข้อมูล' DEFAULT 'System Administrator', \`updateFullName\` varchar(200) NOT NULL COMMENT 'ชื่อ User ที่แก้ไขข้อมูลล่าสุด' DEFAULT 'System Administrator', \`assign_id\` varchar(255) NOT NULL COMMENT 'Id ของแบบมอบหมายงาน', \`knowledge_id\` int NOT NULL COMMENT 'Runnig number', \`knowledge_level\` int NOT NULL COMMENT 'ระดับพฤติกรรม' DEFAULT '0', PRIMARY KEY (\`assign_id\`, \`knowledge_id\`)) ENGINE=InnoDB`); + await queryRunner.query(`CREATE TABLE \`laws\` (\`createdAt\` datetime(6) NOT NULL COMMENT 'สร้างข้อมูลเมื่อ' DEFAULT CURRENT_TIMESTAMP(6), \`createdUserId\` varchar(40) NOT NULL COMMENT 'User Id ที่สร้างข้อมูล' DEFAULT '00000000-0000-0000-0000-000000000000', \`updatedAt\` datetime(6) NOT NULL COMMENT 'แก้ไขข้อมูลล่าสุดเมื่อ' DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6), \`updateUserId\` varchar(40) NOT NULL COMMENT 'User Id ที่แก้ไขข้อมูล' DEFAULT '00000000-0000-0000-0000-000000000000', \`createdFullName\` varchar(200) NOT NULL COMMENT 'ชื่อ User ที่สร้างข้อมูล' DEFAULT 'System Administrator', \`updateFullName\` varchar(200) NOT NULL COMMENT 'ชื่อ User ที่แก้ไขข้อมูลล่าสุด' DEFAULT 'System Administrator', \`id\` int NOT NULL AUTO_INCREMENT, \`parent_id\` int NOT NULL COMMENT 'เลเวลของ node ข้อมูล' DEFAULT '0', \`description\` text NULL COMMENT 'คำอธิบาย', \`status_select\` int NOT NULL COMMENT '1 คือเลือกได้, 0 คือไม่ให้เลือก' DEFAULT '1', \`active\` int NOT NULL COMMENT 'คำอธิบายระดับความรู้ ระดับที่ 5' DEFAULT '1', PRIMARY KEY (\`id\`)) ENGINE=InnoDB`); + await queryRunner.query(`CREATE TABLE \`assignLaw\` (\`createdAt\` datetime(6) NOT NULL COMMENT 'สร้างข้อมูลเมื่อ' DEFAULT CURRENT_TIMESTAMP(6), \`createdUserId\` varchar(40) NOT NULL COMMENT 'User Id ที่สร้างข้อมูล' DEFAULT '00000000-0000-0000-0000-000000000000', \`updatedAt\` datetime(6) NOT NULL COMMENT 'แก้ไขข้อมูลล่าสุดเมื่อ' DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6), \`updateUserId\` varchar(40) NOT NULL COMMENT 'User Id ที่แก้ไขข้อมูล' DEFAULT '00000000-0000-0000-0000-000000000000', \`createdFullName\` varchar(200) NOT NULL COMMENT 'ชื่อ User ที่สร้างข้อมูล' DEFAULT 'System Administrator', \`updateFullName\` varchar(200) NOT NULL COMMENT 'ชื่อ User ที่แก้ไขข้อมูลล่าสุด' DEFAULT 'System Administrator', \`assign_id\` varchar(255) NOT NULL COMMENT 'Id ของแบบมอบหมายงาน', \`law_id\` int NOT NULL COMMENT 'Running number', \`ordering\` int NOT NULL COMMENT 'ลำดับ' DEFAULT '0', PRIMARY KEY (\`assign_id\`, \`law_id\`)) ENGINE=InnoDB`); + await queryRunner.query(`CREATE TABLE \`assignOutput\` (\`createdAt\` datetime(6) NOT NULL COMMENT 'สร้างข้อมูลเมื่อ' DEFAULT CURRENT_TIMESTAMP(6), \`createdUserId\` varchar(40) NOT NULL COMMENT 'User Id ที่สร้างข้อมูล' DEFAULT '00000000-0000-0000-0000-000000000000', \`updatedAt\` datetime(6) NOT NULL COMMENT 'แก้ไขข้อมูลล่าสุดเมื่อ' DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6), \`updateUserId\` varchar(40) NOT NULL COMMENT 'User Id ที่แก้ไขข้อมูล' DEFAULT '00000000-0000-0000-0000-000000000000', \`createdFullName\` varchar(200) NOT NULL COMMENT 'ชื่อ User ที่สร้างข้อมูล' DEFAULT 'System Administrator', \`updateFullName\` varchar(200) NOT NULL COMMENT 'ชื่อ User ที่แก้ไขข้อมูลล่าสุด' DEFAULT 'System Administrator', \`id\` int NOT NULL COMMENT 'Runnig number', \`assign_id\` varchar(255) NOT NULL COMMENT 'Id ของแบบมอบหมายงาน', \`output_desc\` text NOT NULL COMMENT 'ผลผลิตของงานที่คาดหวัง', \`indicator_desc\` text NOT NULL COMMENT 'ตัวชี้วัดความสําเร็จของงาน', PRIMARY KEY (\`id\`, \`assign_id\`)) ENGINE=InnoDB`); + await queryRunner.query(`CREATE TABLE \`skills\` (\`createdAt\` datetime(6) NOT NULL COMMENT 'สร้างข้อมูลเมื่อ' DEFAULT CURRENT_TIMESTAMP(6), \`createdUserId\` varchar(40) NOT NULL COMMENT 'User Id ที่สร้างข้อมูล' DEFAULT '00000000-0000-0000-0000-000000000000', \`updatedAt\` datetime(6) NOT NULL COMMENT 'แก้ไขข้อมูลล่าสุดเมื่อ' DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6), \`updateUserId\` varchar(40) NOT NULL COMMENT 'User Id ที่แก้ไขข้อมูล' DEFAULT '00000000-0000-0000-0000-000000000000', \`createdFullName\` varchar(200) NOT NULL COMMENT 'ชื่อ User ที่สร้างข้อมูล' DEFAULT 'System Administrator', \`updateFullName\` varchar(200) NOT NULL COMMENT 'ชื่อ User ที่แก้ไขข้อมูลล่าสุด' DEFAULT 'System Administrator', \`id\` int NOT NULL AUTO_INCREMENT, \`type\` enum ('computer', 'english', 'information', 'resourse') NOT NULL COMMENT 'หมวดหมู่ทักษะ', \`title\` varchar(255) NOT NULL COMMENT 'หัวข้อความรู้', \`level1\` text NULL COMMENT 'คำอธิบายระดับความรู้ ระดับที่ 1', \`level2\` text NULL COMMENT 'คำอธิบายระดับความรู้ ระดับที่ 2', \`level3\` text NULL COMMENT 'คำอธิบายระดับความรู้ ระดับที่ 3', \`level4\` text NULL COMMENT 'คำอธิบายระดับความรู้ ระดับที่ 4', \`level5\` text NULL COMMENT 'คำอธิบายระดับความรู้ ระดับที่ 5', \`active\` int NOT NULL COMMENT 'สถานะการใช้งาน 1 คือใช้งานปกติ, 0 คือไม่ใช้งาน' DEFAULT '1', PRIMARY KEY (\`id\`)) ENGINE=InnoDB`); + await queryRunner.query(`CREATE TABLE \`assignSkill\` (\`createdAt\` datetime(6) NOT NULL COMMENT 'สร้างข้อมูลเมื่อ' DEFAULT CURRENT_TIMESTAMP(6), \`createdUserId\` varchar(40) NOT NULL COMMENT 'User Id ที่สร้างข้อมูล' DEFAULT '00000000-0000-0000-0000-000000000000', \`updatedAt\` datetime(6) NOT NULL COMMENT 'แก้ไขข้อมูลล่าสุดเมื่อ' DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6), \`updateUserId\` varchar(40) NOT NULL COMMENT 'User Id ที่แก้ไขข้อมูล' DEFAULT '00000000-0000-0000-0000-000000000000', \`createdFullName\` varchar(200) NOT NULL COMMENT 'ชื่อ User ที่สร้างข้อมูล' DEFAULT 'System Administrator', \`updateFullName\` varchar(200) NOT NULL COMMENT 'ชื่อ User ที่แก้ไขข้อมูลล่าสุด' DEFAULT 'System Administrator', \`assign_id\` varchar(255) NOT NULL COMMENT 'Id ของแบบมอบหมายงาน', \`skill_id\` int NOT NULL COMMENT 'Runnig number', \`skill_level\` int NOT NULL COMMENT 'ระดับพฤติกรรม' DEFAULT '0', PRIMARY KEY (\`assign_id\`, \`skill_id\`)) ENGINE=InnoDB`); + await queryRunner.query(`CREATE TABLE \`evaluateAchievement\` (\`createdAt\` datetime(6) NOT NULL COMMENT 'สร้างข้อมูลเมื่อ' DEFAULT CURRENT_TIMESTAMP(6), \`createdUserId\` varchar(40) NOT NULL COMMENT 'User Id ที่สร้างข้อมูล' DEFAULT '00000000-0000-0000-0000-000000000000', \`updatedAt\` datetime(6) NOT NULL COMMENT 'แก้ไขข้อมูลล่าสุดเมื่อ' DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6), \`updateUserId\` varchar(40) NOT NULL COMMENT 'User Id ที่แก้ไขข้อมูล' DEFAULT '00000000-0000-0000-0000-000000000000', \`createdFullName\` varchar(200) NOT NULL COMMENT 'ชื่อ User ที่สร้างข้อมูล' DEFAULT 'System Administrator', \`updateFullName\` varchar(200) NOT NULL COMMENT 'ชื่อ User ที่แก้ไขข้อมูลล่าสุด' DEFAULT 'System Administrator', \`id\` varchar(36) NOT NULL, \`assign_id\` varchar(255) NOT NULL COMMENT 'Id ของแบบมอบหมายงาน', \`evaluate_id\` varchar(255) NOT NULL COMMENT 'Id แบบประเมิน', \`output_id\` varchar(255) NOT NULL COMMENT 'Id ผลลัพท์', \`assessor_id\` varchar(255) NOT NULL COMMENT 'Id ผู้ประเมิน', \`evaluate_expect_level\` int NOT NULL COMMENT 'ระดับคะแนนผลผลิตของงานที่คาดหวัง' DEFAULT '0', \`output_desc\` text NOT NULL COMMENT 'คำอธิบายผลผลิตของงานที่เกิดขึ้นจริง', \`evaluate_output_level\` int NOT NULL COMMENT 'ระดับคะแนนผลผลิตของงานที่เกิดขึ้นจริง' DEFAULT '0', PRIMARY KEY (\`id\`)) ENGINE=InnoDB`); + await queryRunner.query(`CREATE TABLE \`evaluateAssessor\` (\`createdAt\` datetime(6) NOT NULL COMMENT 'สร้างข้อมูลเมื่อ' DEFAULT CURRENT_TIMESTAMP(6), \`createdUserId\` varchar(40) NOT NULL COMMENT 'User Id ที่สร้างข้อมูล' DEFAULT '00000000-0000-0000-0000-000000000000', \`updatedAt\` datetime(6) NOT NULL COMMENT 'แก้ไขข้อมูลล่าสุดเมื่อ' DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6), \`updateUserId\` varchar(40) NOT NULL COMMENT 'User Id ที่แก้ไขข้อมูล' DEFAULT '00000000-0000-0000-0000-000000000000', \`createdFullName\` varchar(200) NOT NULL COMMENT 'ชื่อ User ที่สร้างข้อมูล' DEFAULT 'System Administrator', \`updateFullName\` varchar(200) NOT NULL COMMENT 'ชื่อ User ที่แก้ไขข้อมูลล่าสุด' DEFAULT 'System Administrator', \`id\` varchar(36) NOT NULL, \`assign_id\` varchar(255) NOT NULL COMMENT 'Id ของแบบมอบหมายงาน', \`director_id\` varchar(255) NOT NULL COMMENT 'Id ผู้ประเมิน', \`no\` varchar(255) NOT NULL COMMENT 'ครั้งที่บันทึก' DEFAULT '1', \`date_start\` date NOT NULL COMMENT 'วันที่เริ่มการบันทึก', \`date_finish\` date NOT NULL COMMENT 'วันที่สิ้นสุดการบันทึก', \`assessor_dated\` date NULL COMMENT 'วันที่ลงนาม', \`knowledge_level\` int NOT NULL COMMENT 'คะแนนความรู้' DEFAULT '0', \`skill_level\` int NOT NULL COMMENT 'คะแนนทักษะ' DEFAULT '0', \`competency_level\` int NOT NULL COMMENT 'คะแนนสมรรถนะ' DEFAULT '0', \`learn_level\` int NOT NULL COMMENT 'คะแนนความสามารถในการเรียนรู้งาน' DEFAULT '0', \`apply_level\` int NOT NULL COMMENT 'คะแนนความสามารถในการปรับใช้ความรู้กับงานในหน้าที่' DEFAULT '0', \`achievement_other_desc\` text NULL COMMENT 'ความสำเร็จของงานที่ได้รับมอบหมายอื่นๆ', \`achievement_other_level\` int NULL COMMENT 'ระดับความสำเร็จของงานที่ได้รับมอบหมายอื่นๆ', \`achievement_strength_desc\` text NOT NULL COMMENT 'จุดเด่น', \`achievement_improve_desc\` text NOT NULL COMMENT 'สิ่งที่ควรปรับปรุง', \`conduct1_level\` int NOT NULL COMMENT 'คะแนนความประพฤติ ให้บริการประชาชนหรือผู้รับบริการด้วยอัธยาศัยดี' DEFAULT '0', \`conduct2_level\` int NOT NULL COMMENT 'คะแนนความประพฤติ มีความรับผิดชอบในการปฏิบัติบัติงาน' DEFAULT '0', \`conduct3_level\` int NOT NULL COMMENT 'คะแนนความประพฤติ ให้บริการประชาชนหรือผู้รับบริการด้วยความรวดเร็ว เอาใจใส่เป็นมาตรฐานเดียวกัน' DEFAULT '0', \`conduct4_level\` int NOT NULL COMMENT 'คะแนนความประพฤติ ตั้งใจปฏิบัติบัติหน้าที่ราชการด้วยความอุตสาหะ ขยันหมั่นเพียร' DEFAULT '0', \`moral1_level\` int NOT NULL COMMENT 'คะแนนความมีจรรยาบรรณ อุทิศตนและเสียสละเวลาในการปฏิบัติบัติงานอย่างเต็มกำลังความสามารถ' DEFAULT '0', \`moral2_level\` int NOT NULL COMMENT 'คะแนนความมีจรรยาบรรณ มีจิตสำนึกที่ดี ปฏิบัติบัติงานด้วยความซื่อสัตย์ สุจริต' DEFAULT '0', \`moral3_level\` int NOT NULL COMMENT 'คะแนนความมีจรรยาบรรณ ยึดมั่นในสถาบันบัพระมหากษัตริย์ และไม่กระทำการใด ๆ อันจะก่อให้เกิดความเสียหายต่อประเทศชาติ' DEFAULT '0', \`discipline1_level\` int NOT NULL COMMENT 'คะแนนการรักษาวินัย มีความรับรัผิดชอบในการรักษาเวลาทำงาน' DEFAULT '0', \`discipline2_level\` int NOT NULL COMMENT 'คะแนนการรักษาวินัย แต่งกายในการปฏิบัติบัติงานได้อย่างเหมาะสมกับการเป็นข้าราชการ' DEFAULT '0', \`discipline3_level\` int NOT NULL COMMENT 'คะแนนการรักษาวินัย ไม่กระทำการใด ๆ อันอาจก่อให้เกิดความเสียหายแก่ชื่อเสียงของหน่วยงาน' DEFAULT '0', \`discipline4_level\` int NOT NULL COMMENT 'คะแนนการรักษาวินัย ไม่กระทำการใด ๆ อันเป็นการเสื่อมเกียรติและศักดิ์ศรีของความเป็นข้าราชการ' DEFAULT '0', \`discipline5_level\` int NOT NULL COMMENT 'คะแนนการรักษาวินัย ปฏิบัติบัติหน้าที่อย่างตรงไปตรงมาโดยยึดหลักจรรยาบรรณวิชาชีพ' DEFAULT '0', \`behavior_other_desc\` text NULL COMMENT 'ความประพฤติอื่นๆ', \`behavior_other_level\` int NULL COMMENT 'ระดับความประพฤติอื่นๆ', \`behavior_strength_desc\` text NOT NULL COMMENT 'จุดเด่นของความประพฤติ', \`behavior_improve_desc\` text NOT NULL COMMENT 'สิ่งที่ควรปรับปรุงของความประพฤติ', \`orientation\` int NOT NULL COMMENT 'คะแนนการพัฒนาตนเอง การปฐมนิเทศ' DEFAULT '0', \`self_learning\` int NOT NULL COMMENT 'คะแนนการพัฒนาตนเอง การเรียนรู้ด้วยตนเอง' DEFAULT '0', \`training_seminar\` int NOT NULL COMMENT 'คะแนนการพัฒนาตนเอง การอบรมสัมนาร่วมกัน' DEFAULT '0', \`other_training\` int NOT NULL COMMENT 'คะแนนการพัฒนาตนเอง การอบรมอื่น ๆ ตามที่หน่วยงานกำหนด' DEFAULT '0', PRIMARY KEY (\`id\`)) ENGINE=InnoDB`); + await queryRunner.query(`CREATE TABLE \`evaluateChairman\` (\`createdAt\` datetime(6) NOT NULL COMMENT 'สร้างข้อมูลเมื่อ' DEFAULT CURRENT_TIMESTAMP(6), \`createdUserId\` varchar(40) NOT NULL COMMENT 'User Id ที่สร้างข้อมูล' DEFAULT '00000000-0000-0000-0000-000000000000', \`updatedAt\` datetime(6) NOT NULL COMMENT 'แก้ไขข้อมูลล่าสุดเมื่อ' DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6), \`updateUserId\` varchar(40) NOT NULL COMMENT 'User Id ที่แก้ไขข้อมูล' DEFAULT '00000000-0000-0000-0000-000000000000', \`createdFullName\` varchar(200) NOT NULL COMMENT 'ชื่อ User ที่สร้างข้อมูล' DEFAULT 'System Administrator', \`updateFullName\` varchar(200) NOT NULL COMMENT 'ชื่อ User ที่แก้ไขข้อมูลล่าสุด' DEFAULT 'System Administrator', \`id\` varchar(36) NOT NULL, \`assign_id\` varchar(255) NOT NULL COMMENT 'Id ของแบบมอบหมายงาน', \`director_id\` varchar(255) NOT NULL COMMENT 'Id ผู้ประเมิน', \`no\` varchar(255) NOT NULL COMMENT 'ครั้งที่ประเมิน' DEFAULT '1', \`date_start\` date NOT NULL COMMENT 'วันที่เริ่มการบันทึก', \`date_finish\` date NOT NULL COMMENT 'วันที่สิ้นสุดการบันทึก', \`chairman_dated\` date NULL COMMENT 'วันที่ลงนาม', \`knowledge_level\` int NOT NULL COMMENT 'คะแนนความสามารถในการเรียนรู้งาน' DEFAULT '0', \`apply_level\` int NOT NULL COMMENT 'คะแนนความสามารถในการปรับใช้ความรู้กับงานในหน้าที่' DEFAULT '0', \`success_level\` int NOT NULL COMMENT 'คะแนนความสำเร็จของงานที่ได้รับมอบหมาย' DEFAULT '0', \`achievement_other_desc\` text NULL COMMENT 'ความสำเร็จของงานที่ได้รับมอบหมายอื่นๆ', \`achievement_other_level\` int NULL COMMENT 'ระดับความสำเร็จของงานที่ได้รับมอบหมายอื่นๆ', \`conduct1_level\` int NOT NULL COMMENT 'คะแนนความประพฤติ ให้บริการประชาชนหรือผู้รับบริการด้วยอัธยาศัยดี' DEFAULT '0', \`conduct2_level\` int NOT NULL COMMENT 'คะแนนความประพฤติ มีความรับผิดชอบในการปฏิบัติบัติงาน' DEFAULT '0', \`conduct3_level\` int NOT NULL COMMENT 'คะแนนความประพฤติ ให้บริการประชาชนหรือผู้รับบริการด้วยความรวดเร็ว เอาใจใส่เป็นมาตรฐานเดียวกัน' DEFAULT '0', \`conduct4_level\` int NOT NULL COMMENT 'คะแนนความประพฤติ ตั้งใจปฏิบัติบัติหน้าที่ราชการด้วยความอุตสาหะ ขยันหมั่นเพียร' DEFAULT '0', \`moral1_level\` int NOT NULL COMMENT 'คะแนนความมีจรรยาบรรณ อุทิศตนและเสียสละเวลาในการปฏิบัติบัติงานอย่างเต็มกำลังความสามารถ' DEFAULT '0', \`moral2_level\` int NOT NULL COMMENT 'คะแนนความมีจรรยาบรรณ มีจิตสำนึกที่ดี ปฏิบัติบัติงานด้วยความซื่อสัตย์ สุจริต' DEFAULT '0', \`moral3_level\` int NOT NULL COMMENT 'คะแนนความมีจรรยาบรรณ ยึดมั่นในสถาบันบัพระมหากษัตริย์ และไม่กระทำการใด ๆ อันจะก่อให้เกิดความเสียหายต่อประเทศชาติ' DEFAULT '0', \`discipline1_level\` int NOT NULL COMMENT 'คะแนนการรักษาวินัย มีความรับรัผิดชอบในการรักษาเวลาทำงาน' DEFAULT '0', \`discipline2_level\` int NOT NULL COMMENT 'คะแนนการรักษาวินัย แต่งกายในการปฏิบัติบัติงานได้อย่างเหมาะสมกับการเป็นข้าราชการ' DEFAULT '0', \`discipline3_level\` int NOT NULL COMMENT 'คะแนนการรักษาวินัย ไม่กระทำการใด ๆ อันอาจก่อให้เกิดความเสียหายแก่ชื่อเสียงของหน่วยงาน' DEFAULT '0', \`discipline4_level\` int NOT NULL COMMENT 'คะแนนการรักษาวินัย ไม่กระทำการใด ๆ อันเป็นการเสื่อมเกียรติและศักดิ์ศรีของความเป็นข้าราชการ' DEFAULT '0', \`discipline5_level\` int NOT NULL COMMENT 'คะแนนการรักษาวินัย ปฏิบัติบัติหน้าที่อย่างตรงไปตรงมาโดยยึดหลักจรรยาบรรณวิชาชีพ' DEFAULT '0', \`behavior_other_desc\` text NULL COMMENT 'ความประพฤติอื่นๆ', \`behavior_other_level\` int NULL COMMENT 'ระดับความประพฤติอื่นๆ', \`develop_orientation_score\` int NOT NULL COMMENT 'คะแนนการพัฒนาตนเอง การปฐมนิเทศ' DEFAULT '0', \`develop_self_learning_score\` int NOT NULL COMMENT 'คะแนนการพัฒนาตนเอง การเรียนรู้ด้วยตนเอง' DEFAULT '0', \`develop_training_seminar_score\` int NOT NULL COMMENT 'คะแนนการพัฒนาตนเอง การอบรมสัมนาร่วมกัน' DEFAULT '0', \`develop_other_training_score\` int NOT NULL COMMENT 'คะแนนการพัฒนาตนเอง การอบรมอื่น ๆ ตามที่หน่วยงานกำหนด' DEFAULT '0', \`develop_orientation_percent\` float NOT NULL COMMENT 'เปอร์เซ็นต์การพัฒนาตนเอง การปฐมนิเทศ' DEFAULT '0', \`develop_self_learning_percent\` float NOT NULL COMMENT 'เปอร์เซ็นต์การพัฒนาตนเอง การเรียนรู้ด้วยตนเอง' DEFAULT '0', \`develop_training_seminar_percent\` float NOT NULL COMMENT 'เปอร์เซ็นต์การพัฒนาตนเอง การอบรมสัมนาร่วมกัน' DEFAULT '0', \`develop_other_training_percent\` float NOT NULL COMMENT 'เปอร์เซ็นต์การพัฒนาตนเอง การอบรมอื่น ๆ ตามที่หน่วยงานกำหนด' DEFAULT '0', \`develop_result\` int NULL COMMENT 'ผลประเมิน 0 = ไม่ผ่าน, 1 = ผ่าน', \`achievement_score\` int NOT NULL COMMENT 'คะแนน ผลสัมฤทธิ์ของการทดลองปฏิบัติบัติหน้าที่ราชการ' DEFAULT '0', \`achievement_score_total\` int NOT NULL COMMENT 'คะแนนเต็ม พฤติกรรม' DEFAULT '0', \`achievement_percent\` float NOT NULL COMMENT 'เปอร์เซ็นต์ผลสัมฤทธิ์' DEFAULT '0', \`achievement_result\` int NULL COMMENT 'ผลประเมินผลสัมฤทธิ์ 0 = ไม่ผ่าน, 1 = ผ่าน', \`behavior_score\` int NOT NULL COMMENT 'คะแนน พฤติกรรม' DEFAULT '0', \`behavior_score_total\` int NOT NULL COMMENT 'ผลคะแนนรวมพฤติกรรม' DEFAULT '0', \`behavior_percent\` float NOT NULL COMMENT 'เปอร์เซ็นต์พฤติกรรม' DEFAULT '0', \`behavior_result\` int NULL COMMENT 'ผลประเมินพฤติกรรม 0 = ไม่ผ่าน, 1 = ผ่าน', \`sum_score\` int NOT NULL COMMENT 'ผลคะแนนรวม' DEFAULT '0', \`sum_percent\` int NOT NULL COMMENT 'เปอร์เซ็นต์ผลรวม' DEFAULT '0', \`evaluate_result\` int NULL COMMENT 'ผลการประเมิน 0 = ไม่ผ่าน, 1 = ผ่าน', \`director1_dated\` date NULL COMMENT 'วันที่ลงนาม', \`director2_dated\` date NULL COMMENT 'วันที่ลงนาม', PRIMARY KEY (\`id\`)) ENGINE=InnoDB`); + await queryRunner.query(`CREATE TABLE \`evaluateResult\` (\`createdAt\` datetime(6) NOT NULL COMMENT 'สร้างข้อมูลเมื่อ' DEFAULT CURRENT_TIMESTAMP(6), \`createdUserId\` varchar(40) NOT NULL COMMENT 'User Id ที่สร้างข้อมูล' DEFAULT '00000000-0000-0000-0000-000000000000', \`updatedAt\` datetime(6) NOT NULL COMMENT 'แก้ไขข้อมูลล่าสุดเมื่อ' DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6), \`updateUserId\` varchar(40) NOT NULL COMMENT 'User Id ที่แก้ไขข้อมูล' DEFAULT '00000000-0000-0000-0000-000000000000', \`createdFullName\` varchar(200) NOT NULL COMMENT 'ชื่อ User ที่สร้างข้อมูล' DEFAULT 'System Administrator', \`updateFullName\` varchar(200) NOT NULL COMMENT 'ชื่อ User ที่แก้ไขข้อมูลล่าสุด' DEFAULT 'System Administrator', \`id\` varchar(36) NOT NULL, \`assign_id\` varchar(255) NOT NULL COMMENT 'Id ของแบบมอบหมายงาน', \`director_id\` varchar(255) NOT NULL COMMENT 'Id ผู้ประเมิน', \`no\` varchar(255) NOT NULL COMMENT 'ครั้งที่ประเมิน' DEFAULT '1', \`date_start\` date NOT NULL COMMENT 'วันที่เริ่มการบันทึก', \`date_finish\` date NOT NULL COMMENT 'วันที่สิ้นสุดการบันทึก', \`develop_complete\` int NULL COMMENT 'ผลประเมิน 0 = ไม่ผ่าน, 1 = ผ่าน', \`pass_result\` int NULL COMMENT 'ผลการทดลองงาน 0 = ไม่ผ่าน, 1 = ผ่าน', \`expand_month\` int NULL COMMENT 'จำนวนเดือนที่ขยาย', \`reson\` longtext NULL COMMENT 'เหตุผล', \`chairman_dated\` date NULL COMMENT 'วันที่ลงนาม', \`director1_dated\` date NULL COMMENT 'วันที่ลงนาม', \`director2_dated\` date NULL COMMENT 'วันที่ลงนาม', PRIMARY KEY (\`id\`)) ENGINE=InnoDB`); + await queryRunner.query(`CREATE TABLE \`personal\` (\`createdAt\` datetime(6) NOT NULL COMMENT 'สร้างข้อมูลเมื่อ' DEFAULT CURRENT_TIMESTAMP(6), \`createdUserId\` varchar(40) NOT NULL COMMENT 'User Id ที่สร้างข้อมูล' DEFAULT '00000000-0000-0000-0000-000000000000', \`updatedAt\` datetime(6) NOT NULL COMMENT 'แก้ไขข้อมูลล่าสุดเมื่อ' DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6), \`updateUserId\` varchar(40) NOT NULL COMMENT 'User Id ที่แก้ไขข้อมูล' DEFAULT '00000000-0000-0000-0000-000000000000', \`createdFullName\` varchar(200) NOT NULL COMMENT 'ชื่อ User ที่สร้างข้อมูล' DEFAULT 'System Administrator', \`updateFullName\` varchar(200) NOT NULL COMMENT 'ชื่อ User ที่แก้ไขข้อมูลล่าสุด' DEFAULT 'System Administrator', \`personal_id\` varchar(36) NOT NULL, \`order_number\` varchar(255) NULL COMMENT 'เลขที่คำสั่งบรรจุ', \`probation_status\` int NOT NULL COMMENT '1 อยู่ระหว่างการทดลองปฏิบัติหน้าที่ราชการ, 2 พ้นการทดลองปฏิบัติหน้าที่ราชการ, 3 ไม่พ้นการทดลองปฏิบัติหน้าที่ราชการ, 4 ยุติการทดลองปฏิบัติหน้าที่ราชการเนื่องจากเปลี่ยนตำแหน่ง, 5 ยุติการทดลองปฏิบัติหน้าที่ราชการเนื่องจากลาออก, 6 ยุติการทดลองปฏิบัติหน้าที่ราชการเนื่องจากถึงแก่กรรม, 7 ขยายระยะเวลาทดลองปฏิบัติหน้าที่ราชการ, 8 ดึงรายชื่อไปออกคำสั่งแล้ว' DEFAULT '1', \`active\` int NOT NULL COMMENT 'สถานะการใช้งาน 1 คือใช้งานปกติ, 0 คือไม่ใช้งาน' DEFAULT '1', \`prefixName\` varchar(255) NULL COMMENT 'คำนำหน้าชื่อ', \`firstName\` varchar(255) NOT NULL COMMENT 'ชื่อ', \`lastName\` varchar(255) NOT NULL COMMENT 'นามสกุล', \`isProbation\` tinyint NOT NULL COMMENT 'สถานะการทดลองงาน 1 คืออยู่ระหว่างการทดลองงาน, 0 คือไม่อยู่ระหว่างการทดลองงาน' DEFAULT '0', \`positionName\` varchar(255) NULL COMMENT 'ตำแหน่งในสายงาน', \`posNo\` varchar(255) NULL COMMENT 'เลขที่ตำแหน่ง', \`positionLevelName\` varchar(255) NULL COMMENT 'ระดับตำแหน่ง', \`positionLineName\` varchar(255) NULL COMMENT 'ด้าน/สาขา', \`positionTypeName\` varchar(255) NULL COMMENT 'ประเภทตำแหน่ง', \`orgRootName\` varchar(255) NULL COMMENT 'หน่วยงาน', \`organization\` varchar(255) NULL COMMENT 'สังกัด', PRIMARY KEY (\`personal_id\`)) ENGINE=InnoDB`); + await queryRunner.query(`CREATE TABLE \`assign\` (\`createdAt\` datetime(6) NOT NULL COMMENT 'สร้างข้อมูลเมื่อ' DEFAULT CURRENT_TIMESTAMP(6), \`createdUserId\` varchar(40) NOT NULL COMMENT 'User Id ที่สร้างข้อมูล' DEFAULT '00000000-0000-0000-0000-000000000000', \`updatedAt\` datetime(6) NOT NULL COMMENT 'แก้ไขข้อมูลล่าสุดเมื่อ' DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6), \`updateUserId\` varchar(40) NOT NULL COMMENT 'User Id ที่แก้ไขข้อมูล' DEFAULT '00000000-0000-0000-0000-000000000000', \`createdFullName\` varchar(200) NOT NULL COMMENT 'ชื่อ User ที่สร้างข้อมูล' DEFAULT 'System Administrator', \`updateFullName\` varchar(200) NOT NULL COMMENT 'ชื่อ User ที่แก้ไขข้อมูลล่าสุด' DEFAULT 'System Administrator', \`id\` varchar(36) NOT NULL, \`personal_id\` varchar(255) NOT NULL COMMENT 'Id ของบุคคล', \`round_no\` int NOT NULL COMMENT 'ครั้งที่แบบมอบหมายงาน' DEFAULT '0', \`date_start\` date NOT NULL COMMENT 'วันที่เริ่มทดลองงาน', \`date_finish\` date NOT NULL COMMENT 'วันที่สิ้นสุดทดลองงาน', \`other4_desc\` longtext NULL COMMENT 'อื่นๆ(3.5)', \`other5_no1_desc\` longtext NULL COMMENT 'อื่นๆ(5.2)', \`experimenter_dated\` date NULL COMMENT 'ผู้ทดลองปฏิบัติหน้าที่ราชการลงวันที่', \`active\` int NOT NULL COMMENT 'สถานะการใช้งาน 1 คือใช้งานปกติ, 0 คือไม่ใช้งาน' DEFAULT '1', PRIMARY KEY (\`id\`)) ENGINE=InnoDB`); + await queryRunner.query(`CREATE TABLE \`survey\` (\`createdAt\` datetime(6) NOT NULL COMMENT 'สร้างข้อมูลเมื่อ' DEFAULT CURRENT_TIMESTAMP(6), \`createdUserId\` varchar(40) NOT NULL COMMENT 'User Id ที่สร้างข้อมูล' DEFAULT '00000000-0000-0000-0000-000000000000', \`updatedAt\` datetime(6) NOT NULL COMMENT 'แก้ไขข้อมูลล่าสุดเมื่อ' DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6), \`updateUserId\` varchar(40) NOT NULL COMMENT 'User Id ที่แก้ไขข้อมูล' DEFAULT '00000000-0000-0000-0000-000000000000', \`createdFullName\` varchar(200) NOT NULL COMMENT 'ชื่อ User ที่สร้างข้อมูล' DEFAULT 'System Administrator', \`updateFullName\` varchar(200) NOT NULL COMMENT 'ชื่อ User ที่แก้ไขข้อมูลล่าสุด' DEFAULT 'System Administrator', \`personal_id\` varchar(255) NOT NULL COMMENT 'Id ของบุคคล', \`assign_id\` varchar(255) NOT NULL COMMENT 'Id ของแบบมอบหมายงาน', \`answer1\` longtext NULL COMMENT 'ความคิดเห็นข้อที่ 1', \`answer2\` longtext NULL COMMENT 'ความคิดเห็นข้อที่ 2', \`answer3\` longtext NULL COMMENT 'ความคิดเห็นข้อที่ 3', PRIMARY KEY (\`personal_id\`, \`assign_id\`)) ENGINE=InnoDB`); + await queryRunner.query(`CREATE TABLE \`knowlegeSkillMaps\` (\`createdAt\` datetime(6) NOT NULL COMMENT 'สร้างข้อมูลเมื่อ' DEFAULT CURRENT_TIMESTAMP(6), \`createdUserId\` varchar(40) NOT NULL COMMENT 'User Id ที่สร้างข้อมูล' DEFAULT '00000000-0000-0000-0000-000000000000', \`updatedAt\` datetime(6) NOT NULL COMMENT 'แก้ไขข้อมูลล่าสุดเมื่อ' DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6), \`updateUserId\` varchar(40) NOT NULL COMMENT 'User Id ที่แก้ไขข้อมูล' DEFAULT '00000000-0000-0000-0000-000000000000', \`createdFullName\` varchar(200) NOT NULL COMMENT 'ชื่อ User ที่สร้างข้อมูล' DEFAULT 'System Administrator', \`updateFullName\` varchar(200) NOT NULL COMMENT 'ชื่อ User ที่แก้ไขข้อมูลล่าสุด' DEFAULT 'System Administrator', \`no\` int NOT NULL COMMENT 'runing number', \`positionLevelName\` varchar(255) NOT NULL COMMENT 'ระดับตำแหน่ง', \`positionName\` varchar(255) NOT NULL COMMENT 'ตำแหน่งทางสายงาน', \`type\` enum ('academic', 'general') NOT NULL COMMENT 'academic คือทักษะด้านวิชาการ / general คือทักษะทั่วไป', \`knowlage_performance_level\` int NOT NULL COMMENT 'ระดับความสามารถที่คาดหวัง' DEFAULT '1', \`knowlage_law_level\` int NOT NULL COMMENT 'ระดับความรู้ด้านกฎหมายที่คาดหวัง' DEFAULT '1', \`skill_computer_level\` int NOT NULL COMMENT 'ทักษะด้านคอมพิวเตอร์ที่คาดหวัง' DEFAULT '1', \`skill_english_level\` int NOT NULL COMMENT 'ทักษะด้านภาษาอังกฤษที่คาดหวัง' DEFAULT '1', \`skill_information_level\` int NOT NULL COMMENT 'ทักษะด้านการจัดการข้อมูลที่คาดหวัง' DEFAULT '1', \`skill_resourse_level\` int NOT NULL COMMENT 'ทักษะด้านการบริหารจัดการทรัพยากรที่คาดหวัง' DEFAULT '1', \`active\` int NOT NULL COMMENT 'สถานะการใช้งาน 1 คือใช้งานปกติ, 0 คือไม่ใช้งาน' DEFAULT '1', PRIMARY KEY (\`no\`, \`positionLevelName\`, \`positionName\`)) ENGINE=InnoDB`); + await queryRunner.query(`ALTER TABLE \`evaluateCommander\` ADD CONSTRAINT \`FK_3ac205b79476e5b3302d4522807\` FOREIGN KEY (\`assign_id\`) REFERENCES \`assign\`(\`id\`) ON DELETE NO ACTION ON UPDATE NO ACTION`); + await queryRunner.query(`ALTER TABLE \`assignCompetencyGroup\` ADD CONSTRAINT \`FK_0c8e3ba9da6e17761efac0ed04c\` FOREIGN KEY (\`assign_id\`) REFERENCES \`assign\`(\`id\`) ON DELETE NO ACTION ON UPDATE NO ACTION`); + await queryRunner.query(`ALTER TABLE \`assignCompetency\` ADD CONSTRAINT \`FK_e984fcb547dec4c8216c4d626b9\` FOREIGN KEY (\`assign_id\`) REFERENCES \`assign\`(\`id\`) ON DELETE NO ACTION ON UPDATE NO ACTION`); + await queryRunner.query(`ALTER TABLE \`assignDirector\` ADD CONSTRAINT \`FK_0c1cd15998b0e35787fabb403fd\` FOREIGN KEY (\`assign_id\`) REFERENCES \`assign\`(\`id\`) ON DELETE NO ACTION ON UPDATE NO ACTION`); + await queryRunner.query(`ALTER TABLE \`assignJob\` ADD CONSTRAINT \`FK_c9a08054726e5865d11f6538976\` FOREIGN KEY (\`assign_id\`) REFERENCES \`assign\`(\`id\`) ON DELETE NO ACTION ON UPDATE NO ACTION`); + await queryRunner.query(`ALTER TABLE \`assignKnowledge\` ADD CONSTRAINT \`FK_b7a8d0d819494f133ac3c4076cd\` FOREIGN KEY (\`assign_id\`) REFERENCES \`assign\`(\`id\`) ON DELETE NO ACTION ON UPDATE NO ACTION`); + await queryRunner.query(`ALTER TABLE \`assignKnowledge\` ADD CONSTRAINT \`FK_212ee6dca05d47da4e548fa6308\` FOREIGN KEY (\`knowledge_id\`) REFERENCES \`knowledge\`(\`id\`) ON DELETE NO ACTION ON UPDATE NO ACTION`); + await queryRunner.query(`ALTER TABLE \`assignLaw\` ADD CONSTRAINT \`FK_f8640b2734b750359174fea4f21\` FOREIGN KEY (\`assign_id\`) REFERENCES \`assign\`(\`id\`) ON DELETE NO ACTION ON UPDATE NO ACTION`); + await queryRunner.query(`ALTER TABLE \`assignOutput\` ADD CONSTRAINT \`FK_bf2d4a27f12b01c50b52cc1007e\` FOREIGN KEY (\`assign_id\`) REFERENCES \`assign\`(\`id\`) ON DELETE NO ACTION ON UPDATE NO ACTION`); + await queryRunner.query(`ALTER TABLE \`assignSkill\` ADD CONSTRAINT \`FK_e850165abf58efdc90572d5aac0\` FOREIGN KEY (\`assign_id\`) REFERENCES \`assign\`(\`id\`) ON DELETE NO ACTION ON UPDATE NO ACTION`); + await queryRunner.query(`ALTER TABLE \`assignSkill\` ADD CONSTRAINT \`FK_51d79136434ce1702a29461916f\` FOREIGN KEY (\`skill_id\`) REFERENCES \`skills\`(\`id\`) ON DELETE NO ACTION ON UPDATE NO ACTION`); + await queryRunner.query(`ALTER TABLE \`evaluateAchievement\` ADD CONSTRAINT \`FK_609c97e1a6f8367971cc99b78a9\` FOREIGN KEY (\`assign_id\`) REFERENCES \`assign\`(\`id\`) ON DELETE NO ACTION ON UPDATE NO ACTION`); + await queryRunner.query(`ALTER TABLE \`evaluateAssessor\` ADD CONSTRAINT \`FK_598ed9e4cd02e2b1b5e195a648c\` FOREIGN KEY (\`assign_id\`) REFERENCES \`assign\`(\`id\`) ON DELETE NO ACTION ON UPDATE NO ACTION`); + await queryRunner.query(`ALTER TABLE \`evaluateChairman\` ADD CONSTRAINT \`FK_1482472a351168a240d4e8e66c0\` FOREIGN KEY (\`assign_id\`) REFERENCES \`assign\`(\`id\`) ON DELETE NO ACTION ON UPDATE NO ACTION`); + await queryRunner.query(`ALTER TABLE \`evaluateResult\` ADD CONSTRAINT \`FK_789dffc739a053124f1ed30661a\` FOREIGN KEY (\`assign_id\`) REFERENCES \`assign\`(\`id\`) ON DELETE NO ACTION ON UPDATE NO ACTION`); + await queryRunner.query(`ALTER TABLE \`assign\` ADD CONSTRAINT \`FK_033697052dc4180c78f402a2bb8\` FOREIGN KEY (\`personal_id\`) REFERENCES \`personal\`(\`personal_id\`) ON DELETE NO ACTION ON UPDATE NO ACTION`); + await queryRunner.query(`ALTER TABLE \`survey\` ADD CONSTRAINT \`FK_c6c406d5f35f8a7c38d947d0a64\` FOREIGN KEY (\`assign_id\`) REFERENCES \`assign\`(\`id\`) ON DELETE NO ACTION ON UPDATE NO ACTION`); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE \`survey\` DROP FOREIGN KEY \`FK_c6c406d5f35f8a7c38d947d0a64\``); + await queryRunner.query(`ALTER TABLE \`assign\` DROP FOREIGN KEY \`FK_033697052dc4180c78f402a2bb8\``); + await queryRunner.query(`ALTER TABLE \`evaluateResult\` DROP FOREIGN KEY \`FK_789dffc739a053124f1ed30661a\``); + await queryRunner.query(`ALTER TABLE \`evaluateChairman\` DROP FOREIGN KEY \`FK_1482472a351168a240d4e8e66c0\``); + await queryRunner.query(`ALTER TABLE \`evaluateAssessor\` DROP FOREIGN KEY \`FK_598ed9e4cd02e2b1b5e195a648c\``); + await queryRunner.query(`ALTER TABLE \`evaluateAchievement\` DROP FOREIGN KEY \`FK_609c97e1a6f8367971cc99b78a9\``); + await queryRunner.query(`ALTER TABLE \`assignSkill\` DROP FOREIGN KEY \`FK_51d79136434ce1702a29461916f\``); + await queryRunner.query(`ALTER TABLE \`assignSkill\` DROP FOREIGN KEY \`FK_e850165abf58efdc90572d5aac0\``); + await queryRunner.query(`ALTER TABLE \`assignOutput\` DROP FOREIGN KEY \`FK_bf2d4a27f12b01c50b52cc1007e\``); + await queryRunner.query(`ALTER TABLE \`assignLaw\` DROP FOREIGN KEY \`FK_f8640b2734b750359174fea4f21\``); + await queryRunner.query(`ALTER TABLE \`assignKnowledge\` DROP FOREIGN KEY \`FK_212ee6dca05d47da4e548fa6308\``); + await queryRunner.query(`ALTER TABLE \`assignKnowledge\` DROP FOREIGN KEY \`FK_b7a8d0d819494f133ac3c4076cd\``); + await queryRunner.query(`ALTER TABLE \`assignJob\` DROP FOREIGN KEY \`FK_c9a08054726e5865d11f6538976\``); + await queryRunner.query(`ALTER TABLE \`assignDirector\` DROP FOREIGN KEY \`FK_0c1cd15998b0e35787fabb403fd\``); + await queryRunner.query(`ALTER TABLE \`assignCompetency\` DROP FOREIGN KEY \`FK_e984fcb547dec4c8216c4d626b9\``); + await queryRunner.query(`ALTER TABLE \`assignCompetencyGroup\` DROP FOREIGN KEY \`FK_0c8e3ba9da6e17761efac0ed04c\``); + await queryRunner.query(`ALTER TABLE \`evaluateCommander\` DROP FOREIGN KEY \`FK_3ac205b79476e5b3302d4522807\``); + await queryRunner.query(`DROP TABLE \`knowlegeSkillMaps\``); + await queryRunner.query(`DROP TABLE \`survey\``); + await queryRunner.query(`DROP TABLE \`assign\``); + await queryRunner.query(`DROP TABLE \`personal\``); + await queryRunner.query(`DROP TABLE \`evaluateResult\``); + await queryRunner.query(`DROP TABLE \`evaluateChairman\``); + await queryRunner.query(`DROP TABLE \`evaluateAssessor\``); + await queryRunner.query(`DROP TABLE \`evaluateAchievement\``); + await queryRunner.query(`DROP TABLE \`assignSkill\``); + await queryRunner.query(`DROP TABLE \`skills\``); + await queryRunner.query(`DROP TABLE \`assignOutput\``); + await queryRunner.query(`DROP TABLE \`assignLaw\``); + await queryRunner.query(`DROP TABLE \`laws\``); + await queryRunner.query(`DROP TABLE \`assignKnowledge\``); + await queryRunner.query(`DROP TABLE \`knowledge\``); + await queryRunner.query(`DROP TABLE \`assignJob\``); + await queryRunner.query(`DROP TABLE \`assignDirector\``); + await queryRunner.query(`DROP TABLE \`assignCompetency\``); + await queryRunner.query(`DROP TABLE \`assignCompetencyGroup\``); + await queryRunner.query(`DROP TABLE \`evaluateCommander\``); + } + +} diff --git a/static/index.html b/static/index.html new file mode 100644 index 0000000..6d01669 --- /dev/null +++ b/static/index.html @@ -0,0 +1,266 @@ + + + + + + + +
+ + + + + + + + + + + + + + + + + + + +
+ +

Result

+

+
+    

Events

+

+
+    
+  
+
diff --git a/static/keycloak.js b/static/keycloak.js
new file mode 100644
index 0000000..8fd0ccf
--- /dev/null
+++ b/static/keycloak.js
@@ -0,0 +1,2952 @@
+/*
+ * 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.
+ */
+
+(function (root, factory) {
+  if (typeof exports === "object") {
+    if (typeof module === "object") {
+      module.exports = factory(require("js-sha256"), require("base64-js"));
+    } else {
+      exports["keycloak"] = factory(require("js-sha256"), require("base64-js"));
+    }
+  } else {
+    /**
+     * [js-sha256]{@link https://github.com/emn178/js-sha256}
+     *
+     * @version 0.9.0
+     * @author Chen, Yi-Cyuan [emn178@gmail.com]
+     * @copyright Chen, Yi-Cyuan 2014-2017
+     * @license MIT
+     */
+    !(function () {
+      "use strict";
+      function t(t, i) {
+        i
+          ? ((d[0] =
+              d[16] =
+              d[1] =
+              d[2] =
+              d[3] =
+              d[4] =
+              d[5] =
+              d[6] =
+              d[7] =
+              d[8] =
+              d[9] =
+              d[10] =
+              d[11] =
+              d[12] =
+              d[13] =
+              d[14] =
+              d[15] =
+                0),
+            (this.blocks = d))
+          : (this.blocks = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]),
+          t
+            ? ((this.h0 = 3238371032),
+              (this.h1 = 914150663),
+              (this.h2 = 812702999),
+              (this.h3 = 4144912697),
+              (this.h4 = 4290775857),
+              (this.h5 = 1750603025),
+              (this.h6 = 1694076839),
+              (this.h7 = 3204075428))
+            : ((this.h0 = 1779033703),
+              (this.h1 = 3144134277),
+              (this.h2 = 1013904242),
+              (this.h3 = 2773480762),
+              (this.h4 = 1359893119),
+              (this.h5 = 2600822924),
+              (this.h6 = 528734635),
+              (this.h7 = 1541459225)),
+          (this.block = this.start = this.bytes = this.hBytes = 0),
+          (this.finalized = this.hashed = !1),
+          (this.first = !0),
+          (this.is224 = t);
+      }
+      function i(i, r, s) {
+        var e,
+          n = typeof i;
+        if ("string" === n) {
+          var o,
+            a = [],
+            u = i.length,
+            c = 0;
+          for (e = 0; e < u; ++e)
+            (o = i.charCodeAt(e)) < 128
+              ? (a[c++] = o)
+              : o < 2048
+              ? ((a[c++] = 192 | (o >> 6)), (a[c++] = 128 | (63 & o)))
+              : o < 55296 || o >= 57344
+              ? ((a[c++] = 224 | (o >> 12)),
+                (a[c++] = 128 | ((o >> 6) & 63)),
+                (a[c++] = 128 | (63 & o)))
+              : ((o =
+                  65536 + (((1023 & o) << 10) | (1023 & i.charCodeAt(++e)))),
+                (a[c++] = 240 | (o >> 18)),
+                (a[c++] = 128 | ((o >> 12) & 63)),
+                (a[c++] = 128 | ((o >> 6) & 63)),
+                (a[c++] = 128 | (63 & o)));
+          i = a;
+        } else {
+          if ("object" !== n) throw new Error(h);
+          if (null === i) throw new Error(h);
+          if (f && i.constructor === ArrayBuffer) i = new Uint8Array(i);
+          else if (!(Array.isArray(i) || (f && ArrayBuffer.isView(i))))
+            throw new Error(h);
+        }
+        i.length > 64 && (i = new t(r, !0).update(i).array());
+        var y = [],
+          p = [];
+        for (e = 0; e < 64; ++e) {
+          var l = i[e] || 0;
+          (y[e] = 92 ^ l), (p[e] = 54 ^ l);
+        }
+        t.call(this, r, s),
+          this.update(p),
+          (this.oKeyPad = y),
+          (this.inner = !0),
+          (this.sharedMemory = s);
+      }
+      var h = "input is invalid type",
+        r = "object" == typeof window,
+        s = r ? window : {};
+      s.JS_SHA256_NO_WINDOW && (r = !1);
+      var e = !r && "object" == typeof self,
+        n =
+          !s.JS_SHA256_NO_NODE_JS &&
+          "object" == typeof process &&
+          process.versions &&
+          process.versions.node;
+      n ? (s = global) : e && (s = self);
+      var o =
+          !s.JS_SHA256_NO_COMMON_JS &&
+          "object" == typeof module &&
+          module.exports,
+        a = "function" == typeof define && define.amd,
+        f = !s.JS_SHA256_NO_ARRAY_BUFFER && "undefined" != typeof ArrayBuffer,
+        u = "0123456789abcdef".split(""),
+        c = [-2147483648, 8388608, 32768, 128],
+        y = [24, 16, 8, 0],
+        p = [
+          1116352408, 1899447441, 3049323471, 3921009573, 961987163, 1508970993,
+          2453635748, 2870763221, 3624381080, 310598401, 607225278, 1426881987,
+          1925078388, 2162078206, 2614888103, 3248222580, 3835390401,
+          4022224774, 264347078, 604807628, 770255983, 1249150122, 1555081692,
+          1996064986, 2554220882, 2821834349, 2952996808, 3210313671,
+          3336571891, 3584528711, 113926993, 338241895, 666307205, 773529912,
+          1294757372, 1396182291, 1695183700, 1986661051, 2177026350,
+          2456956037, 2730485921, 2820302411, 3259730800, 3345764771,
+          3516065817, 3600352804, 4094571909, 275423344, 430227734, 506948616,
+          659060556, 883997877, 958139571, 1322822218, 1537002063, 1747873779,
+          1955562222, 2024104815, 2227730452, 2361852424, 2428436474,
+          2756734187, 3204031479, 3329325298,
+        ],
+        l = ["hex", "array", "digest", "arrayBuffer"],
+        d = [];
+      (!s.JS_SHA256_NO_NODE_JS && Array.isArray) ||
+        (Array.isArray = function (t) {
+          return "[object Array]" === Object.prototype.toString.call(t);
+        }),
+        !f ||
+          (!s.JS_SHA256_NO_ARRAY_BUFFER_IS_VIEW && ArrayBuffer.isView) ||
+          (ArrayBuffer.isView = function (t) {
+            return (
+              "object" == typeof t &&
+              t.buffer &&
+              t.buffer.constructor === ArrayBuffer
+            );
+          });
+      var A = function (i, h) {
+          return function (r) {
+            return new t(h, !0).update(r)[i]();
+          };
+        },
+        w = function (i) {
+          var h = A("hex", i);
+          n && (h = b(h, i)),
+            (h.create = function () {
+              return new t(i);
+            }),
+            (h.update = function (t) {
+              return h.create().update(t);
+            });
+          for (var r = 0; r < l.length; ++r) {
+            var s = l[r];
+            h[s] = A(s, i);
+          }
+          return h;
+        },
+        b = function (t, i) {
+          var r = eval("require('crypto')"),
+            s = eval("require('buffer').Buffer"),
+            e = i ? "sha224" : "sha256",
+            n = function (i) {
+              if ("string" == typeof i)
+                return r.createHash(e).update(i, "utf8").digest("hex");
+              if (null === i || void 0 === i) throw new Error(h);
+              return (
+                i.constructor === ArrayBuffer && (i = new Uint8Array(i)),
+                Array.isArray(i) || ArrayBuffer.isView(i) || i.constructor === s
+                  ? r.createHash(e).update(new s(i)).digest("hex")
+                  : t(i)
+              );
+            };
+          return n;
+        },
+        v = function (t, h) {
+          return function (r, s) {
+            return new i(r, h, !0).update(s)[t]();
+          };
+        },
+        _ = function (t) {
+          var h = v("hex", t);
+          (h.create = function (h) {
+            return new i(h, t);
+          }),
+            (h.update = function (t, i) {
+              return h.create(t).update(i);
+            });
+          for (var r = 0; r < l.length; ++r) {
+            var s = l[r];
+            h[s] = v(s, t);
+          }
+          return h;
+        };
+      (t.prototype.update = function (t) {
+        if (!this.finalized) {
+          var i,
+            r = typeof t;
+          if ("string" !== r) {
+            if ("object" !== r) throw new Error(h);
+            if (null === t) throw new Error(h);
+            if (f && t.constructor === ArrayBuffer) t = new Uint8Array(t);
+            else if (!(Array.isArray(t) || (f && ArrayBuffer.isView(t))))
+              throw new Error(h);
+            i = !0;
+          }
+          for (var s, e, n = 0, o = t.length, a = this.blocks; n < o; ) {
+            if (
+              (this.hashed &&
+                ((this.hashed = !1),
+                (a[0] = this.block),
+                (a[16] =
+                  a[1] =
+                  a[2] =
+                  a[3] =
+                  a[4] =
+                  a[5] =
+                  a[6] =
+                  a[7] =
+                  a[8] =
+                  a[9] =
+                  a[10] =
+                  a[11] =
+                  a[12] =
+                  a[13] =
+                  a[14] =
+                  a[15] =
+                    0)),
+              i)
+            )
+              for (e = this.start; n < o && e < 64; ++n)
+                a[e >> 2] |= t[n] << y[3 & e++];
+            else
+              for (e = this.start; n < o && e < 64; ++n)
+                (s = t.charCodeAt(n)) < 128
+                  ? (a[e >> 2] |= s << y[3 & e++])
+                  : s < 2048
+                  ? ((a[e >> 2] |= (192 | (s >> 6)) << y[3 & e++]),
+                    (a[e >> 2] |= (128 | (63 & s)) << y[3 & e++]))
+                  : s < 55296 || s >= 57344
+                  ? ((a[e >> 2] |= (224 | (s >> 12)) << y[3 & e++]),
+                    (a[e >> 2] |= (128 | ((s >> 6) & 63)) << y[3 & e++]),
+                    (a[e >> 2] |= (128 | (63 & s)) << y[3 & e++]))
+                  : ((s =
+                      65536 +
+                      (((1023 & s) << 10) | (1023 & t.charCodeAt(++n)))),
+                    (a[e >> 2] |= (240 | (s >> 18)) << y[3 & e++]),
+                    (a[e >> 2] |= (128 | ((s >> 12) & 63)) << y[3 & e++]),
+                    (a[e >> 2] |= (128 | ((s >> 6) & 63)) << y[3 & e++]),
+                    (a[e >> 2] |= (128 | (63 & s)) << y[3 & e++]));
+            (this.lastByteIndex = e),
+              (this.bytes += e - this.start),
+              e >= 64
+                ? ((this.block = a[16]),
+                  (this.start = e - 64),
+                  this.hash(),
+                  (this.hashed = !0))
+                : (this.start = e);
+          }
+          return (
+            this.bytes > 4294967295 &&
+              ((this.hBytes += (this.bytes / 4294967296) << 0),
+              (this.bytes = this.bytes % 4294967296)),
+            this
+          );
+        }
+      }),
+        (t.prototype.finalize = function () {
+          if (!this.finalized) {
+            this.finalized = !0;
+            var t = this.blocks,
+              i = this.lastByteIndex;
+            (t[16] = this.block),
+              (t[i >> 2] |= c[3 & i]),
+              (this.block = t[16]),
+              i >= 56 &&
+                (this.hashed || this.hash(),
+                (t[0] = this.block),
+                (t[16] =
+                  t[1] =
+                  t[2] =
+                  t[3] =
+                  t[4] =
+                  t[5] =
+                  t[6] =
+                  t[7] =
+                  t[8] =
+                  t[9] =
+                  t[10] =
+                  t[11] =
+                  t[12] =
+                  t[13] =
+                  t[14] =
+                  t[15] =
+                    0)),
+              (t[14] = (this.hBytes << 3) | (this.bytes >>> 29)),
+              (t[15] = this.bytes << 3),
+              this.hash();
+          }
+        }),
+        (t.prototype.hash = function () {
+          var t,
+            i,
+            h,
+            r,
+            s,
+            e,
+            n,
+            o,
+            a,
+            f = this.h0,
+            u = this.h1,
+            c = this.h2,
+            y = this.h3,
+            l = this.h4,
+            d = this.h5,
+            A = this.h6,
+            w = this.h7,
+            b = this.blocks;
+          for (t = 16; t < 64; ++t)
+            (i =
+              (((s = b[t - 15]) >>> 7) | (s << 25)) ^
+              ((s >>> 18) | (s << 14)) ^
+              (s >>> 3)),
+              (h =
+                (((s = b[t - 2]) >>> 17) | (s << 15)) ^
+                ((s >>> 19) | (s << 13)) ^
+                (s >>> 10)),
+              (b[t] = (b[t - 16] + i + b[t - 7] + h) << 0);
+          for (a = u & c, t = 0; t < 64; t += 4)
+            this.first
+              ? (this.is224
+                  ? ((e = 300032),
+                    (w = ((s = b[0] - 1413257819) - 150054599) << 0),
+                    (y = (s + 24177077) << 0))
+                  : ((e = 704751109),
+                    (w = ((s = b[0] - 210244248) - 1521486534) << 0),
+                    (y = (s + 143694565) << 0)),
+                (this.first = !1))
+              : ((i =
+                  ((f >>> 2) | (f << 30)) ^
+                  ((f >>> 13) | (f << 19)) ^
+                  ((f >>> 22) | (f << 10))),
+                (r = (e = f & u) ^ (f & c) ^ a),
+                (w =
+                  (y +
+                    (s =
+                      w +
+                      (h =
+                        ((l >>> 6) | (l << 26)) ^
+                        ((l >>> 11) | (l << 21)) ^
+                        ((l >>> 25) | (l << 7))) +
+                      ((l & d) ^ (~l & A)) +
+                      p[t] +
+                      b[t])) <<
+                  0),
+                (y = (s + (i + r)) << 0)),
+              (i =
+                ((y >>> 2) | (y << 30)) ^
+                ((y >>> 13) | (y << 19)) ^
+                ((y >>> 22) | (y << 10))),
+              (r = (n = y & f) ^ (y & u) ^ e),
+              (A =
+                (c +
+                  (s =
+                    A +
+                    (h =
+                      ((w >>> 6) | (w << 26)) ^
+                      ((w >>> 11) | (w << 21)) ^
+                      ((w >>> 25) | (w << 7))) +
+                    ((w & l) ^ (~w & d)) +
+                    p[t + 1] +
+                    b[t + 1])) <<
+                0),
+              (i =
+                (((c = (s + (i + r)) << 0) >>> 2) | (c << 30)) ^
+                ((c >>> 13) | (c << 19)) ^
+                ((c >>> 22) | (c << 10))),
+              (r = (o = c & y) ^ (c & f) ^ n),
+              (d =
+                (u +
+                  (s =
+                    d +
+                    (h =
+                      ((A >>> 6) | (A << 26)) ^
+                      ((A >>> 11) | (A << 21)) ^
+                      ((A >>> 25) | (A << 7))) +
+                    ((A & w) ^ (~A & l)) +
+                    p[t + 2] +
+                    b[t + 2])) <<
+                0),
+              (i =
+                (((u = (s + (i + r)) << 0) >>> 2) | (u << 30)) ^
+                ((u >>> 13) | (u << 19)) ^
+                ((u >>> 22) | (u << 10))),
+              (r = (a = u & c) ^ (u & y) ^ o),
+              (l =
+                (f +
+                  (s =
+                    l +
+                    (h =
+                      ((d >>> 6) | (d << 26)) ^
+                      ((d >>> 11) | (d << 21)) ^
+                      ((d >>> 25) | (d << 7))) +
+                    ((d & A) ^ (~d & w)) +
+                    p[t + 3] +
+                    b[t + 3])) <<
+                0),
+              (f = (s + (i + r)) << 0);
+          (this.h0 = (this.h0 + f) << 0),
+            (this.h1 = (this.h1 + u) << 0),
+            (this.h2 = (this.h2 + c) << 0),
+            (this.h3 = (this.h3 + y) << 0),
+            (this.h4 = (this.h4 + l) << 0),
+            (this.h5 = (this.h5 + d) << 0),
+            (this.h6 = (this.h6 + A) << 0),
+            (this.h7 = (this.h7 + w) << 0);
+        }),
+        (t.prototype.hex = function () {
+          this.finalize();
+          var t = this.h0,
+            i = this.h1,
+            h = this.h2,
+            r = this.h3,
+            s = this.h4,
+            e = this.h5,
+            n = this.h6,
+            o = this.h7,
+            a =
+              u[(t >> 28) & 15] +
+              u[(t >> 24) & 15] +
+              u[(t >> 20) & 15] +
+              u[(t >> 16) & 15] +
+              u[(t >> 12) & 15] +
+              u[(t >> 8) & 15] +
+              u[(t >> 4) & 15] +
+              u[15 & t] +
+              u[(i >> 28) & 15] +
+              u[(i >> 24) & 15] +
+              u[(i >> 20) & 15] +
+              u[(i >> 16) & 15] +
+              u[(i >> 12) & 15] +
+              u[(i >> 8) & 15] +
+              u[(i >> 4) & 15] +
+              u[15 & i] +
+              u[(h >> 28) & 15] +
+              u[(h >> 24) & 15] +
+              u[(h >> 20) & 15] +
+              u[(h >> 16) & 15] +
+              u[(h >> 12) & 15] +
+              u[(h >> 8) & 15] +
+              u[(h >> 4) & 15] +
+              u[15 & h] +
+              u[(r >> 28) & 15] +
+              u[(r >> 24) & 15] +
+              u[(r >> 20) & 15] +
+              u[(r >> 16) & 15] +
+              u[(r >> 12) & 15] +
+              u[(r >> 8) & 15] +
+              u[(r >> 4) & 15] +
+              u[15 & r] +
+              u[(s >> 28) & 15] +
+              u[(s >> 24) & 15] +
+              u[(s >> 20) & 15] +
+              u[(s >> 16) & 15] +
+              u[(s >> 12) & 15] +
+              u[(s >> 8) & 15] +
+              u[(s >> 4) & 15] +
+              u[15 & s] +
+              u[(e >> 28) & 15] +
+              u[(e >> 24) & 15] +
+              u[(e >> 20) & 15] +
+              u[(e >> 16) & 15] +
+              u[(e >> 12) & 15] +
+              u[(e >> 8) & 15] +
+              u[(e >> 4) & 15] +
+              u[15 & e] +
+              u[(n >> 28) & 15] +
+              u[(n >> 24) & 15] +
+              u[(n >> 20) & 15] +
+              u[(n >> 16) & 15] +
+              u[(n >> 12) & 15] +
+              u[(n >> 8) & 15] +
+              u[(n >> 4) & 15] +
+              u[15 & n];
+          return (
+            this.is224 ||
+              (a +=
+                u[(o >> 28) & 15] +
+                u[(o >> 24) & 15] +
+                u[(o >> 20) & 15] +
+                u[(o >> 16) & 15] +
+                u[(o >> 12) & 15] +
+                u[(o >> 8) & 15] +
+                u[(o >> 4) & 15] +
+                u[15 & o]),
+            a
+          );
+        }),
+        (t.prototype.toString = t.prototype.hex),
+        (t.prototype.digest = function () {
+          this.finalize();
+          var t = this.h0,
+            i = this.h1,
+            h = this.h2,
+            r = this.h3,
+            s = this.h4,
+            e = this.h5,
+            n = this.h6,
+            o = this.h7,
+            a = [
+              (t >> 24) & 255,
+              (t >> 16) & 255,
+              (t >> 8) & 255,
+              255 & t,
+              (i >> 24) & 255,
+              (i >> 16) & 255,
+              (i >> 8) & 255,
+              255 & i,
+              (h >> 24) & 255,
+              (h >> 16) & 255,
+              (h >> 8) & 255,
+              255 & h,
+              (r >> 24) & 255,
+              (r >> 16) & 255,
+              (r >> 8) & 255,
+              255 & r,
+              (s >> 24) & 255,
+              (s >> 16) & 255,
+              (s >> 8) & 255,
+              255 & s,
+              (e >> 24) & 255,
+              (e >> 16) & 255,
+              (e >> 8) & 255,
+              255 & e,
+              (n >> 24) & 255,
+              (n >> 16) & 255,
+              (n >> 8) & 255,
+              255 & n,
+            ];
+          return (
+            this.is224 ||
+              a.push((o >> 24) & 255, (o >> 16) & 255, (o >> 8) & 255, 255 & o),
+            a
+          );
+        }),
+        (t.prototype.array = t.prototype.digest),
+        (t.prototype.arrayBuffer = function () {
+          this.finalize();
+          var t = new ArrayBuffer(this.is224 ? 28 : 32),
+            i = new DataView(t);
+          return (
+            i.setUint32(0, this.h0),
+            i.setUint32(4, this.h1),
+            i.setUint32(8, this.h2),
+            i.setUint32(12, this.h3),
+            i.setUint32(16, this.h4),
+            i.setUint32(20, this.h5),
+            i.setUint32(24, this.h6),
+            this.is224 || i.setUint32(28, this.h7),
+            t
+          );
+        }),
+        (i.prototype = new t()),
+        (i.prototype.finalize = function () {
+          if ((t.prototype.finalize.call(this), this.inner)) {
+            this.inner = !1;
+            var i = this.array();
+            t.call(this, this.is224, this.sharedMemory),
+              this.update(this.oKeyPad),
+              this.update(i),
+              t.prototype.finalize.call(this);
+          }
+        });
+      var B = w();
+      (B.sha256 = B),
+        (B.sha224 = w(!0)),
+        (B.sha256.hmac = _()),
+        (B.sha224.hmac = _(!0)),
+        o
+          ? (module.exports = B)
+          : ((s.sha256 = B.sha256),
+            (s.sha224 = B.sha224),
+            a &&
+              define(function () {
+                return B;
+              }));
+    })();
+
+    /**
+     * [base64-js]{@link https://github.com/beatgammit/base64-js}
+     *
+     * @version v1.3.0
+     * @author Kirill, Fomichev
+     * @copyright Kirill, Fomichev 2014
+     * @license MIT
+     */
+    (function (r) {
+      if (typeof exports === "object" && typeof module !== "undefined") {
+        module.exports = r();
+      } else if (typeof define === "function" && define.amd) {
+        define([], r);
+      } else {
+        var e;
+        if (typeof window !== "undefined") {
+          e = window;
+        } else if (typeof global !== "undefined") {
+          e = global;
+        } else if (typeof self !== "undefined") {
+          e = self;
+        } else {
+          e = this;
+        }
+        e.base64js = r();
+      }
+    })(function () {
+      var r, e, n;
+      return (function () {
+        function r(e, n, t) {
+          function o(f, i) {
+            if (!n[f]) {
+              if (!e[f]) {
+                var u = "function" == typeof require && require;
+                if (!i && u) return u(f, !0);
+                if (a) return a(f, !0);
+                var v = new Error("Cannot find module '" + f + "'");
+                throw ((v.code = "MODULE_NOT_FOUND"), v);
+              }
+              var d = (n[f] = { exports: {} });
+              e[f][0].call(
+                d.exports,
+                function (r) {
+                  var n = e[f][1][r];
+                  return o(n || r);
+                },
+                d,
+                d.exports,
+                r,
+                e,
+                n,
+                t
+              );
+            }
+            return n[f].exports;
+          }
+          for (
+            var a = "function" == typeof require && require, f = 0;
+            f < t.length;
+            f++
+          )
+            o(t[f]);
+          return o;
+        }
+        return r;
+      })()(
+        {
+          "/": [
+            function (r, e, n) {
+              "use strict";
+              n.byteLength = d;
+              n.toByteArray = h;
+              n.fromByteArray = p;
+              var t = [];
+              var o = [];
+              var a = typeof Uint8Array !== "undefined" ? Uint8Array : Array;
+              var f =
+                "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+              for (var i = 0, u = f.length; i < u; ++i) {
+                t[i] = f[i];
+                o[f.charCodeAt(i)] = i;
+              }
+              o["-".charCodeAt(0)] = 62;
+              o["_".charCodeAt(0)] = 63;
+              function v(r) {
+                var e = r.length;
+                if (e % 4 > 0) {
+                  throw new Error(
+                    "Invalid string. Length must be a multiple of 4"
+                  );
+                }
+                var n = r.indexOf("=");
+                if (n === -1) n = e;
+                var t = n === e ? 0 : 4 - (n % 4);
+                return [n, t];
+              }
+              function d(r) {
+                var e = v(r);
+                var n = e[0];
+                var t = e[1];
+                return ((n + t) * 3) / 4 - t;
+              }
+              function c(r, e, n) {
+                return ((e + n) * 3) / 4 - n;
+              }
+              function h(r) {
+                var e;
+                var n = v(r);
+                var t = n[0];
+                var f = n[1];
+                var i = new a(c(r, t, f));
+                var u = 0;
+                var d = f > 0 ? t - 4 : t;
+                for (var h = 0; h < d; h += 4) {
+                  e =
+                    (o[r.charCodeAt(h)] << 18) |
+                    (o[r.charCodeAt(h + 1)] << 12) |
+                    (o[r.charCodeAt(h + 2)] << 6) |
+                    o[r.charCodeAt(h + 3)];
+                  i[u++] = (e >> 16) & 255;
+                  i[u++] = (e >> 8) & 255;
+                  i[u++] = e & 255;
+                }
+                if (f === 2) {
+                  e = (o[r.charCodeAt(h)] << 2) | (o[r.charCodeAt(h + 1)] >> 4);
+                  i[u++] = e & 255;
+                }
+                if (f === 1) {
+                  e =
+                    (o[r.charCodeAt(h)] << 10) |
+                    (o[r.charCodeAt(h + 1)] << 4) |
+                    (o[r.charCodeAt(h + 2)] >> 2);
+                  i[u++] = (e >> 8) & 255;
+                  i[u++] = e & 255;
+                }
+                return i;
+              }
+              function s(r) {
+                return (
+                  t[(r >> 18) & 63] +
+                  t[(r >> 12) & 63] +
+                  t[(r >> 6) & 63] +
+                  t[r & 63]
+                );
+              }
+              function l(r, e, n) {
+                var t;
+                var o = [];
+                for (var a = e; a < n; a += 3) {
+                  t =
+                    ((r[a] << 16) & 16711680) +
+                    ((r[a + 1] << 8) & 65280) +
+                    (r[a + 2] & 255);
+                  o.push(s(t));
+                }
+                return o.join("");
+              }
+              function p(r) {
+                var e;
+                var n = r.length;
+                var o = n % 3;
+                var a = [];
+                var f = 16383;
+                for (var i = 0, u = n - o; i < u; i += f) {
+                  a.push(l(r, i, i + f > u ? u : i + f));
+                }
+                if (o === 1) {
+                  e = r[n - 1];
+                  a.push(t[e >> 2] + t[(e << 4) & 63] + "==");
+                } else if (o === 2) {
+                  e = (r[n - 2] << 8) + r[n - 1];
+                  a.push(
+                    t[e >> 10] + t[(e >> 4) & 63] + t[(e << 2) & 63] + "="
+                  );
+                }
+                return a.join("");
+              }
+            },
+            {},
+          ],
+        },
+        {},
+        []
+      )("/");
+    });
+
+    /**
+     * [promise-polyfill]{@link https://github.com/taylorhakes/promise-polyfill}
+     *
+     * @version v8.1.3
+     * @author Hakes, Taylor
+     * @copyright Hakes, Taylor 2014
+     * @license MIT
+     */
+    !(function (e, n) {
+      "object" == typeof exports && "undefined" != typeof module
+        ? n()
+        : "function" == typeof define && define.amd
+        ? define(n)
+        : n();
+    })(0, function () {
+      "use strict";
+      function e(e) {
+        var n = this.constructor;
+        return this.then(
+          function (t) {
+            return n.resolve(e()).then(function () {
+              return t;
+            });
+          },
+          function (t) {
+            return n.resolve(e()).then(function () {
+              return n.reject(t);
+            });
+          }
+        );
+      }
+      function n(e) {
+        return !(!e || "undefined" == typeof e.length);
+      }
+      function t() {}
+      function o(e) {
+        if (!(this instanceof o))
+          throw new TypeError("Promises must be constructed via new");
+        if ("function" != typeof e) throw new TypeError("not a function");
+        (this._state = 0),
+          (this._handled = !1),
+          (this._value = undefined),
+          (this._deferreds = []),
+          c(e, this);
+      }
+      function r(e, n) {
+        for (; 3 === e._state; ) e = e._value;
+        0 !== e._state
+          ? ((e._handled = !0),
+            o._immediateFn(function () {
+              var t = 1 === e._state ? n.onFulfilled : n.onRejected;
+              if (null !== t) {
+                var o;
+                try {
+                  o = t(e._value);
+                } catch (r) {
+                  return void f(n.promise, r);
+                }
+                i(n.promise, o);
+              } else (1 === e._state ? i : f)(n.promise, e._value);
+            }))
+          : e._deferreds.push(n);
+      }
+      function i(e, n) {
+        try {
+          if (n === e)
+            throw new TypeError("A promise cannot be resolved with itself.");
+          if (n && ("object" == typeof n || "function" == typeof n)) {
+            var t = n.then;
+            if (n instanceof o)
+              return (e._state = 3), (e._value = n), void u(e);
+            if ("function" == typeof t)
+              return void c(
+                (function (e, n) {
+                  return function () {
+                    e.apply(n, arguments);
+                  };
+                })(t, n),
+                e
+              );
+          }
+          (e._state = 1), (e._value = n), u(e);
+        } catch (r) {
+          f(e, r);
+        }
+      }
+      function f(e, n) {
+        (e._state = 2), (e._value = n), u(e);
+      }
+      function u(e) {
+        2 === e._state &&
+          0 === e._deferreds.length &&
+          o._immediateFn(function () {
+            e._handled || o._unhandledRejectionFn(e._value);
+          });
+        for (var n = 0, t = e._deferreds.length; t > n; n++)
+          r(e, e._deferreds[n]);
+        e._deferreds = null;
+      }
+      function c(e, n) {
+        var t = !1;
+        try {
+          e(
+            function (e) {
+              t || ((t = !0), i(n, e));
+            },
+            function (e) {
+              t || ((t = !0), f(n, e));
+            }
+          );
+        } catch (o) {
+          if (t) return;
+          (t = !0), f(n, o);
+        }
+      }
+      var a = setTimeout;
+      (o.prototype["catch"] = function (e) {
+        return this.then(null, e);
+      }),
+        (o.prototype.then = function (e, n) {
+          var o = new this.constructor(t);
+          return (
+            r(
+              this,
+              new (function (e, n, t) {
+                (this.onFulfilled = "function" == typeof e ? e : null),
+                  (this.onRejected = "function" == typeof n ? n : null),
+                  (this.promise = t);
+              })(e, n, o)
+            ),
+            o
+          );
+        }),
+        (o.prototype["finally"] = e),
+        (o.all = function (e) {
+          return new o(function (t, o) {
+            function r(e, n) {
+              try {
+                if (n && ("object" == typeof n || "function" == typeof n)) {
+                  var u = n.then;
+                  if ("function" == typeof u)
+                    return void u.call(
+                      n,
+                      function (n) {
+                        r(e, n);
+                      },
+                      o
+                    );
+                }
+                (i[e] = n), 0 == --f && t(i);
+              } catch (c) {
+                o(c);
+              }
+            }
+            if (!n(e)) return o(new TypeError("Promise.all accepts an array"));
+            var i = Array.prototype.slice.call(e);
+            if (0 === i.length) return t([]);
+            for (var f = i.length, u = 0; i.length > u; u++) r(u, i[u]);
+          });
+        }),
+        (o.resolve = function (e) {
+          return e && "object" == typeof e && e.constructor === o
+            ? e
+            : new o(function (n) {
+                n(e);
+              });
+        }),
+        (o.reject = function (e) {
+          return new o(function (n, t) {
+            t(e);
+          });
+        }),
+        (o.race = function (e) {
+          return new o(function (t, r) {
+            if (!n(e)) return r(new TypeError("Promise.race accepts an array"));
+            for (var i = 0, f = e.length; f > i; i++)
+              o.resolve(e[i]).then(t, r);
+          });
+        }),
+        (o._immediateFn =
+          ("function" == typeof setImmediate &&
+            function (e) {
+              setImmediate(e);
+            }) ||
+          function (e) {
+            a(e, 0);
+          }),
+        (o._unhandledRejectionFn = function (e) {
+          void 0 !== console &&
+            console &&
+            console.warn("Possible Unhandled Promise Rejection:", e);
+        });
+      var l = (function () {
+        if ("undefined" != typeof self) return self;
+        if ("undefined" != typeof window) return window;
+        if ("undefined" != typeof global) return global;
+        throw Error("unable to locate global object");
+      })();
+      "Promise" in l
+        ? l.Promise.prototype["finally"] || (l.Promise.prototype["finally"] = e)
+        : (l.Promise = o);
+    });
+
+    var Keycloak = factory(root["sha256"], root["base64js"]);
+    root["Keycloak"] = Keycloak;
+
+    if (typeof define === "function" && define.amd) {
+      define("keycloak", [], function () {
+        return Keycloak;
+      });
+    }
+  }
+})(window, function (sha256_imported, base64js_imported) {
+  if (typeof Promise === "undefined") {
+    throw Error(
+      "Keycloak requires an environment that supports Promises. Make sure that you include the appropriate polyfill."
+    );
+  }
+
+  var loggedPromiseDeprecation = false;
+
+  function logPromiseDeprecation() {
+    if (!loggedPromiseDeprecation) {
+      loggedPromiseDeprecation = true;
+      console.warn(
+        "[KEYCLOAK] Usage of legacy style promise methods such as `.error()` and `.success()` has been deprecated and support will be removed in future versions. Use standard style promise methods such as `.then() and `.catch()` instead."
+      );
+    }
+  }
+
+  function Keycloak(config) {
+    if (!(this instanceof Keycloak)) {
+      return new Keycloak(config);
+    }
+
+    var kc = this;
+    var adapter;
+    var refreshQueue = [];
+    var callbackStorage;
+
+    var loginIframe = {
+      enable: true,
+      callbackList: [],
+      interval: 5,
+    };
+
+    var scripts = document.getElementsByTagName("script");
+    for (var i = 0; i < scripts.length; i++) {
+      if (
+        (scripts[i].src.indexOf("keycloak.js") !== -1 ||
+          scripts[i].src.indexOf("keycloak.min.js") !== -1) &&
+        scripts[i].src.indexOf("version=") !== -1
+      ) {
+        kc.iframeVersion = scripts[i].src
+          .substring(scripts[i].src.indexOf("version=") + 8)
+          .split("&")[0];
+      }
+    }
+
+    var useNonce = true;
+    var logInfo = createLogger(console.info);
+    var logWarn = createLogger(console.warn);
+
+    kc.init = function (initOptions) {
+      kc.authenticated = false;
+
+      callbackStorage = createCallbackStorage();
+      var adapters = ["default", "cordova", "cordova-native"];
+
+      if (initOptions && adapters.indexOf(initOptions.adapter) > -1) {
+        adapter = loadAdapter(initOptions.adapter);
+      } else if (initOptions && typeof initOptions.adapter === "object") {
+        adapter = initOptions.adapter;
+      } else {
+        if (window.Cordova || window.cordova) {
+          adapter = loadAdapter("cordova");
+        } else {
+          adapter = loadAdapter();
+        }
+      }
+
+      if (initOptions) {
+        if (typeof initOptions.useNonce !== "undefined") {
+          useNonce = initOptions.useNonce;
+        }
+
+        if (typeof initOptions.checkLoginIframe !== "undefined") {
+          loginIframe.enable = initOptions.checkLoginIframe;
+        }
+
+        if (initOptions.checkLoginIframeInterval) {
+          loginIframe.interval = initOptions.checkLoginIframeInterval;
+        }
+
+        if (initOptions.onLoad === "login-required") {
+          kc.loginRequired = true;
+        }
+
+        if (initOptions.responseMode) {
+          if (
+            initOptions.responseMode === "query" ||
+            initOptions.responseMode === "fragment"
+          ) {
+            kc.responseMode = initOptions.responseMode;
+          } else {
+            throw "Invalid value for responseMode";
+          }
+        }
+
+        if (initOptions.flow) {
+          switch (initOptions.flow) {
+            case "standard":
+              kc.responseType = "code";
+              break;
+            case "implicit":
+              kc.responseType = "id_token token";
+              break;
+            case "hybrid":
+              kc.responseType = "code id_token token";
+              break;
+            default:
+              throw "Invalid value for flow";
+          }
+          kc.flow = initOptions.flow;
+        }
+
+        if (initOptions.timeSkew != null) {
+          kc.timeSkew = initOptions.timeSkew;
+        }
+
+        if (initOptions.redirectUri) {
+          kc.redirectUri = initOptions.redirectUri;
+        }
+
+        if (initOptions.silentCheckSsoRedirectUri) {
+          kc.silentCheckSsoRedirectUri = initOptions.silentCheckSsoRedirectUri;
+        }
+
+        if (typeof initOptions.silentCheckSsoFallback === "boolean") {
+          kc.silentCheckSsoFallback = initOptions.silentCheckSsoFallback;
+        } else {
+          kc.silentCheckSsoFallback = true;
+        }
+
+        if (initOptions.pkceMethod) {
+          if (initOptions.pkceMethod !== "S256") {
+            throw "Invalid value for pkceMethod";
+          }
+          kc.pkceMethod = initOptions.pkceMethod;
+        }
+
+        if (typeof initOptions.enableLogging === "boolean") {
+          kc.enableLogging = initOptions.enableLogging;
+        } else {
+          kc.enableLogging = false;
+        }
+
+        if (typeof initOptions.scope === "string") {
+          kc.scope = initOptions.scope;
+        }
+
+        if (
+          typeof initOptions.messageReceiveTimeout === "number" &&
+          initOptions.messageReceiveTimeout > 0
+        ) {
+          kc.messageReceiveTimeout = initOptions.messageReceiveTimeout;
+        } else {
+          kc.messageReceiveTimeout = 10000;
+        }
+      }
+
+      if (!kc.responseMode) {
+        kc.responseMode = "fragment";
+      }
+      if (!kc.responseType) {
+        kc.responseType = "code";
+        kc.flow = "standard";
+      }
+
+      var promise = createPromise();
+
+      var initPromise = createPromise();
+      initPromise.promise
+        .then(function () {
+          kc.onReady && kc.onReady(kc.authenticated);
+          promise.setSuccess(kc.authenticated);
+        })
+        .catch(function (error) {
+          promise.setError(error);
+        });
+
+      var configPromise = loadConfig(config);
+
+      function onLoad() {
+        var doLogin = function (prompt) {
+          if (!prompt) {
+            options.prompt = "none";
+          }
+
+          kc.login(options)
+            .then(function () {
+              initPromise.setSuccess();
+            })
+            .catch(function (error) {
+              initPromise.setError(error);
+            });
+        };
+
+        var checkSsoSilently = function () {
+          var ifrm = document.createElement("iframe");
+          var src = kc.createLoginUrl({
+            prompt: "none",
+            redirectUri: kc.silentCheckSsoRedirectUri,
+          });
+          ifrm.setAttribute("src", src);
+          ifrm.setAttribute("title", "keycloak-silent-check-sso");
+          ifrm.style.display = "none";
+          document.body.appendChild(ifrm);
+
+          var messageCallback = function (event) {
+            if (
+              event.origin !== window.location.origin ||
+              ifrm.contentWindow !== event.source
+            ) {
+              return;
+            }
+
+            var oauth = parseCallback(event.data);
+            processCallback(oauth, initPromise);
+
+            document.body.removeChild(ifrm);
+            window.removeEventListener("message", messageCallback);
+          };
+
+          window.addEventListener("message", messageCallback);
+        };
+
+        var options = {};
+        switch (initOptions.onLoad) {
+          case "check-sso":
+            if (loginIframe.enable) {
+              setupCheckLoginIframe().then(function () {
+                checkLoginIframe()
+                  .then(function (unchanged) {
+                    if (!unchanged) {
+                      kc.silentCheckSsoRedirectUri
+                        ? checkSsoSilently()
+                        : doLogin(false);
+                    } else {
+                      initPromise.setSuccess();
+                    }
+                  })
+                  .catch(function (error) {
+                    initPromise.setError(error);
+                  });
+              });
+            } else {
+              kc.silentCheckSsoRedirectUri
+                ? checkSsoSilently()
+                : doLogin(false);
+            }
+            break;
+          case "login-required":
+            doLogin(true);
+            break;
+          default:
+            throw "Invalid value for onLoad";
+        }
+      }
+
+      function processInit() {
+        var callback = parseCallback(window.location.href);
+
+        if (callback) {
+          window.history.replaceState(
+            window.history.state,
+            null,
+            callback.newUrl
+          );
+        }
+
+        if (callback && callback.valid) {
+          return setupCheckLoginIframe()
+            .then(function () {
+              processCallback(callback, initPromise);
+            })
+            .catch(function (error) {
+              initPromise.setError(error);
+            });
+        } else if (initOptions) {
+          if (initOptions.token && initOptions.refreshToken) {
+            setToken(
+              initOptions.token,
+              initOptions.refreshToken,
+              initOptions.idToken
+            );
+
+            if (loginIframe.enable) {
+              setupCheckLoginIframe().then(function () {
+                checkLoginIframe()
+                  .then(function (unchanged) {
+                    if (unchanged) {
+                      kc.onAuthSuccess && kc.onAuthSuccess();
+                      initPromise.setSuccess();
+                      scheduleCheckIframe();
+                    } else {
+                      initPromise.setSuccess();
+                    }
+                  })
+                  .catch(function (error) {
+                    initPromise.setError(error);
+                  });
+              });
+            } else {
+              kc.updateToken(-1)
+                .then(function () {
+                  kc.onAuthSuccess && kc.onAuthSuccess();
+                  initPromise.setSuccess();
+                })
+                .catch(function (error) {
+                  kc.onAuthError && kc.onAuthError();
+                  if (initOptions.onLoad) {
+                    onLoad();
+                  } else {
+                    initPromise.setError(error);
+                  }
+                });
+            }
+          } else if (initOptions.onLoad) {
+            onLoad();
+          } else {
+            initPromise.setSuccess();
+          }
+        } else {
+          initPromise.setSuccess();
+        }
+      }
+
+      function domReady() {
+        var promise = createPromise();
+
+        var checkReadyState = function () {
+          if (
+            document.readyState === "interactive" ||
+            document.readyState === "complete"
+          ) {
+            document.removeEventListener("readystatechange", checkReadyState);
+            promise.setSuccess();
+          }
+        };
+        document.addEventListener("readystatechange", checkReadyState);
+
+        checkReadyState(); // just in case the event was already fired and we missed it (in case the init is done later than at the load time, i.e. it's done from code)
+
+        return promise.promise;
+      }
+
+      configPromise.then(function () {
+        domReady()
+          .then(check3pCookiesSupported)
+          .then(processInit)
+          .catch(function (error) {
+            promise.setError(error);
+          });
+      });
+      configPromise.catch(function (error) {
+        promise.setError(error);
+      });
+
+      return promise.promise;
+    };
+
+    kc.login = function (options) {
+      return adapter.login(options);
+    };
+
+    function generateRandomData(len) {
+      // use web crypto APIs if possible
+      var array = null;
+      var crypto = window.crypto || window.msCrypto;
+      if (crypto && crypto.getRandomValues && window.Uint8Array) {
+        array = new Uint8Array(len);
+        crypto.getRandomValues(array);
+        return array;
+      }
+
+      // fallback to Math random
+      array = new Array(len);
+      for (var j = 0; j < array.length; j++) {
+        array[j] = Math.floor(256 * Math.random());
+      }
+      return array;
+    }
+
+    function generateCodeVerifier(len) {
+      return generateRandomString(
+        len,
+        "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
+      );
+    }
+
+    function generateRandomString(len, alphabet) {
+      var randomData = generateRandomData(len);
+      var chars = new Array(len);
+      for (var i = 0; i < len; i++) {
+        chars[i] = alphabet.charCodeAt(randomData[i] % alphabet.length);
+      }
+      return String.fromCharCode.apply(null, chars);
+    }
+
+    function generatePkceChallenge(pkceMethod, codeVerifier) {
+      switch (pkceMethod) {
+        // The use of the "plain" method is considered insecure and therefore not supported.
+        case "S256":
+          // hash codeVerifier, then encode as url-safe base64 without padding
+          var hashBytes = new Uint8Array(
+            sha256_imported.arrayBuffer(codeVerifier)
+          );
+          var encodedHash = base64js_imported
+            .fromByteArray(hashBytes)
+            .replace(/\+/g, "-")
+            .replace(/\//g, "_")
+            .replace(/\=/g, "");
+          return encodedHash;
+        default:
+          throw "Invalid value for pkceMethod";
+      }
+    }
+
+    kc.createLoginUrl = function (options) {
+      var state = createUUID();
+      var nonce = createUUID();
+
+      var redirectUri = adapter.redirectUri(options);
+
+      var callbackState = {
+        state: state,
+        nonce: nonce,
+        redirectUri: encodeURIComponent(redirectUri),
+      };
+
+      if (options && options.prompt) {
+        callbackState.prompt = options.prompt;
+      }
+
+      var baseUrl;
+      if (options && options.action == "register") {
+        baseUrl = kc.endpoints.register();
+      } else {
+        baseUrl = kc.endpoints.authorize();
+      }
+
+      var scope = (options && options.scope) || kc.scope;
+      if (!scope) {
+        // if scope is not set, default to "openid"
+        scope = "openid";
+      } else if (scope.indexOf("openid") === -1) {
+        // if openid scope is missing, prefix the given scopes with it
+        scope = "openid " + scope;
+      }
+
+      var url =
+        baseUrl +
+        "?client_id=" +
+        encodeURIComponent(kc.clientId) +
+        "&redirect_uri=" +
+        encodeURIComponent(redirectUri) +
+        "&state=" +
+        encodeURIComponent(state) +
+        "&response_mode=" +
+        encodeURIComponent(kc.responseMode) +
+        "&response_type=" +
+        encodeURIComponent(kc.responseType) +
+        "&scope=" +
+        encodeURIComponent(scope);
+      if (useNonce) {
+        url = url + "&nonce=" + encodeURIComponent(nonce);
+      }
+
+      if (options && options.prompt) {
+        url += "&prompt=" + encodeURIComponent(options.prompt);
+      }
+
+      if (options && options.maxAge) {
+        url += "&max_age=" + encodeURIComponent(options.maxAge);
+      }
+
+      if (options && options.loginHint) {
+        url += "&login_hint=" + encodeURIComponent(options.loginHint);
+      }
+
+      if (options && options.idpHint) {
+        url += "&kc_idp_hint=" + encodeURIComponent(options.idpHint);
+      }
+
+      if (options && options.action && options.action != "register") {
+        url += "&kc_action=" + encodeURIComponent(options.action);
+      }
+
+      if (options && options.locale) {
+        url += "&ui_locales=" + encodeURIComponent(options.locale);
+      }
+
+      if (kc.pkceMethod) {
+        var codeVerifier = generateCodeVerifier(96);
+        callbackState.pkceCodeVerifier = codeVerifier;
+        var pkceChallenge = generatePkceChallenge(kc.pkceMethod, codeVerifier);
+        url += "&code_challenge=" + pkceChallenge;
+        url += "&code_challenge_method=" + kc.pkceMethod;
+      }
+
+      callbackStorage.add(callbackState);
+
+      return url;
+    };
+
+    kc.logout = function (options) {
+      return adapter.logout(options);
+    };
+
+    kc.createLogoutUrl = function (options) {
+      var url =
+        kc.endpoints.logout() +
+        "?redirect_uri=" +
+        encodeURIComponent(adapter.redirectUri(options, false));
+
+      return url;
+    };
+
+    kc.register = function (options) {
+      return adapter.register(options);
+    };
+
+    kc.createRegisterUrl = function (options) {
+      if (!options) {
+        options = {};
+      }
+      options.action = "register";
+      return kc.createLoginUrl(options);
+    };
+
+    kc.createAccountUrl = function (options) {
+      var realm = getRealmUrl();
+      var url = undefined;
+      if (typeof realm !== "undefined") {
+        url =
+          realm +
+          "/account" +
+          "?referrer=" +
+          encodeURIComponent(kc.clientId) +
+          "&referrer_uri=" +
+          encodeURIComponent(adapter.redirectUri(options));
+      }
+      return url;
+    };
+
+    kc.accountManagement = function () {
+      return adapter.accountManagement();
+    };
+
+    kc.hasRealmRole = function (role) {
+      var access = kc.realmAccess;
+      return !!access && access.roles.indexOf(role) >= 0;
+    };
+
+    kc.hasResourceRole = function (role, resource) {
+      if (!kc.resourceAccess) {
+        return false;
+      }
+
+      var access = kc.resourceAccess[resource || kc.clientId];
+      return !!access && access.roles.indexOf(role) >= 0;
+    };
+
+    kc.loadUserProfile = function () {
+      var url = getRealmUrl() + "/account";
+      var req = new XMLHttpRequest();
+      req.open("GET", url, true);
+      req.setRequestHeader("Accept", "application/json");
+      req.setRequestHeader("Authorization", "bearer " + kc.token);
+
+      var promise = createPromise();
+
+      req.onreadystatechange = function () {
+        if (req.readyState == 4) {
+          if (req.status == 200) {
+            kc.profile = JSON.parse(req.responseText);
+            promise.setSuccess(kc.profile);
+          } else {
+            promise.setError();
+          }
+        }
+      };
+
+      req.send();
+
+      return promise.promise;
+    };
+
+    kc.loadUserInfo = function () {
+      var url = kc.endpoints.userinfo();
+      var req = new XMLHttpRequest();
+      req.open("GET", url, true);
+      req.setRequestHeader("Accept", "application/json");
+      req.setRequestHeader("Authorization", "bearer " + kc.token);
+
+      var promise = createPromise();
+
+      req.onreadystatechange = function () {
+        if (req.readyState == 4) {
+          if (req.status == 200) {
+            kc.userInfo = JSON.parse(req.responseText);
+            promise.setSuccess(kc.userInfo);
+          } else {
+            promise.setError();
+          }
+        }
+      };
+
+      req.send();
+
+      return promise.promise;
+    };
+
+    kc.isTokenExpired = function (minValidity) {
+      if (!kc.tokenParsed || (!kc.refreshToken && kc.flow != "implicit")) {
+        throw "Not authenticated";
+      }
+
+      if (kc.timeSkew == null) {
+        logInfo(
+          "[KEYCLOAK] Unable to determine if token is expired as timeskew is not set"
+        );
+        return true;
+      }
+
+      var expiresIn =
+        kc.tokenParsed["exp"] -
+        Math.ceil(new Date().getTime() / 1000) +
+        kc.timeSkew;
+      if (minValidity) {
+        if (isNaN(minValidity)) {
+          throw "Invalid minValidity";
+        }
+        expiresIn -= minValidity;
+      }
+      return expiresIn < 0;
+    };
+
+    kc.updateToken = function (minValidity) {
+      var promise = createPromise();
+
+      if (!kc.refreshToken) {
+        promise.setError();
+        return promise.promise;
+      }
+
+      minValidity = minValidity || 5;
+
+      var exec = function () {
+        var refreshToken = false;
+        if (minValidity == -1) {
+          refreshToken = true;
+          logInfo("[KEYCLOAK] Refreshing token: forced refresh");
+        } else if (!kc.tokenParsed || kc.isTokenExpired(minValidity)) {
+          refreshToken = true;
+          logInfo("[KEYCLOAK] Refreshing token: token expired");
+        }
+
+        if (!refreshToken) {
+          promise.setSuccess(false);
+        } else {
+          var params =
+            "grant_type=refresh_token&" + "refresh_token=" + kc.refreshToken;
+          var url = kc.endpoints.token();
+
+          refreshQueue.push(promise);
+
+          if (refreshQueue.length == 1) {
+            var req = new XMLHttpRequest();
+            req.open("POST", url, true);
+            req.setRequestHeader(
+              "Content-type",
+              "application/x-www-form-urlencoded"
+            );
+            req.withCredentials = true;
+
+            params += "&client_id=" + encodeURIComponent(kc.clientId);
+
+            var timeLocal = new Date().getTime();
+
+            req.onreadystatechange = function () {
+              if (req.readyState == 4) {
+                if (req.status == 200) {
+                  logInfo("[KEYCLOAK] Token refreshed");
+
+                  timeLocal = (timeLocal + new Date().getTime()) / 2;
+
+                  var tokenResponse = JSON.parse(req.responseText);
+
+                  setToken(
+                    tokenResponse["access_token"],
+                    tokenResponse["refresh_token"],
+                    tokenResponse["id_token"],
+                    timeLocal
+                  );
+
+                  kc.onAuthRefreshSuccess && kc.onAuthRefreshSuccess();
+                  for (
+                    var p = refreshQueue.pop();
+                    p != null;
+                    p = refreshQueue.pop()
+                  ) {
+                    p.setSuccess(true);
+                  }
+                } else {
+                  logWarn("[KEYCLOAK] Failed to refresh token");
+
+                  if (req.status == 400) {
+                    kc.clearToken();
+                  }
+
+                  kc.onAuthRefreshError && kc.onAuthRefreshError();
+                  for (
+                    var p = refreshQueue.pop();
+                    p != null;
+                    p = refreshQueue.pop()
+                  ) {
+                    p.setError(true);
+                  }
+                }
+              }
+            };
+
+            req.send(params);
+          }
+        }
+      };
+
+      if (loginIframe.enable) {
+        var iframePromise = checkLoginIframe();
+        iframePromise
+          .then(function () {
+            exec();
+          })
+          .catch(function (error) {
+            promise.setError(error);
+          });
+      } else {
+        exec();
+      }
+
+      return promise.promise;
+    };
+
+    kc.clearToken = function () {
+      if (kc.token) {
+        setToken(null, null, null);
+        kc.onAuthLogout && kc.onAuthLogout();
+        if (kc.loginRequired) {
+          kc.login();
+        }
+      }
+    };
+
+    function getRealmUrl() {
+      if (typeof kc.authServerUrl !== "undefined") {
+        if (kc.authServerUrl.charAt(kc.authServerUrl.length - 1) == "/") {
+          return kc.authServerUrl + "realms/" + encodeURIComponent(kc.realm);
+        } else {
+          return kc.authServerUrl + "/realms/" + encodeURIComponent(kc.realm);
+        }
+      } else {
+        return undefined;
+      }
+    }
+
+    function getOrigin() {
+      if (!window.location.origin) {
+        return (
+          window.location.protocol +
+          "//" +
+          window.location.hostname +
+          (window.location.port ? ":" + window.location.port : "")
+        );
+      } else {
+        return window.location.origin;
+      }
+    }
+
+    function processCallback(oauth, promise) {
+      var code = oauth.code;
+      var error = oauth.error;
+      var prompt = oauth.prompt;
+
+      var timeLocal = new Date().getTime();
+
+      if (oauth["kc_action_status"]) {
+        kc.onActionUpdate && kc.onActionUpdate(oauth["kc_action_status"]);
+      }
+
+      if (error) {
+        if (prompt != "none") {
+          var errorData = {
+            error: error,
+            error_description: oauth.error_description,
+          };
+          kc.onAuthError && kc.onAuthError(errorData);
+          promise && promise.setError(errorData);
+        } else {
+          promise && promise.setSuccess();
+        }
+        return;
+      } else if (
+        kc.flow != "standard" &&
+        (oauth.access_token || oauth.id_token)
+      ) {
+        authSuccess(oauth.access_token, null, oauth.id_token, true);
+      }
+
+      if (kc.flow != "implicit" && code) {
+        var params = "code=" + code + "&grant_type=authorization_code";
+        var url = kc.endpoints.token();
+
+        var req = new XMLHttpRequest();
+        req.open("POST", url, true);
+        req.setRequestHeader(
+          "Content-type",
+          "application/x-www-form-urlencoded"
+        );
+
+        params += "&client_id=" + encodeURIComponent(kc.clientId);
+        params += "&redirect_uri=" + oauth.redirectUri;
+
+        if (oauth.pkceCodeVerifier) {
+          params += "&code_verifier=" + oauth.pkceCodeVerifier;
+        }
+
+        req.withCredentials = true;
+
+        req.onreadystatechange = function () {
+          if (req.readyState == 4) {
+            if (req.status == 200) {
+              var tokenResponse = JSON.parse(req.responseText);
+              authSuccess(
+                tokenResponse["access_token"],
+                tokenResponse["refresh_token"],
+                tokenResponse["id_token"],
+                kc.flow === "standard"
+              );
+              scheduleCheckIframe();
+            } else {
+              kc.onAuthError && kc.onAuthError();
+              promise && promise.setError();
+            }
+          }
+        };
+
+        req.send(params);
+      }
+
+      function authSuccess(accessToken, refreshToken, idToken, fulfillPromise) {
+        timeLocal = (timeLocal + new Date().getTime()) / 2;
+
+        setToken(accessToken, refreshToken, idToken, timeLocal);
+
+        if (
+          useNonce &&
+          ((kc.tokenParsed && kc.tokenParsed.nonce != oauth.storedNonce) ||
+            (kc.refreshTokenParsed &&
+              kc.refreshTokenParsed.nonce != oauth.storedNonce) ||
+            (kc.idTokenParsed && kc.idTokenParsed.nonce != oauth.storedNonce))
+        ) {
+          logInfo("[KEYCLOAK] Invalid nonce, clearing token");
+          kc.clearToken();
+          promise && promise.setError();
+        } else {
+          if (fulfillPromise) {
+            kc.onAuthSuccess && kc.onAuthSuccess();
+            promise && promise.setSuccess();
+          }
+        }
+      }
+    }
+
+    function loadConfig(url) {
+      var promise = createPromise();
+      var configUrl;
+
+      if (!config) {
+        configUrl = "keycloak.json";
+      } else if (typeof config === "string") {
+        configUrl = config;
+      }
+
+      function setupOidcEndoints(oidcConfiguration) {
+        if (!oidcConfiguration) {
+          kc.endpoints = {
+            authorize: function () {
+              return getRealmUrl() + "/protocol/openid-connect/auth";
+            },
+            token: function () {
+              return getRealmUrl() + "/protocol/openid-connect/token";
+            },
+            logout: function () {
+              return getRealmUrl() + "/protocol/openid-connect/logout";
+            },
+            checkSessionIframe: function () {
+              var src =
+                getRealmUrl() +
+                "/protocol/openid-connect/login-status-iframe.html";
+              if (kc.iframeVersion) {
+                src = src + "?version=" + kc.iframeVersion;
+              }
+              return src;
+            },
+            thirdPartyCookiesIframe: function () {
+              var src =
+                getRealmUrl() +
+                "/protocol/openid-connect/3p-cookies/step1.html";
+              if (kc.iframeVersion) {
+                src = src + "?version=" + kc.iframeVersion;
+              }
+              return src;
+            },
+            register: function () {
+              return getRealmUrl() + "/protocol/openid-connect/registrations";
+            },
+            userinfo: function () {
+              return getRealmUrl() + "/protocol/openid-connect/userinfo";
+            },
+          };
+        } else {
+          kc.endpoints = {
+            authorize: function () {
+              return oidcConfiguration.authorization_endpoint;
+            },
+            token: function () {
+              return oidcConfiguration.token_endpoint;
+            },
+            logout: function () {
+              if (!oidcConfiguration.end_session_endpoint) {
+                throw "Not supported by the OIDC server";
+              }
+              return oidcConfiguration.end_session_endpoint;
+            },
+            checkSessionIframe: function () {
+              if (!oidcConfiguration.check_session_iframe) {
+                throw "Not supported by the OIDC server";
+              }
+              return oidcConfiguration.check_session_iframe;
+            },
+            register: function () {
+              throw 'Redirection to "Register user" page not supported in standard OIDC mode';
+            },
+            userinfo: function () {
+              if (!oidcConfiguration.userinfo_endpoint) {
+                throw "Not supported by the OIDC server";
+              }
+              return oidcConfiguration.userinfo_endpoint;
+            },
+          };
+        }
+      }
+
+      if (configUrl) {
+        var req = new XMLHttpRequest();
+        req.open("GET", configUrl, true);
+        req.setRequestHeader("Accept", "application/json");
+
+        req.onreadystatechange = function () {
+          if (req.readyState == 4) {
+            if (req.status == 200 || fileLoaded(req)) {
+              var config = JSON.parse(req.responseText);
+
+              kc.authServerUrl = config["auth-server-url"];
+              kc.realm = config["realm"];
+              kc.clientId = config["resource"];
+              setupOidcEndoints(null);
+              promise.setSuccess();
+            } else {
+              promise.setError();
+            }
+          }
+        };
+
+        req.send();
+      } else {
+        if (!config.clientId) {
+          throw "clientId missing";
+        }
+
+        kc.clientId = config.clientId;
+
+        var oidcProvider = config["oidcProvider"];
+        if (!oidcProvider) {
+          if (!config["url"]) {
+            var scripts = document.getElementsByTagName("script");
+            for (var i = 0; i < scripts.length; i++) {
+              if (scripts[i].src.match(/.*keycloak\.js/)) {
+                config.url = scripts[i].src.substr(
+                  0,
+                  scripts[i].src.indexOf("/js/keycloak.js")
+                );
+                break;
+              }
+            }
+          }
+          if (!config.realm) {
+            throw "realm missing";
+          }
+
+          kc.authServerUrl = config.url;
+          kc.realm = config.realm;
+          setupOidcEndoints(null);
+          promise.setSuccess();
+        } else {
+          if (typeof oidcProvider === "string") {
+            var oidcProviderConfigUrl;
+            if (oidcProvider.charAt(oidcProvider.length - 1) == "/") {
+              oidcProviderConfigUrl =
+                oidcProvider + ".well-known/openid-configuration";
+            } else {
+              oidcProviderConfigUrl =
+                oidcProvider + "/.well-known/openid-configuration";
+            }
+            var req = new XMLHttpRequest();
+            req.open("GET", oidcProviderConfigUrl, true);
+            req.setRequestHeader("Accept", "application/json");
+
+            req.onreadystatechange = function () {
+              if (req.readyState == 4) {
+                if (req.status == 200 || fileLoaded(req)) {
+                  var oidcProviderConfig = JSON.parse(req.responseText);
+                  setupOidcEndoints(oidcProviderConfig);
+                  promise.setSuccess();
+                } else {
+                  promise.setError();
+                }
+              }
+            };
+
+            req.send();
+          } else {
+            setupOidcEndoints(oidcProvider);
+            promise.setSuccess();
+          }
+        }
+      }
+
+      return promise.promise;
+    }
+
+    function fileLoaded(xhr) {
+      return (
+        xhr.status == 0 &&
+        xhr.responseText &&
+        xhr.responseURL.startsWith("file:")
+      );
+    }
+
+    function setToken(token, refreshToken, idToken, timeLocal) {
+      if (kc.tokenTimeoutHandle) {
+        clearTimeout(kc.tokenTimeoutHandle);
+        kc.tokenTimeoutHandle = null;
+      }
+
+      if (refreshToken) {
+        kc.refreshToken = refreshToken;
+        kc.refreshTokenParsed = decodeToken(refreshToken);
+      } else {
+        delete kc.refreshToken;
+        delete kc.refreshTokenParsed;
+      }
+
+      if (idToken) {
+        kc.idToken = idToken;
+        kc.idTokenParsed = decodeToken(idToken);
+      } else {
+        delete kc.idToken;
+        delete kc.idTokenParsed;
+      }
+
+      if (token) {
+        kc.token = token;
+        kc.tokenParsed = decodeToken(token);
+        kc.sessionId = kc.tokenParsed.session_state;
+        kc.authenticated = true;
+        kc.subject = kc.tokenParsed.sub;
+        kc.realmAccess = kc.tokenParsed.realm_access;
+        kc.resourceAccess = kc.tokenParsed.resource_access;
+
+        if (timeLocal) {
+          kc.timeSkew = Math.floor(timeLocal / 1000) - kc.tokenParsed.iat;
+        }
+
+        if (kc.timeSkew != null) {
+          logInfo(
+            "[KEYCLOAK] Estimated time difference between browser and server is " +
+              kc.timeSkew +
+              " seconds"
+          );
+
+          if (kc.onTokenExpired) {
+            var expiresIn =
+              (kc.tokenParsed["exp"] -
+                new Date().getTime() / 1000 +
+                kc.timeSkew) *
+              1000;
+            logInfo(
+              "[KEYCLOAK] Token expires in " +
+                Math.round(expiresIn / 1000) +
+                " s"
+            );
+            if (expiresIn <= 0) {
+              kc.onTokenExpired();
+            } else {
+              kc.tokenTimeoutHandle = setTimeout(kc.onTokenExpired, expiresIn);
+            }
+          }
+        }
+      } else {
+        delete kc.token;
+        delete kc.tokenParsed;
+        delete kc.subject;
+        delete kc.realmAccess;
+        delete kc.resourceAccess;
+
+        kc.authenticated = false;
+      }
+    }
+
+    function decodeToken(str) {
+      str = str.split(".")[1];
+
+      str = str.replace(/-/g, "+");
+      str = str.replace(/_/g, "/");
+      switch (str.length % 4) {
+        case 0:
+          break;
+        case 2:
+          str += "==";
+          break;
+        case 3:
+          str += "=";
+          break;
+        default:
+          throw "Invalid token";
+      }
+
+      str = decodeURIComponent(escape(atob(str)));
+
+      str = JSON.parse(str);
+      return str;
+    }
+
+    function createUUID() {
+      var hexDigits = "0123456789abcdef";
+      var s = generateRandomString(36, hexDigits).split("");
+      s[14] = "4";
+      s[19] = hexDigits.substr((s[19] & 0x3) | 0x8, 1);
+      s[8] = s[13] = s[18] = s[23] = "-";
+      var uuid = s.join("");
+      return uuid;
+    }
+
+    function parseCallback(url) {
+      var oauth = parseCallbackUrl(url);
+      if (!oauth) {
+        return;
+      }
+
+      var oauthState = callbackStorage.get(oauth.state);
+
+      if (oauthState) {
+        oauth.valid = true;
+        oauth.redirectUri = oauthState.redirectUri;
+        oauth.storedNonce = oauthState.nonce;
+        oauth.prompt = oauthState.prompt;
+        oauth.pkceCodeVerifier = oauthState.pkceCodeVerifier;
+      }
+
+      return oauth;
+    }
+
+    function parseCallbackUrl(url) {
+      var supportedParams;
+      switch (kc.flow) {
+        case "standard":
+          supportedParams = [
+            "code",
+            "state",
+            "session_state",
+            "kc_action_status",
+          ];
+          break;
+        case "implicit":
+          supportedParams = [
+            "access_token",
+            "token_type",
+            "id_token",
+            "state",
+            "session_state",
+            "expires_in",
+            "kc_action_status",
+          ];
+          break;
+        case "hybrid":
+          supportedParams = [
+            "access_token",
+            "token_type",
+            "id_token",
+            "code",
+            "state",
+            "session_state",
+            "expires_in",
+            "kc_action_status",
+          ];
+          break;
+      }
+
+      supportedParams.push("error");
+      supportedParams.push("error_description");
+      supportedParams.push("error_uri");
+
+      var queryIndex = url.indexOf("?");
+      var fragmentIndex = url.indexOf("#");
+
+      var newUrl;
+      var parsed;
+
+      if (kc.responseMode === "query" && queryIndex !== -1) {
+        newUrl = url.substring(0, queryIndex);
+        parsed = parseCallbackParams(
+          url.substring(
+            queryIndex + 1,
+            fragmentIndex !== -1 ? fragmentIndex : url.length
+          ),
+          supportedParams
+        );
+        if (parsed.paramsString !== "") {
+          newUrl += "?" + parsed.paramsString;
+        }
+        if (fragmentIndex !== -1) {
+          newUrl += url.substring(fragmentIndex);
+        }
+      } else if (kc.responseMode === "fragment" && fragmentIndex !== -1) {
+        newUrl = url.substring(0, fragmentIndex);
+        parsed = parseCallbackParams(
+          url.substring(fragmentIndex + 1),
+          supportedParams
+        );
+        if (parsed.paramsString !== "") {
+          newUrl += "#" + parsed.paramsString;
+        }
+      }
+
+      if (parsed && parsed.oauthParams) {
+        if (kc.flow === "standard" || kc.flow === "hybrid") {
+          if (
+            (parsed.oauthParams.code || parsed.oauthParams.error) &&
+            parsed.oauthParams.state
+          ) {
+            parsed.oauthParams.newUrl = newUrl;
+            return parsed.oauthParams;
+          }
+        } else if (kc.flow === "implicit") {
+          if (
+            (parsed.oauthParams.access_token || parsed.oauthParams.error) &&
+            parsed.oauthParams.state
+          ) {
+            parsed.oauthParams.newUrl = newUrl;
+            return parsed.oauthParams;
+          }
+        }
+      }
+    }
+
+    function parseCallbackParams(paramsString, supportedParams) {
+      var p = paramsString.split("&");
+      var result = {
+        paramsString: "",
+        oauthParams: {},
+      };
+      for (var i = 0; i < p.length; i++) {
+        var split = p[i].indexOf("=");
+        var key = p[i].slice(0, split);
+        if (supportedParams.indexOf(key) !== -1) {
+          result.oauthParams[key] = p[i].slice(split + 1);
+        } else {
+          if (result.paramsString !== "") {
+            result.paramsString += "&";
+          }
+          result.paramsString += p[i];
+        }
+      }
+      return result;
+    }
+
+    function createPromise() {
+      // Need to create a native Promise which also preserves the
+      // interface of the custom promise type previously used by the API
+      var p = {
+        setSuccess: function (result) {
+          p.resolve(result);
+        },
+
+        setError: function (result) {
+          p.reject(result);
+        },
+      };
+      p.promise = new Promise(function (resolve, reject) {
+        p.resolve = resolve;
+        p.reject = reject;
+      });
+
+      p.promise.success = function (callback) {
+        logPromiseDeprecation();
+
+        this.then(function handleSuccess(value) {
+          callback(value);
+        });
+
+        return this;
+      };
+
+      p.promise.error = function (callback) {
+        logPromiseDeprecation();
+
+        this.catch(function handleError(error) {
+          callback(error);
+        });
+
+        return this;
+      };
+
+      return p;
+    }
+
+    // Function to extend existing native Promise with timeout
+    function applyTimeoutToPromise(promise, timeout, errorMessage) {
+      var timeoutHandle = null;
+      var timeoutPromise = new Promise(function (resolve, reject) {
+        timeoutHandle = setTimeout(function () {
+          reject({
+            error:
+              errorMessage ||
+              "Promise is not settled within timeout of " + timeout + "ms",
+          });
+        }, timeout);
+      });
+
+      return Promise.race([promise, timeoutPromise]).finally(function () {
+        clearTimeout(timeoutHandle);
+      });
+    }
+
+    function setupCheckLoginIframe() {
+      var promise = createPromise();
+
+      if (!loginIframe.enable) {
+        promise.setSuccess();
+        return promise.promise;
+      }
+
+      if (loginIframe.iframe) {
+        promise.setSuccess();
+        return promise.promise;
+      }
+
+      var iframe = document.createElement("iframe");
+      loginIframe.iframe = iframe;
+
+      iframe.onload = function () {
+        var authUrl = kc.endpoints.authorize();
+        if (authUrl.charAt(0) === "/") {
+          loginIframe.iframeOrigin = getOrigin();
+        } else {
+          loginIframe.iframeOrigin = authUrl.substring(
+            0,
+            authUrl.indexOf("/", 8)
+          );
+        }
+        promise.setSuccess();
+      };
+
+      var src = kc.endpoints.checkSessionIframe();
+      iframe.setAttribute("src", src);
+      iframe.setAttribute("title", "keycloak-session-iframe");
+      iframe.style.display = "none";
+      document.body.appendChild(iframe);
+
+      var messageCallback = function (event) {
+        if (
+          event.origin !== loginIframe.iframeOrigin ||
+          loginIframe.iframe.contentWindow !== event.source
+        ) {
+          return;
+        }
+
+        if (
+          !(
+            event.data == "unchanged" ||
+            event.data == "changed" ||
+            event.data == "error"
+          )
+        ) {
+          return;
+        }
+
+        if (event.data != "unchanged") {
+          kc.clearToken();
+        }
+
+        var callbacks = loginIframe.callbackList.splice(
+          0,
+          loginIframe.callbackList.length
+        );
+
+        for (var i = callbacks.length - 1; i >= 0; --i) {
+          var promise = callbacks[i];
+          if (event.data == "error") {
+            promise.setError();
+          } else {
+            promise.setSuccess(event.data == "unchanged");
+          }
+        }
+      };
+
+      window.addEventListener("message", messageCallback, false);
+
+      return promise.promise;
+    }
+
+    function scheduleCheckIframe() {
+      if (loginIframe.enable) {
+        if (kc.token) {
+          setTimeout(function () {
+            checkLoginIframe().then(function (unchanged) {
+              if (unchanged) {
+                scheduleCheckIframe();
+              }
+            });
+          }, loginIframe.interval * 1000);
+        }
+      }
+    }
+
+    function checkLoginIframe() {
+      var promise = createPromise();
+
+      if (loginIframe.iframe && loginIframe.iframeOrigin) {
+        var msg = kc.clientId + " " + (kc.sessionId ? kc.sessionId : "");
+        loginIframe.callbackList.push(promise);
+        var origin = loginIframe.iframeOrigin;
+        if (loginIframe.callbackList.length == 1) {
+          loginIframe.iframe.contentWindow.postMessage(msg, origin);
+        }
+      } else {
+        promise.setSuccess();
+      }
+
+      return promise.promise;
+    }
+
+    function check3pCookiesSupported() {
+      var promise = createPromise();
+
+      if (loginIframe.enable || kc.silentCheckSsoRedirectUri) {
+        var iframe = document.createElement("iframe");
+        iframe.setAttribute("src", kc.endpoints.thirdPartyCookiesIframe());
+        iframe.setAttribute("title", "keycloak-3p-check-iframe");
+        iframe.style.display = "none";
+        document.body.appendChild(iframe);
+
+        var messageCallback = function (event) {
+          if (iframe.contentWindow !== event.source) {
+            return;
+          }
+
+          if (event.data !== "supported" && event.data !== "unsupported") {
+            return;
+          } else if (event.data === "unsupported") {
+            loginIframe.enable = false;
+            if (kc.silentCheckSsoFallback) {
+              kc.silentCheckSsoRedirectUri = false;
+            }
+            logWarn(
+              "[KEYCLOAK] 3rd party cookies aren't supported by this browser. checkLoginIframe and " +
+                "silent check-sso are not available."
+            );
+          }
+
+          document.body.removeChild(iframe);
+          window.removeEventListener("message", messageCallback);
+          promise.setSuccess();
+        };
+
+        window.addEventListener("message", messageCallback, false);
+      } else {
+        promise.setSuccess();
+      }
+
+      return applyTimeoutToPromise(
+        promise.promise,
+        kc.messageReceiveTimeout,
+        "Timeout when waiting for 3rd party check iframe message."
+      );
+    }
+
+    function loadAdapter(type) {
+      if (!type || type == "default") {
+        return {
+          login: function (options) {
+            window.location.replace(kc.createLoginUrl(options));
+            return createPromise().promise;
+          },
+
+          logout: function (options) {
+            window.location.replace(kc.createLogoutUrl(options));
+            return createPromise().promise;
+          },
+
+          register: function (options) {
+            window.location.replace(kc.createRegisterUrl(options));
+            return createPromise().promise;
+          },
+
+          accountManagement: function () {
+            var accountUrl = kc.createAccountUrl();
+            if (typeof accountUrl !== "undefined") {
+              window.location.href = accountUrl;
+            } else {
+              throw "Not supported by the OIDC server";
+            }
+            return createPromise().promise;
+          },
+
+          redirectUri: function (options, encodeHash) {
+            if (arguments.length == 1) {
+              encodeHash = true;
+            }
+
+            if (options && options.redirectUri) {
+              return options.redirectUri;
+            } else if (kc.redirectUri) {
+              return kc.redirectUri;
+            } else {
+              return location.href;
+            }
+          },
+        };
+      }
+
+      if (type == "cordova") {
+        loginIframe.enable = false;
+        var cordovaOpenWindowWrapper = function (loginUrl, target, options) {
+          if (window.cordova && window.cordova.InAppBrowser) {
+            // Use inappbrowser for IOS and Android if available
+            return window.cordova.InAppBrowser.open(loginUrl, target, options);
+          } else {
+            return window.open(loginUrl, target, options);
+          }
+        };
+
+        var shallowCloneCordovaOptions = function (userOptions) {
+          if (userOptions && userOptions.cordovaOptions) {
+            return Object.keys(userOptions.cordovaOptions).reduce(function (
+              options,
+              optionName
+            ) {
+              options[optionName] = userOptions.cordovaOptions[optionName];
+              return options;
+            },
+            {});
+          } else {
+            return {};
+          }
+        };
+
+        var formatCordovaOptions = function (cordovaOptions) {
+          return Object.keys(cordovaOptions)
+            .reduce(function (options, optionName) {
+              options.push(optionName + "=" + cordovaOptions[optionName]);
+              return options;
+            }, [])
+            .join(",");
+        };
+
+        var createCordovaOptions = function (userOptions) {
+          var cordovaOptions = shallowCloneCordovaOptions(userOptions);
+          cordovaOptions.location = "no";
+          if (userOptions && userOptions.prompt == "none") {
+            cordovaOptions.hidden = "yes";
+          }
+          return formatCordovaOptions(cordovaOptions);
+        };
+
+        return {
+          login: function (options) {
+            var promise = createPromise();
+
+            var cordovaOptions = createCordovaOptions(options);
+            var loginUrl = kc.createLoginUrl(options);
+            var ref = cordovaOpenWindowWrapper(
+              loginUrl,
+              "_blank",
+              cordovaOptions
+            );
+            var completed = false;
+
+            var closed = false;
+            var closeBrowser = function () {
+              closed = true;
+              ref.close();
+            };
+
+            ref.addEventListener("loadstart", function (event) {
+              if (event.url.indexOf("http://localhost") == 0) {
+                var callback = parseCallback(event.url);
+                processCallback(callback, promise);
+                closeBrowser();
+                completed = true;
+              }
+            });
+
+            ref.addEventListener("loaderror", function (event) {
+              if (!completed) {
+                if (event.url.indexOf("http://localhost") == 0) {
+                  var callback = parseCallback(event.url);
+                  processCallback(callback, promise);
+                  closeBrowser();
+                  completed = true;
+                } else {
+                  promise.setError();
+                  closeBrowser();
+                }
+              }
+            });
+
+            ref.addEventListener("exit", function (event) {
+              if (!closed) {
+                promise.setError({
+                  reason: "closed_by_user",
+                });
+              }
+            });
+
+            return promise.promise;
+          },
+
+          logout: function (options) {
+            var promise = createPromise();
+
+            var logoutUrl = kc.createLogoutUrl(options);
+            var ref = cordovaOpenWindowWrapper(
+              logoutUrl,
+              "_blank",
+              "location=no,hidden=yes,clearcache=yes"
+            );
+
+            var error;
+
+            ref.addEventListener("loadstart", function (event) {
+              if (event.url.indexOf("http://localhost") == 0) {
+                ref.close();
+              }
+            });
+
+            ref.addEventListener("loaderror", function (event) {
+              if (event.url.indexOf("http://localhost") == 0) {
+                ref.close();
+              } else {
+                error = true;
+                ref.close();
+              }
+            });
+
+            ref.addEventListener("exit", function (event) {
+              if (error) {
+                promise.setError();
+              } else {
+                kc.clearToken();
+                promise.setSuccess();
+              }
+            });
+
+            return promise.promise;
+          },
+
+          register: function (options) {
+            var promise = createPromise();
+            var registerUrl = kc.createRegisterUrl();
+            var cordovaOptions = createCordovaOptions(options);
+            var ref = cordovaOpenWindowWrapper(
+              registerUrl,
+              "_blank",
+              cordovaOptions
+            );
+            ref.addEventListener("loadstart", function (event) {
+              if (event.url.indexOf("http://localhost") == 0) {
+                ref.close();
+                var oauth = parseCallback(event.url);
+                processCallback(oauth, promise);
+              }
+            });
+            return promise.promise;
+          },
+
+          accountManagement: function () {
+            var accountUrl = kc.createAccountUrl();
+            if (typeof accountUrl !== "undefined") {
+              var ref = cordovaOpenWindowWrapper(
+                accountUrl,
+                "_blank",
+                "location=no"
+              );
+              ref.addEventListener("loadstart", function (event) {
+                if (event.url.indexOf("http://localhost") == 0) {
+                  ref.close();
+                }
+              });
+            } else {
+              throw "Not supported by the OIDC server";
+            }
+          },
+
+          redirectUri: function (options) {
+            return "http://localhost";
+          },
+        };
+      }
+
+      if (type == "cordova-native") {
+        loginIframe.enable = false;
+
+        return {
+          login: function (options) {
+            var promise = createPromise();
+            var loginUrl = kc.createLoginUrl(options);
+
+            universalLinks.subscribe("keycloak", function (event) {
+              universalLinks.unsubscribe("keycloak");
+              window.cordova.plugins.browsertab.close();
+              var oauth = parseCallback(event.url);
+              processCallback(oauth, promise);
+            });
+
+            window.cordova.plugins.browsertab.openUrl(loginUrl);
+            return promise.promise;
+          },
+
+          logout: function (options) {
+            var promise = createPromise();
+            var logoutUrl = kc.createLogoutUrl(options);
+
+            universalLinks.subscribe("keycloak", function (event) {
+              universalLinks.unsubscribe("keycloak");
+              window.cordova.plugins.browsertab.close();
+              kc.clearToken();
+              promise.setSuccess();
+            });
+
+            window.cordova.plugins.browsertab.openUrl(logoutUrl);
+            return promise.promise;
+          },
+
+          register: function (options) {
+            var promise = createPromise();
+            var registerUrl = kc.createRegisterUrl(options);
+            universalLinks.subscribe("keycloak", function (event) {
+              universalLinks.unsubscribe("keycloak");
+              window.cordova.plugins.browsertab.close();
+              var oauth = parseCallback(event.url);
+              processCallback(oauth, promise);
+            });
+            window.cordova.plugins.browsertab.openUrl(registerUrl);
+            return promise.promise;
+          },
+
+          accountManagement: function () {
+            var accountUrl = kc.createAccountUrl();
+            if (typeof accountUrl !== "undefined") {
+              window.cordova.plugins.browsertab.openUrl(accountUrl);
+            } else {
+              throw "Not supported by the OIDC server";
+            }
+          },
+
+          redirectUri: function (options) {
+            if (options && options.redirectUri) {
+              return options.redirectUri;
+            } else if (kc.redirectUri) {
+              return kc.redirectUri;
+            } else {
+              return "http://localhost";
+            }
+          },
+        };
+      }
+
+      throw "invalid adapter type: " + type;
+    }
+
+    var LocalStorage = function () {
+      if (!(this instanceof LocalStorage)) {
+        return new LocalStorage();
+      }
+
+      localStorage.setItem("kc-test", "test");
+      localStorage.removeItem("kc-test");
+
+      var cs = this;
+
+      function clearExpired() {
+        var time = new Date().getTime();
+        for (var i = 0; i < localStorage.length; i++) {
+          var key = localStorage.key(i);
+          if (key && key.indexOf("kc-callback-") == 0) {
+            var value = localStorage.getItem(key);
+            if (value) {
+              try {
+                var expires = JSON.parse(value).expires;
+                if (!expires || expires < time) {
+                  localStorage.removeItem(key);
+                }
+              } catch (err) {
+                localStorage.removeItem(key);
+              }
+            }
+          }
+        }
+      }
+
+      cs.get = function (state) {
+        if (!state) {
+          return;
+        }
+
+        var key = "kc-callback-" + state;
+        var value = localStorage.getItem(key);
+        if (value) {
+          localStorage.removeItem(key);
+          value = JSON.parse(value);
+        }
+
+        clearExpired();
+        return value;
+      };
+
+      cs.add = function (state) {
+        clearExpired();
+
+        var key = "kc-callback-" + state.state;
+        state.expires = new Date().getTime() + 60 * 60 * 1000;
+        localStorage.setItem(key, JSON.stringify(state));
+      };
+    };
+
+    var CookieStorage = function () {
+      if (!(this instanceof CookieStorage)) {
+        return new CookieStorage();
+      }
+
+      var cs = this;
+
+      cs.get = function (state) {
+        if (!state) {
+          return;
+        }
+
+        var value = getCookie("kc-callback-" + state);
+        setCookie("kc-callback-" + state, "", cookieExpiration(-100));
+        if (value) {
+          return JSON.parse(value);
+        }
+      };
+
+      cs.add = function (state) {
+        setCookie(
+          "kc-callback-" + state.state,
+          JSON.stringify(state),
+          cookieExpiration(60)
+        );
+      };
+
+      cs.removeItem = function (key) {
+        setCookie(key, "", cookieExpiration(-100));
+      };
+
+      var cookieExpiration = function (minutes) {
+        var exp = new Date();
+        exp.setTime(exp.getTime() + minutes * 60 * 1000);
+        return exp;
+      };
+
+      var getCookie = function (key) {
+        var name = key + "=";
+        var ca = document.cookie.split(";");
+        for (var i = 0; i < ca.length; i++) {
+          var c = ca[i];
+          while (c.charAt(0) == " ") {
+            c = c.substring(1);
+          }
+          if (c.indexOf(name) == 0) {
+            return c.substring(name.length, c.length);
+          }
+        }
+        return "";
+      };
+
+      var setCookie = function (key, value, expirationDate) {
+        var cookie =
+          key +
+          "=" +
+          value +
+          "; " +
+          "expires=" +
+          expirationDate.toUTCString() +
+          "; ";
+        document.cookie = cookie;
+      };
+    };
+
+    function createCallbackStorage() {
+      try {
+        return new LocalStorage();
+      } catch (err) {}
+
+      return new CookieStorage();
+    }
+
+    function createLogger(fn) {
+      return function () {
+        if (kc.enableLogging) {
+          fn.apply(console, Array.prototype.slice.call(arguments));
+        }
+      };
+    }
+  }
+
+  return Keycloak;
+});
diff --git a/static/keycloak.json b/static/keycloak.json
new file mode 100644
index 0000000..d143f91
--- /dev/null
+++ b/static/keycloak.json
@@ -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"
+  }
\ No newline at end of file
diff --git a/tsconfig.json b/tsconfig.json
new file mode 100644
index 0000000..3219961
--- /dev/null
+++ b/tsconfig.json
@@ -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
+  }
+}
diff --git a/tsoa.json b/tsoa.json
new file mode 100644
index 0000000..e50408a
--- /dev/null
+++ b/tsoa.json
@@ -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"
+  }
+}
diff --git a/workflows/release.yaml b/workflows/release.yaml
new file mode 100644
index 0000000..0c083cc
--- /dev/null
+++ b/workflows/release.yaml
@@ -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}}
+