diff --git a/src/controllers/DevTestController.ts b/src/controllers/DevTestController.ts new file mode 100644 index 00000000..e3edfaa5 --- /dev/null +++ b/src/controllers/DevTestController.ts @@ -0,0 +1,576 @@ +import { + Controller, + Post, + Put, + Patch, + Delete, + Route, + Security, + Tags, + Body, + Path, + Request, + Response, + Get, + Query, +} from "tsoa"; +import { AppDataSource } from "../database/data-source"; +import HttpStatus from "../interfaces/http-status"; +import HttpSuccess from "../interfaces/http-success"; +import HttpStatusCode from "../interfaces/http-status"; +import HttpError from "../interfaces/http-error"; +import { Command } from "../entities/Command"; +import { Brackets, LessThan, MoreThan, Double, In, Between, IsNull, Not, Any } from "typeorm"; +import { CommandType } from "../entities/CommandType"; +import { Profile, CreateProfileAllFields } from "../entities/Profile"; +import { RequestWithUser, RequestWithUserWebService } from "../middlewares/user"; +import { OrgRevision } from "../entities/OrgRevision"; +import { ProfileEmployee } from "../entities/ProfileEmployee"; +import { PosMaster } from "../entities/PosMaster"; +import permission from "../interfaces/permission"; +import { viewCurrentTenureOfficer } from "../entities/view/viewCurrentTenureOfficer"; +import { CommandController } from "./CommandController"; +import Extension from "../interfaces/extension"; +import { viewRegistryOfficer } from "../entities/view/viewRegistryOfficer"; +import { viewRegistryEmployee } from "../entities/view/viewRegistryEmployee"; +import { Registry } from "../entities/Registry"; +import { RegistryEmployee } from "../entities/RegistryEmployee"; +import { TenurePositionOfficer } from "../entities/TenurePositionOfficer"; +import { PosMasterAssign, PosMasterAssignDTO } from "../entities/PosMasterAssign"; +import { PermissionProfile } from "../entities/PermissionProfile"; +import { OrgRoot } from "../entities/OrgRoot"; +import { MetaWorkflow } from "../entities/MetaWorkflow"; +import { MetaState } from "../entities/MetaState"; +import { MetaStateOperator } from "../entities/MetaStateOperator"; +import { Workflow } from "../entities/Workflow"; +import { State } from "../entities/State"; +import { StateOperator } from "../entities/StateOperator"; +import { StateOperatorUser } from "../entities/StateOperatorUser"; +import { + commandTypePath, + calculateGovAge, + calculateAge, + calculateRetireDate, + calculateRetireLaw, + removeProfileInOrganize, + setLogDataDiff, +} from "../interfaces/utils"; +import CallAPI from "../interfaces/call-api"; +import { PostRetireToExprofile } from "./ExRetirementController" +import { Position } from "../entities/Position"; +import { PosLevel } from "../entities/PosLevel"; +import { TenureLevelOfficer } from "../entities/TenureLevelOfficer"; +import { TenurePositionEmployee } from "../entities/TenurePositionEmployee"; +import { TenureLevelEmployee } from "../entities/TenureLevelEmployee"; +import { TenurePositionExecutiveOfficer } from "../entities/TenurePositionExecutiveOfficer"; + +@Route("api/v1/org/DevTest") +@Tags("DevTest") +@Security("bearerAuth") +@Response( + HttpStatusCode.INTERNAL_SERVER_ERROR, + "เกิดข้อผิดพลาด ไม่สามารถแสดงรายการได้ กรุณาลองใหม่ในภายหลัง", +) +export class DevTestController extends Controller { + private commandRepository = AppDataSource.getRepository(Command); + private commandTypeRepository = AppDataSource.getRepository(CommandType); + private orgRevisionRepo = AppDataSource.getRepository(OrgRevision); + private orgRootRepo = AppDataSource.getRepository(OrgRoot); + private posMasterRepo = AppDataSource.getRepository(PosMaster); + private profileRepo = AppDataSource.getRepository(Profile); + private profileEmpRepo = AppDataSource.getRepository(ProfileEmployee); + private registryRepo = AppDataSource.getRepository(Registry); + private registryEmployeeRepo = AppDataSource.getRepository(RegistryEmployee); + private posMasterAssignRepository = AppDataSource.getRepository(PosMasterAssign); + private permissionProfilesRepository = AppDataSource.getRepository(PermissionProfile); + private profileEmployeeRepo = AppDataSource.getRepository(ProfileEmployee); + private metaWorkflowRepo = AppDataSource.getRepository(MetaWorkflow); + private metaStateRepo = AppDataSource.getRepository(MetaState); + private metaStateOperatorRepo = AppDataSource.getRepository(MetaStateOperator); + private workflowRepo = AppDataSource.getRepository(Workflow); + private stateRepo = AppDataSource.getRepository(State); + private stateOperatorRepo = AppDataSource.getRepository(StateOperator); + private stateOperatorUserRepo = AppDataSource.getRepository(StateOperatorUser); + private positionRepository = AppDataSource.getRepository(Position); + private positionOfficerRepo = AppDataSource.getRepository(TenurePositionOfficer); + private positionEmployeeRepo = AppDataSource.getRepository(TenurePositionEmployee); + private levelOfficerRepo = AppDataSource.getRepository(TenureLevelOfficer); + private levelEmployeeRepo = AppDataSource.getRepository(TenureLevelEmployee); + private positionExecutiveOfficerRepo = AppDataSource.getRepository( + TenurePositionExecutiveOfficer, + ); + + @Patch("tick-officer-registry") + public async calculateOfficerPosition( + @Request() req: RequestWithUser, + @Body() + body: { + profileIds: string[]; + }, + ) { + + console.log("1.") + /** + * =============================== + * PREPARE DATA + * =============================== + */ + const profile = await this.profileRepo.find({ + where: { id: In(body.profileIds) }, + relations: { + posLevel: true, + posType: true, + }, + }); + + if (!profile.length) return; + + const [{ today }] = await AppDataSource.query( + "SELECT CURRENT_DATE() as today", + ); + + const orgRevision = await this.orgRevisionRepo.findOne({ + select: ["id"], + where: { + orgRevisionIsDraft: false, + orgRevisionIsCurrent: true, + }, + }); + + /** + * =============================== + * TRANSACTION + * =============================== + */ + const queryRunner = AppDataSource.createQueryRunner(); + await queryRunner.connect(); + await queryRunner.startTransaction(); + console.log("2.") + try { + /** + * =============================== + * RESULT BUFFERS (SAVE ARRAY) + * =============================== + */ + const positionOfficerBulk: any[] = []; + const levelOfficerBulk: any[] = []; + const executiveOfficerBulk: any[] = []; + console.log("3.") + /** + * =============================== + * MAIN LOOP (SINGLE LOOP) + * =============================== + */ + for (const x of profile) { + const currentDate = + x.isLeave && x.leaveDate + ? Extension.toDateOnlyString(x.leaveDate) + : today; + /** + * ==================================== + * PARALLEL STORED PROCEDURES + * ==================================== + */ + const [ + positionResult, + levelResult, + executiveResult, + ] = await Promise.all([ + AppDataSource.query("CALL GetProfileSalaryPosition(?, ?)", [ + x.id, + currentDate, + ]), + AppDataSource.query("CALL GetProfileSalaryLevel(?, ?)", [ + x.id, + currentDate, + ]), + AppDataSource.query("CALL GetProfileSalaryExecutive(?, ?)", [ + x.id, + currentDate, + ]), + ]); + console.log("4.",x.id) + /** + * ==================================== + * POSITION + * ==================================== + */ + const posRows = positionResult?.[0] ?? []; + const posMap = + posRows.length > 1 + ? posRows.slice(1).map((r: any, i: number) => ({ + days_diff: Number(r.days_diff) || 0, + positionName: posRows[i]?.positionName, + })) + : []; + + const posCal = posMap + .filter((p:any) => p.positionName === x.position) + .reduce( + (a:any, c:any) => ({ + days_diff: a.days_diff + c.days_diff, + positionName: c.positionName, + }), + { days_diff: 0, positionName: null }, + ); + + positionOfficerBulk.push({ + profileId: x.id, + positionName: posCal.positionName, + days_diff: posCal.days_diff, + Years: Math.floor(posCal.days_diff / 365.2524), + Months: Math.floor((posCal.days_diff / 30.4375) % 12), + Days: Math.floor(posCal.days_diff % 30.4375), + }); + console.log("5.",x.id) + /** + * ==================================== + * 2️⃣ POSITION LEVEL + * ==================================== + */ + const lvlRows = levelResult?.[0] ?? []; + const lvlMap = + lvlRows.length > 1 + ? lvlRows.slice(1).map((r: any, i: number) => ({ + days_diff: Number(r.days_diff) || 0, + positionType: lvlRows[i]?.positionType, + positionLevel: lvlRows[i]?.positionLevel, + positionCee: lvlRows[i]?.positionCee, + })) + : []; + + const lvlCal = lvlMap + .filter( + (l:any) => + l.positionLevel === x.posLevel?.posLevelName && + l.positionType === x.posType?.posTypeName, + ) + .reduce( + (a:any, c:any) => ({ + days_diff: a.days_diff + c.days_diff, + positionType: c.positionType, + positionLevel: c.positionLevel, + positionCee: c.positionCee, + }), + { + days_diff: 0, + positionType: null, + positionLevel: null, + positionCee: null, + }, + ); + + levelOfficerBulk.push({ + profileId: x.id, + positionType: lvlCal.positionType, + positionLevel: lvlCal.positionLevel, + positionCee: lvlCal.positionCee, + days_diff: lvlCal.days_diff, + Years: x.posLevel ? (lvlCal.days_diff / 365.2524).toFixed(4) : 0, + Months: x.posLevel ? ((lvlCal.days_diff / 30.4375) % 12).toFixed(4) : 0, + Days: x.posLevel ? (lvlCal.days_diff % 30.4375).toFixed(4) : 0, + }); + console.log("6.",x.id) + /** + * ==================================== + * 3️⃣ POSITION EXECUTIVE + * ==================================== + */ + const exeRows = executiveResult?.[0] ?? []; + const exeMap = + exeRows.length > 1 + ? exeRows.slice(1).map((r: any, i: number) => ({ + days_diff: Number(r.days_diff) || 0, + positionExecutive: exeRows[i]?.positionExecutive, + })) + : []; + + const position = await this.positionRepository.findOne({ + where: { + positionIsSelected: true, + posMaster: { + orgRevisionId: orgRevision?.id, + current_holderId: x.id, + }, + }, + order: { createdAt: "DESC" }, + relations: { + posExecutive: true, + }, + }); + + const exeName = position?.posExecutive?.posExecutiveName; + + const exeCal = exeMap + .filter((e:any) => exeName && e.positionExecutive === exeName) + .reduce( + (a:any, c:any) => ({ + days_diff: a.days_diff + c.days_diff, + positionExecutive: c.positionExecutive, + }), + { days_diff: 0, positionExecutive: null }, + ); + + executiveOfficerBulk.push({ + profileId: x.id, + positionExecutiveName: exeCal.positionExecutive, + days_diff: exeCal.days_diff, + Years: (exeCal.days_diff / 365.2524).toFixed(4), + Months: ((exeCal.days_diff / 30.4375) % 12).toFixed(4), + Days: (exeCal.days_diff % 30.4375).toFixed(4), + }); + } + console.log("7.") + /** + * =============================== + * CLEAR ALL DATA AND SAVE ARRAY (BULK) + * =============================== + */ + await queryRunner.manager + .createQueryBuilder() + .delete() + .from(this.positionOfficerRepo.target) + .execute(); + + await queryRunner.manager + .createQueryBuilder() + .delete() + .from(this.levelOfficerRepo.target) + .execute(); + + await queryRunner.manager + .createQueryBuilder() + .delete() + .from(this.positionExecutiveOfficerRepo.target) + .execute(); + console.log("8.") + await queryRunner.manager.save(this.positionOfficerRepo.target, positionOfficerBulk); + await queryRunner.manager.save(this.levelOfficerRepo.target, levelOfficerBulk); + await queryRunner.manager.save(this.positionExecutiveOfficerRepo.target,executiveOfficerBulk); + console.log("9.") + /** + * =============================== + * REGISTRY OFFICER (SYNC VIEW) + * =============================== + */ + const allRegis = await queryRunner.manager + .getRepository(viewRegistryOfficer) + .createQueryBuilder("registryOfficer") + .where("registryOfficer.profileId IN (:...profileIds)", { + profileIds: new Set(profile.map((p) => p.id)) + }) + .getMany(); + + const mapRegistryData = allRegis.map((x) => ({ + ...x, + isProbation: Boolean(x.isProbation), + isLeave: Boolean(x.isLeave), + isRetirement: Boolean(x.isRetirement), + Educations: x.Educations ? JSON.stringify(x.Educations) : "", + })); + console.log("10.") + + await queryRunner.manager + .createQueryBuilder() + .delete() + .from(this.registryRepo.target) + .execute(); + + if (mapRegistryData.length > 0) { + await queryRunner.manager.save(this.registryRepo.target, mapRegistryData); + } + console.log("11.") + /** + * =============================== + * COMMIT + * =============================== + */ + await queryRunner.commitTransaction(); + } catch (error) { + await queryRunner.rollbackTransaction(); + throw error; + } finally { + await queryRunner.release(); + } + } + + @Post("getDNA") + public async GetData( + @Request() req: RequestWithUser + ){ + let _data: any = { + root: null, + child1: null, + child2: null, + child3: null, + child4: null, + }; + + _data = await new permission().PermissionOrgList(req, "COMMAND"); + return new HttpSuccess(_data); + } + + @Post("calculateGovAge") + public async calculateGovAge( + @Request() req: RequestWithUser, + @Body() + body: { + profileId: string; + }, + ){ + return new HttpSuccess(await calculateGovAge(body.profileId, "OFFICER")); + } + + /** + * @summary Test Job กวาดออกคำสั่ง ทำงานทุกๆตี2 + */ + @Post("cronjobCommand") + async CronjobCommand() { + const commandController = new CommandController(); + await commandController.cronjobCommand(); + } + + /** + * @summary payload & Endpoint ออกคำสั่ง + */ + @Put("path-excec/{id}") + async Bright( + @Path() id: string, + @Request() request: RequestWithUser, + ) { + const command = await this.commandRepository.findOne({ + where: { id: id }, + relations: ["commandType", "commandRecives", "commandSends", "commandSends.commandSendCCs"], + }); + if (!command) { + throw new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบข้อมูลคำสั่งนี้"); + } + const path = commandTypePath(command.commandType.code); + return new HttpSuccess({ + path: path + "/excecute", + refIds: command.commandRecives + .filter((x) => x.refId != null) + .map((x) => ({ + refId: x.refId, + commandNo: command.commandNo, + commandYear: command.commandYear, + commandId: command.id, + remark: command.positionDetail, + amount: x.amount, + amountSpecial: x.amountSpecial, + positionSalaryAmount: x.positionSalaryAmount, + mouthSalaryAmount: x.mouthSalaryAmount, + commandCode: command.commandType.commandCode, + commandName: command.commandType.name, + commandDateAffect: command.commandExcecuteDate, + commandDateSign: command.commandAffectDate, + })), + }); + } + + /** + * API รายละเอียดรายการคำสั่ง tab4 แนบท้าย + * @summary API รายละเอียดรายการคำสั่ง tab4 แนบท้าย + * @param {string} id Id คำสั่ง + * @param {string} profileId profileId + */ + @Get("tab4/attachment/{id}/{profileId}") + async GetByIdTab4Attachment( + @Path() id: string, + @Path() profileId: string, + @Request() request: RequestWithUser + ) { + await new permission().PermissionGet(request, "COMMAND"); + + let profile: Profile | ProfileEmployee | null = null; + profile = await this.profileRepo.findOne({ where: { id: profileId } }); + if (!profile) { + profile = await this.profileEmpRepo.findOne({ where: { id: profileId } }); + if (!profile) + throw new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบข้อมูลบุคคลากรนี้"); + } + + const command = await this.commandRepository.findOne({ + where: { id }, + relations: ["commandType", "commandRecives"], + }); + if (!command) { + throw new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบข้อมูลคำสั่งนี้"); + } + + let _command: any = []; + const path = commandTypePath(command.commandType.code); + if (path == null) throw new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบประเภทคำสั่งนี้ในระบบ"); + await new CallAPI() + .PostData(request, path + "/attachment", { + refIds: command.commandRecives + .filter((x) => + x.refId != null && + x.profileId != null && x.profileId == profileId + ) + .map((x) => ({ + refId: x.refId, + Sequence: x.order, + CitizenId: x.citizenId, + Prefix: x.prefix, + FirstName: x.firstName, + LastName: x.lastName, + Amount: x.amount, + PositionSalaryAmount: x.positionSalaryAmount, + MouthSalaryAmount: x.mouthSalaryAmount, + RemarkHorizontal: x.remarkHorizontal, + RemarkVertical: x.remarkVertical, + CommandYear: command.commandYear, + CommandExcecuteDate: command.commandExcecuteDate, + })), + }) + .then(async (res) => { + _command = res; + }) + .catch(() => {}); + + let issue = + command.isBangkok == "OFFICE" + ? "สำนักปลัดกรุงเทพมหานคร" + : command.isBangkok == "BANGKOK" + ? "กรุงเทพมหานคร" + : null; + if (issue == null) { + const orgRevisionActive = await this.orgRevisionRepo.findOne({ + where: { orgRevisionIsCurrent: true, orgRevisionIsDraft: false }, + relations: ["posMasters", "posMasters.orgRoot"], + }); + if (orgRevisionActive != null) { + const profile = await this.profileRepo.findOne({ + where: { + keycloak: command.createdUserId.toString(), + }, + }); + if (profile != null) { + issue = + orgRevisionActive?.posMasters?.filter((x) => x.current_holderId == profile.id)[0] + ?.orgRoot?.orgRootName || null; + } + } + } + if (issue == null) issue = "..................................."; + return new HttpSuccess({ + template: command.commandType.fileAttachment, + reportName: "xlsx-report", + data: { + data: _command, + issuerOrganizationName: issue, + commandNo: command.commandNo == null ? "" : Extension.ToThaiNumber(command.commandNo), + commandYear: + command.commandYear == null + ? "" + : Extension.ToThaiNumber(Extension.ToThaiYear(command.commandYear).toString()), + commandExcecuteDate: + command.commandExcecuteDate == null + ? "" + : Extension.ToThaiNumber(Extension.ToThaiFullDate2(command.commandExcecuteDate)), + }, + }); + } + +}